@@ -28,29 +28,112 @@ public FileService(Config config, LoggingService loggingService)
2828
2929 #region Public Methods
3030
31+ public bool HasChanges ( string sourcePath , string targetPath )
32+ {
33+ bool hasChanges = false ;
34+
35+ if ( ! File . Exists ( sourcePath ) && ! Directory . Exists ( sourcePath ) )
36+ {
37+ hasChanges = false ; // Source doesn't exist, nothing to compare
38+ }
39+ else if ( ! File . Exists ( targetPath ) && ! Directory . Exists ( targetPath ) )
40+ {
41+ hasChanges = true ; // Source exists but target doesn't, this is a change
42+ }
43+ else if ( File . Exists ( sourcePath ) && File . Exists ( targetPath ) )
44+ {
45+ // Both are files - compare sizes first
46+ FileInfo sourceFile = new ( sourcePath ) ;
47+ FileInfo targetFile = new ( targetPath ) ;
48+
49+ if ( sourceFile . Length != targetFile . Length )
50+ {
51+ hasChanges = true ;
52+ }
53+ else
54+ {
55+ string sourceHash = GetHash ( sourcePath ) ;
56+ string targetHash = GetHash ( targetPath ) ;
57+ hasChanges = sourceHash != targetHash ;
58+ }
59+ }
60+ else if ( Directory . Exists ( sourcePath ) && Directory . Exists ( targetPath ) )
61+ {
62+ // Both are directories - quick checks first
63+ DirectoryInfo sourceDir = new ( sourcePath ) ;
64+ DirectoryInfo targetDir = new ( targetPath ) ;
65+
66+ FileInfo [ ] sourceFiles = sourceDir . GetFiles ( "*.*" , SearchOption . AllDirectories )
67+ . Where ( f => f . Name != _config . BackupScreenshotName )
68+ . ToArray ( ) ;
69+ FileInfo [ ] targetFiles = targetDir . GetFiles ( "*.*" , SearchOption . AllDirectories )
70+ . Where ( f => f . Name != _config . BackupScreenshotName )
71+ . ToArray ( ) ;
72+
73+ if ( sourceFiles . Length != targetFiles . Length )
74+ {
75+ hasChanges = true ;
76+ }
77+ else
78+ {
79+ long sourceTotalSize = sourceFiles . Sum ( f => f . Length ) ;
80+ long targetTotalSize = targetFiles . Sum ( f => f . Length ) ;
81+
82+ if ( sourceTotalSize != targetTotalSize )
83+ {
84+ hasChanges = true ;
85+ }
86+ else
87+ {
88+ string sourceHash = GetHash ( sourcePath ) ;
89+ string targetHash = GetHash ( targetPath ) ;
90+ hasChanges = sourceHash != targetHash ;
91+ }
92+ }
93+ }
94+ else
95+ {
96+ // One is a file and one is a directory,
97+ // probably need to handle this...
98+ hasChanges = true ;
99+ }
100+
101+ return hasChanges ;
102+ }
103+
31104 public string GetHash ( string path )
32105 {
33106 string hash = String . Empty ;
34107 byte [ ] hashData = null ;
35108
36- using SHA256 mySHA256 = SHA256 . Create ( ) ;
37-
38109 if ( Directory . Exists ( path ) )
39110 {
40- List < byte > allHashes = new ( ) ;
111+ using MD5 myMD5 = MD5 . Create ( ) ;
112+ myMD5 . Initialize ( ) ;
41113
42114 DirectoryInfo dir = new ( path ) ;
43115 FileInfo [ ] files = dir . GetFiles ( "*.*" , SearchOption . AllDirectories ) ;
44116
117+ // Sort files to ensure deterministic ordering across different file systems
118+ Array . Sort ( files , ( a , b ) => String . Compare ( a . FullName , b . FullName , StringComparison . Ordinal ) ) ;
119+
45120 foreach ( FileInfo fileInfo in files )
46121 {
47122 if ( fileInfo . Name != _config . BackupScreenshotName )
48123 {
49124 try
50125 {
51- byte [ ] fileData = GetFileData ( fileInfo . FullName ) ;
52- byte [ ] hashValue = mySHA256 . ComputeHash ( fileData ) ;
53- allHashes . AddRange ( hashValue ) ;
126+ byte [ ] fileHash = GetFileHash ( fileInfo . FullName ) ;
127+ if ( fileHash != null )
128+ {
129+ // Use relative path from the base directory for consistency
130+ string relativePath = Path . GetRelativePath ( path , fileInfo . FullName ) ;
131+ byte [ ] pathBytes = System . Text . Encoding . UTF8 . GetBytes ( relativePath ) ;
132+ myMD5 . TransformBlock ( pathBytes , 0 , pathBytes . Length , null , 0 ) ;
133+
134+ // Include file hash
135+ myMD5 . TransformBlock ( fileHash , 0 , fileHash . Length , null , 0 ) ;
136+ }
54137 }
55138 catch ( Exception e )
56139 {
@@ -59,14 +142,15 @@ public string GetHash(string path)
59142 }
60143 }
61144
62- hashData = mySHA256 . ComputeHash ( allHashes . ToArray ( ) ) ;
145+ // Finalize the hash
146+ myMD5 . TransformFinalBlock ( Array . Empty < byte > ( ) , 0 , 0 ) ;
147+ hashData = myMD5 . Hash ;
63148 }
64149 else if ( File . Exists ( path ) )
65150 {
66151 try
67152 {
68- byte [ ] fileData = GetFileData ( path ) ;
69- hashData = mySHA256 . ComputeHash ( fileData ) ;
153+ hashData = GetFileHash ( path ) ;
70154 }
71155 catch ( Exception e )
72156 {
@@ -77,34 +161,78 @@ public string GetHash(string path)
77161 return String . Join ( "" , hashData ? . Select ( x => x . ToString ( "x2" ) ) ?? [ ] ) ;
78162 }
79163
164+ private byte [ ] GetFileHash ( string filePath )
165+ {
166+ int attempts = 0 ;
167+ byte [ ] hashData = null ;
168+ Exception lastError = null ;
169+
170+ if ( File . Exists ( filePath ) )
171+ {
172+ while ( hashData == null && attempts < 3 )
173+ {
174+ try
175+ {
176+ using FileStream fileStream = File . Open ( filePath , FileMode . Open , FileAccess . Read , FileShare . ReadWrite ) ;
177+ using MD5 md5 = MD5 . Create ( ) ;
178+ hashData = md5 . ComputeHash ( fileStream ) ;
179+ }
180+ catch ( Exception e )
181+ {
182+ lastError = e ;
183+ attempts ++ ;
184+ if ( attempts < 3 )
185+ {
186+ // Exponential backoff: 50ms, 100ms, 200ms
187+ int delayMs = 50 * ( 1 << ( attempts - 1 ) ) ;
188+ Thread . Sleep ( delayMs ) ;
189+ }
190+ }
191+ }
192+ }
193+
194+ if ( hashData == null && lastError != null )
195+ {
196+ _loggingService . LogError ( $ "{ nameof ( FileService ) } >{ nameof ( GetFileHash ) } - { lastError } ") ;
197+ }
198+
199+ return hashData ;
200+ }
201+
80202 public byte [ ] GetFileData ( string filePath )
81203 {
82204 int attempts = 0 ;
83205 byte [ ] fileData = null ;
84- string error = null ;
206+ Exception lastError = null ;
85207
86208 if ( File . Exists ( filePath ) )
87209 {
88- while ( fileData == null && ++ attempts < 3 )
210+ while ( fileData == null && attempts < 3 )
89211 {
90212 try
91213 {
92- using FileStream fileStream = File . Open ( filePath , FileMode . Open , FileAccess . Read , FileShare . Read ) ;
214+ using FileStream fileStream = File . Open ( filePath , FileMode . Open , FileAccess . Read , FileShare . ReadWrite ) ;
93215 using MemoryStream memoryStream = new ( ) ;
94216 fileStream . CopyTo ( memoryStream ) ;
95217 fileData = memoryStream . ToArray ( ) ;
96218 }
97219 catch ( Exception e )
98220 {
99- error = e . ToString ( ) ;
100- Thread . Sleep ( 100 ) ;
221+ lastError = e ;
222+ attempts ++ ;
223+ if ( attempts < 3 )
224+ {
225+ // Exponential backoff: 50ms, 100ms, 200ms
226+ int delayMs = 50 * ( 1 << ( attempts - 1 ) ) ;
227+ Thread . Sleep ( delayMs ) ;
228+ }
101229 }
102230 }
103231 }
104232
105- if ( error != null )
233+ if ( fileData == null && lastError != null )
106234 {
107- _loggingService . LogError ( $ "{ nameof ( FileService ) } >{ nameof ( GetFileData ) } - { error } ") ;
235+ _loggingService . LogError ( $ "{ nameof ( FileService ) } >{ nameof ( GetFileData ) } - { lastError } ") ;
108236 }
109237
110238 return fileData ;
@@ -145,4 +273,4 @@ public void CopyDirectory(string sourceDir, string destinationDir, bool recursiv
145273 }
146274
147275 #endregion
148- }
276+ }
0 commit comments