@@ -15,39 +15,12 @@ internal class SmbLibProvider : ProviderBase
1515 {
1616 SMB2Client client ;
1717 ISMBFileStore fileStore ;
18- string Server
19- {
20- get
21- {
22- if ( JobOptions . DestinationPath . StartsWith ( "smb://" , StringComparison . OrdinalIgnoreCase ) ) //Linux syntax
23- {
24- //Pattern smb://ServerName/ShareName/Folder1/Folder2
25- return JobOptions . DestinationPath . Substring ( 1 ) ;
26- }
27- else
28- {
29- //Pattern \\ServerName\ShareName\Folder1\Folder2
30- return ( JobOptions . DestinationPath . StartsWith ( "\\ \\ " ) ? JobOptions . DestinationPath . Substring ( 2 ) : JobOptions . DestinationPath ) . Split ( '\\ ' ) [ 0 ] ;
31- }
32- }
33- }
34- string Share
35- {
36- get
37- {
38- var remainingDestination = JobOptions . DestinationPath . Substring ( JobOptions . DestinationPath . IndexOf ( Server ) + Server . Length ) ;
39- return remainingDestination . Trim ( '/' , '\\ ' ) . Split ( '/' , '\\ ' ) [ 0 ] ;
40- }
41- }
42- string DestinationPath
43- {
44- get
45- {
46- return JobOptions . DestinationPath . Substring ( JobOptions . DestinationPath . IndexOf ( Share ) + Share . Length ) . Replace ( '/' , '\\ ' ) ;
47- }
48- }
4918
50- public SmbLibProvider ( IFileJobOptions options ) : base ( options )
19+
20+
21+
22+
23+ public SmbLibProvider ( IFileJobOptions options ) : base ( options )
5124 {
5225
5326 }
@@ -59,65 +32,164 @@ public override void SyncSourceToDest()
5932 {
6033 if ( ! ( JobOptions is IFileSyncJobOptions jobOptions ) )
6134 throw new ArgumentException ( "this instance has no information about syncing files, it has type " + JobOptions . GetType ( ) . ToString ( ) ) ;
62- Directory . CreateDirectory ( jobOptions . SourcePath ) ;
63- //Dateien ins Backup kopieren
64- if ( JobOptions . Credentials != null )
65- {
66- ConnectToShare ( Server , Share , JobOptions . Credentials . Domain , JobOptions . Credentials . UserName , JobOptions . Credentials . Password ) ;
67- }
6835
69- DirectoryInfo _di = new DirectoryInfo ( jobOptions . SourcePath ) ;
36+ //Determine if source or destination is network path
37+ bool sourceIsNetShare = jobOptions . SourcePath . StartsWith ( "\\ \\ " ) ;
38+ bool destIsNetShare = jobOptions . DestinationPath . StartsWith ( "\\ \\ " ) ;
39+ if ( sourceIsNetShare && destIsNetShare )
40+ throw new NotImplementedException ( "both source and destination are network shares - this is not supported yet" ) ;
41+
42+ if ( ! sourceIsNetShare && ! destIsNetShare )
43+ throw new Exception ( "neither source or destination are network share paths - unable to handle with SmbLibProvider" ) ;
7044
71- foreach ( var dir in JobOptions . Subfolders . Count > 0 ? _di . GetDirectories ( ) : new [ ] { _di } )
45+ if ( ! sourceIsNetShare && destIsNetShare )
7246 {
73- if ( JobOptions . Subfolders . Count > 0 && ! JobOptions . Subfolders . Select ( x => x . ToLower ( ) ) . Contains ( dir . Name . ToLower ( ) ) )
74- continue ;
47+ Directory . CreateDirectory ( jobOptions . SourcePath ) ;
7548
49+ string DestinationServer = JobOptions . DestinationPath . StartsWith ( "smb://" , StringComparison . OrdinalIgnoreCase ) ?
50+ JobOptions . DestinationPath . Substring ( 1 ) :
51+ ( JobOptions . DestinationPath . StartsWith ( "\\ \\ " ) ? JobOptions . DestinationPath . Substring ( 2 ) : JobOptions . DestinationPath ) . Split ( '\\ ' ) [ 0 ] ;
7652
53+ string DestinationShare = JobOptions . DestinationPath . Substring ( JobOptions . DestinationPath . IndexOf ( DestinationServer ) + DestinationServer . Length ) . Trim ( '/' , '\\ ' ) . Split ( '/' , '\\ ' ) [ 0 ] ;
54+ string DestinationPath = JobOptions . DestinationPath . Substring ( JobOptions . DestinationPath . IndexOf ( DestinationShare ) + DestinationShare . Length ) . Replace ( '/' , '\\ ' ) ;
7755
78- var _fi = dir . EnumerateFiles (
79- searchPattern : JobOptions . SearchPattern ,
80- searchOption : JobOptions . Recursive ? SearchOption . AllDirectories : SearchOption . TopDirectoryOnly ) ;
81-
82- if ( jobOptions . SyncDeleted )
56+
57+
58+
59+ //Dateien ins Backup kopieren
60+ if ( JobOptions . Credentials != null )
8361 {
84- var remoteFiles = ListFiles ( DestinationPath , true ) ;
85- foreach ( var file in remoteFiles )
86- {
87- var realFilePath = file . Substring ( file . IndexOf ( DestinationPath ) + DestinationPath . Length ) . Trim ( '\\ ' ) . Replace ( '/' , '\\ ' ) ;
88- if ( ! _fi . Any ( x => x . FullName . Replace ( '/' , '\\ ' ) . EndsWith ( realFilePath ) ) )
89- DeleteFile ( file ) ;
90- }
62+ ConnectToShare ( DestinationServer , DestinationShare , JobOptions . Credentials . Domain , JobOptions . Credentials . UserName , JobOptions . Credentials . Password ) ;
9163 }
92- foreach ( FileInfo f in _fi )
64+ else
9365 {
94- bool copy = false ;
95- var relativeFilename = f . FullName . Substring ( Path . GetFullPath ( jobOptions . SourcePath ) . Length ) ;
96- var remotefile = Path . Combine ( DestinationPath , relativeFilename . TrimStart ( '\\ ' , '/' ) ) . Replace ( '/' , '\\ ' ) ;
97- var exists = FileExists ( remotefile , out long size ) ;
98- copy = ! exists || size != f . Length ;
99- if ( copy )
66+ throw new ArgumentNullException ( "Credentials are not set - cannot connect to share" ) ;
67+ }
68+
69+ DirectoryInfo _di = new DirectoryInfo ( jobOptions . SourcePath ) ;
70+
71+ foreach ( var dir in JobOptions . Subfolders . Count > 0 ? _di . GetDirectories ( ) : new [ ] { _di } )
72+ {
73+ if ( JobOptions . Subfolders . Count > 0 && ! JobOptions . Subfolders . Select ( x => x . ToLower ( ) ) . Contains ( dir . Name . ToLower ( ) ) )
74+ continue ;
75+
76+
77+ var _fi = dir . EnumerateFiles (
78+ searchPattern : JobOptions . SearchPattern ,
79+ searchOption : JobOptions . Recursive ? SearchOption . AllDirectories : SearchOption . TopDirectoryOnly ) ;
80+
81+ if ( jobOptions . SyncDeleted )
10082 {
101- logger . LogDebug ( "Copy {A}" , relativeFilename ) ;
102- //File.Copy(f.FullName, remotefile.FullName, true);
103- //CopyFileWithBuffer(f, remotefile);
104- try
83+ var remoteFiles = ListFiles ( DestinationPath , true ) ;
84+ foreach ( var file in remoteFiles )
10585 {
106- WriteFile ( f . FullName , remotefile ) ;
107- if ( jobOptions . DeleteSourceAfterBackup )
86+ var realFilePath = file . Substring ( file . IndexOf ( DestinationPath ) + DestinationPath . Length ) . Trim ( '\\ ' ) . Replace ( '/' , '\\ ' ) ;
87+ if ( ! _fi . Any ( x => x . FullName . Replace ( '/' , '\\ ' ) . EndsWith ( realFilePath ) ) )
88+ DeleteFile ( file ) ;
89+ }
90+ }
91+ foreach ( FileInfo f in _fi )
92+ {
93+ bool copy = false ;
94+ var relativeFilename = f . FullName . Substring ( Path . GetFullPath ( jobOptions . SourcePath ) . Length ) ;
95+ var remotefile = Path . Combine ( DestinationPath , relativeFilename . TrimStart ( '\\ ' , '/' ) ) . Replace ( '/' , '\\ ' ) ;
96+ var exists = FileExists ( remotefile , out long size ) ;
97+ copy = ! exists || size != f . Length ;
98+ if ( copy )
99+ {
100+ logger . LogDebug ( "Copy {A}" , relativeFilename ) ;
101+ try
102+ {
103+ WriteFile ( f . FullName , remotefile ) ;
104+ if ( jobOptions . DeleteSourceAfterBackup )
105+ {
106+ File . Delete ( f . FullName ) ;
107+ }
108+ }
109+ catch ( Exception exc )
108110 {
109- File . Delete ( f . FullName ) ;
111+ logger . LogError ( exc , "Exception in WriteFile for {A}" , relativeFilename ) ;
110112 }
113+
111114 }
112- catch ( Exception exc )
115+ else
116+ logger . LogDebug ( "Skip {A}" , relativeFilename ) ;
117+ }
118+ }
119+ }
120+ else //(sourceIsNetShare && !destIsNetShare)
121+ {
122+ string SourceServer = jobOptions . SourcePath . StartsWith ( "smb://" , StringComparison . OrdinalIgnoreCase ) ?
123+ jobOptions . SourcePath . Substring ( 1 ) :
124+ ( jobOptions . SourcePath . StartsWith ( "\\ \\ " ) ? jobOptions . SourcePath . Substring ( 2 ) : jobOptions . SourcePath ) . Split ( '\\ ' ) [ 0 ] ;
125+
126+ string SourceShare = jobOptions . SourcePath . Substring ( jobOptions . SourcePath . IndexOf ( SourceServer ) + SourceServer . Length ) . Trim ( '/' , '\\ ' ) . Split ( '/' , '\\ ' ) [ 0 ] ;
127+ string SourcePath = jobOptions . SourcePath . Substring ( jobOptions . SourcePath . IndexOf ( SourceShare ) + SourceShare . Length ) . Replace ( '/' , '\\ ' ) ;
128+
129+ if ( JobOptions . Credentials != null )
130+ {
131+ ConnectToShare ( SourceServer , SourceShare , JobOptions . Credentials . Domain , JobOptions . Credentials . UserName , JobOptions . Credentials . Password ) ;
132+ }
133+ else
134+ {
135+ throw new ArgumentNullException ( "Credentials are not set - cannot connect to share" ) ;
136+ }
137+
138+ DirectoryInfo _di = new DirectoryInfo ( jobOptions . DestinationPath ) ;
139+
140+ foreach ( var dir in JobOptions . Subfolders . Count > 0 ? _di . GetDirectories ( ) : new [ ] { _di } )
141+ {
142+ if ( JobOptions . Subfolders . Count > 0 && ! JobOptions . Subfolders . Select ( x => x . ToLower ( ) ) . Contains ( dir . Name . ToLower ( ) ) )
143+ continue ;
144+
145+
146+ var _fi = dir . EnumerateFiles (
147+ searchPattern : JobOptions . SearchPattern ,
148+ searchOption : JobOptions . Recursive ? SearchOption . AllDirectories : SearchOption . TopDirectoryOnly ) ;
149+
150+ var remoteFiles = ListFiles ( SourcePath , true ) ;
151+ if ( jobOptions . SyncDeleted )
152+ {
153+ foreach ( var file in remoteFiles )
113154 {
114- logger . LogError ( exc , "Exception in WriteFile for {A}" , relativeFilename ) ;
155+ var realFilePath = file . Substring ( file . IndexOf ( SourcePath ) + SourcePath . Length ) . Trim ( '\\ ' ) . Replace ( '/' , '\\ ' ) ;
156+ if ( ! _fi . Any ( x => x . FullName . Replace ( '/' , '\\ ' ) . EndsWith ( realFilePath ) ) )
157+ File . Delete ( file ) ;
115158 }
159+ }
160+ foreach ( var remoteFile in remoteFiles )
161+ {
162+ bool copy = false ;
163+ var realFilePath = remoteFile . Substring ( remoteFile . IndexOf ( SourcePath ) + SourcePath . Length ) . Trim ( '\\ ' ) . Replace ( '/' , '\\ ' ) ;
164+
165+ var localFile = Path . Combine ( jobOptions . DestinationPath , realFilePath . TrimStart ( '\\ ' , '/' ) ) . Replace ( '/' , '\\ ' ) ;
166+ var exists = File . Exists ( localFile ) ;
167+ _ = FileExists ( remoteFile , out long remoteSize ) ;
168+ var size = exists ? new FileInfo ( localFile ) . Length : 0 ;
169+ copy = ! exists || size != remoteSize ;
170+ if ( copy )
171+ {
172+ logger . LogDebug ( "Copy {A}" , realFilePath ) ;
173+ try
174+ {
175+ ReadFile ( remoteFile , localFile ) ;
176+ if ( jobOptions . DeleteSourceAfterBackup )
177+ {
178+ DeleteFile ( remoteFile ) ;
179+ }
180+ }
181+ catch ( Exception exc )
182+ {
183+ logger . LogError ( exc , "Exception in ReadFile for {A}" , realFilePath ) ;
184+ }
116185
186+ }
187+ else
188+ logger . LogDebug ( "Skip {A}" , realFilePath ) ;
117189 }
118- else
119- logger . LogDebug ( "Skip {A}" , relativeFilename ) ;
120190 }
191+
192+
121193 }
122194 }
123195
@@ -133,7 +205,7 @@ public void ConnectToShare(string server, string shareName, string domain, strin
133205 bool isConnected = client . Connect ( server , SMBTransportType . DirectTCPTransport ) ;
134206 if ( isConnected )
135207 {
136- logger . LogDebug ( "ConnectToShare with domain {A} and user {B} with {C} pass" , domain , user , password . Select ( x=> '*' ) ) ;
208+ logger . LogDebug ( "ConnectToShare with domain {A} and user {B} with {C} pass" , domain , user , password . Select ( x => '*' ) ) ;
137209 status = client . Login ( domain , user , password ) ;
138210 if ( status == NTStatus . STATUS_SUCCESS )
139211 {
@@ -161,6 +233,45 @@ public void Dispose()
161233 fileStore ? . Disconnect ( ) ;
162234 }
163235
236+ public void ReadFile ( string remoteFilePath , string localFilePath )
237+ {
238+ object fileHandle ;
239+ FileStatus fileStatus ;
240+
241+ var status = fileStore . CreateFile ( out fileHandle , out fileStatus , remoteFilePath , AccessMask . GENERIC_READ | AccessMask . SYNCHRONIZE , FileAttributes . Normal , ShareAccess . Read , CreateDisposition . FILE_OPEN , CreateOptions . FILE_NON_DIRECTORY_FILE | CreateOptions . FILE_SYNCHRONOUS_IO_ALERT , null ) ;
242+
243+ if ( status == NTStatus . STATUS_SUCCESS )
244+ {
245+ Directory . CreateDirectory ( Path . GetDirectoryName ( localFilePath ) ) ;
246+ var stream = File . Open ( localFilePath , FileMode . Create , FileAccess . Write , FileShare . Read ) ;
247+ byte [ ] data ;
248+ long bytesRead = 0 ;
249+ while ( true )
250+ {
251+ status = fileStore . ReadFile ( out data , fileHandle , bytesRead , ( int ) client . MaxReadSize ) ;
252+ if ( status != NTStatus . STATUS_SUCCESS && status != NTStatus . STATUS_END_OF_FILE )
253+ {
254+ throw new Exception ( "Failed to read from file" ) ;
255+ }
256+
257+ if ( status == NTStatus . STATUS_END_OF_FILE || data . Length == 0 )
258+ {
259+ break ;
260+ }
261+ bytesRead += data . Length ;
262+ stream . Write ( data , 0 , data . Length ) ;
263+ }
264+ stream . Close ( ) ;
265+ }
266+ if ( fileHandle != null )
267+ status = fileStore . CloseFile ( fileHandle ) ;
268+ var fileInfo = new FileInfo ( localFilePath ) ;
269+ GetFileAttributes ( remoteFilePath , out DateTime lastWriteTime , out DateTime creationTime , out _ , out DateTime lastAccessTime ) ;
270+ fileInfo . LastWriteTime = lastWriteTime ;
271+ fileInfo . CreationTime = creationTime ;
272+ fileInfo . LastAccessTime = lastAccessTime ;
273+ }
274+
164275 public void WriteFile ( string localFilePath , string remoteFilePath )
165276 {
166277 NTStatus status ;
@@ -283,6 +394,46 @@ bool FileExists(string filepathFromShare, out long size)
283394 return false ;
284395
285396 }
397+ void SetFileAttributes ( string filepathFromShare , DateTime lastWriteTime , DateTime createTime , DateTime modifiedTime , DateTime accessTime )
398+ {
399+ object directoryHandle ;
400+ FileStatus fileStatus ;
401+ var status = fileStore . CreateFile ( out directoryHandle , out fileStatus , filepathFromShare . Trim ( '\\ ' ) , AccessMask . GENERIC_READ , FileAttributes . Normal , ShareAccess . Read | ShareAccess . Write , CreateDisposition . FILE_OPEN , CreateOptions . FILE_NON_DIRECTORY_FILE , null ) ;
402+ if ( status == NTStatus . STATUS_SUCCESS )
403+ {
404+
405+ status = fileStore . GetFileInformation ( out FileInformation result , directoryHandle , FileInformationClass . FileBasicInformation ) ;
406+ ( result as FileBasicInformation ) . LastWriteTime = lastWriteTime ;
407+ ( result as FileBasicInformation ) . CreationTime = createTime ;
408+ ( result as FileBasicInformation ) . ChangeTime = modifiedTime ;
409+ ( result as FileBasicInformation ) . LastAccessTime = accessTime ;
410+ status = fileStore . SetFileInformation ( directoryHandle , result ) ;
411+ status = fileStore . CloseFile ( directoryHandle ) ;
412+ }
413+ throw new Exception ( "unable to set attributes - status " + status ) ;
414+
415+ }
416+ void GetFileAttributes ( string filepathFromShare , out DateTime lastWriteTime , out DateTime createTime , out DateTime modifiedTime , out DateTime accessTime )
417+ {
418+ object directoryHandle ;
419+ FileStatus fileStatus ;
420+ var status = fileStore . CreateFile ( out directoryHandle , out fileStatus , filepathFromShare . Trim ( '\\ ' ) , AccessMask . GENERIC_READ , FileAttributes . Normal , ShareAccess . Read | ShareAccess . Write , CreateDisposition . FILE_OPEN , CreateOptions . FILE_NON_DIRECTORY_FILE , null ) ;
421+ if ( status == NTStatus . STATUS_SUCCESS )
422+ {
423+
424+ status = fileStore . GetFileInformation ( out FileInformation result , directoryHandle , FileInformationClass . FileBasicInformation ) ;
425+ lastWriteTime = ( result as FileBasicInformation ) . LastWriteTime . Time . Value ;
426+ modifiedTime = ( result as FileBasicInformation ) . ChangeTime . Time . Value ;
427+ createTime = ( result as FileBasicInformation ) . CreationTime . Time . Value ;
428+ accessTime = ( result as FileBasicInformation ) . LastAccessTime . Time . Value ;
429+ status = fileStore . CloseFile ( directoryHandle ) ;
430+ }
431+ else
432+ {
433+ throw new Exception ( "unable to set attributes - status " + status ) ;
434+ }
435+
436+ }
286437
287438
288439
0 commit comments