add more resilency, performance adjustments
This commit is contained in:
		| @@ -47,7 +47,7 @@ public class FileDbManager | |||||||
|  |  | ||||||
|     public FileCache? ValidateFileCacheEntity(FileCacheEntity fileCacheEntity) |     public FileCache? ValidateFileCacheEntity(FileCacheEntity fileCacheEntity) | ||||||
|     { |     { | ||||||
|         return GetValidatedFileCache(fileCacheEntity); |         return GetValidatedFileCache(fileCacheEntity, false); | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     public FileCache? GetFileCacheByPath(string path) |     public FileCache? GetFileCacheByPath(string path) | ||||||
| @@ -112,23 +112,26 @@ public class FileDbManager | |||||||
|         return result; |         return result; | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     private FileCache? GetValidatedFileCache(FileCacheEntity e) |     private FileCache? GetValidatedFileCache(FileCacheEntity e, bool removeOnNonExistence = true) | ||||||
|     { |     { | ||||||
|         var fileCache = new FileCache(e); |         var fileCache = new FileCache(e); | ||||||
|         var resulingFileCache = MigrateLegacy(fileCache); |         var resulingFileCache = MigrateLegacy(fileCache); | ||||||
|         if (resulingFileCache == null) return null; |         if (resulingFileCache == null) return null; | ||||||
|  |  | ||||||
|         resulingFileCache = ReplacePathPrefixes(resulingFileCache); |         resulingFileCache = ReplacePathPrefixes(resulingFileCache); | ||||||
|         resulingFileCache = Validate(resulingFileCache); |         resulingFileCache = Validate(resulingFileCache, removeOnNonExistence); | ||||||
|         return resulingFileCache; |         return resulingFileCache; | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     private FileCache? Validate(FileCache fileCache) |     private FileCache? Validate(FileCache fileCache, bool removeOnNonExistence = true) | ||||||
|     { |     { | ||||||
|         var file = new FileInfo(fileCache.Filepath); |         var file = new FileInfo(fileCache.Filepath); | ||||||
|         if (!file.Exists) |         if (!file.Exists) | ||||||
|  |         { | ||||||
|  |             if (removeOnNonExistence) | ||||||
|             { |             { | ||||||
|                 DeleteFromDatabase(new[] { fileCache }); |                 DeleteFromDatabase(new[] { fileCache }); | ||||||
|  |             } | ||||||
|             return null; |             return null; | ||||||
|         } |         } | ||||||
|  |  | ||||||
| @@ -141,7 +144,7 @@ public class FileDbManager | |||||||
|         return fileCache; |         return fileCache; | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     private FileCache? MigrateLegacy(FileCache fileCache) |     private FileCache? MigrateLegacy(FileCache fileCache, bool removeOnNonExistence = true) | ||||||
|     { |     { | ||||||
|         if (fileCache.OriginalFilepath.Contains(PenumbraPrefix + "\\") || fileCache.OriginalFilepath.Contains(CachePrefix)) return fileCache; |         if (fileCache.OriginalFilepath.Contains(PenumbraPrefix + "\\") || fileCache.OriginalFilepath.Contains(CachePrefix)) return fileCache; | ||||||
|  |  | ||||||
| @@ -165,8 +168,11 @@ public class FileDbManager | |||||||
|             MigrateLegacyFilePath(fileCache, newPath); |             MigrateLegacyFilePath(fileCache, newPath); | ||||||
|         } |         } | ||||||
|         else |         else | ||||||
|  |         { | ||||||
|  |             if (removeOnNonExistence) | ||||||
|             { |             { | ||||||
|                 DeleteFromDatabase(new[] { fileCache }); |                 DeleteFromDatabase(new[] { fileCache }); | ||||||
|  |             } | ||||||
|             return null; |             return null; | ||||||
|         } |         } | ||||||
|  |  | ||||||
|   | |||||||
| @@ -1,4 +1,5 @@ | |||||||
| using System; | using System; | ||||||
|  | using System.Collections.Concurrent; | ||||||
| using System.Collections.Generic; | using System.Collections.Generic; | ||||||
| using System.IO; | using System.IO; | ||||||
| using System.Linq; | using System.Linq; | ||||||
| @@ -38,14 +39,23 @@ public class PeriodicFileScanner : IDisposable | |||||||
|  |  | ||||||
|     private void _apiController_DownloadFinished() |     private void _apiController_DownloadFinished() | ||||||
|     { |     { | ||||||
|         InvokeScan(); |         if (fileScanWasRunning) | ||||||
|  |         { | ||||||
|  |             fileScanWasRunning = false; | ||||||
|  |             InvokeScan(true); | ||||||
|  |         } | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     private void _apiController_DownloadStarted() |     private void _apiController_DownloadStarted() | ||||||
|  |     { | ||||||
|  |         if (IsScanRunning) | ||||||
|         { |         { | ||||||
|             _scanCancellationTokenSource?.Cancel(); |             _scanCancellationTokenSource?.Cancel(); | ||||||
|  |             fileScanWasRunning = true; | ||||||
|  |         } | ||||||
|     } |     } | ||||||
|  |  | ||||||
|  |     private bool fileScanWasRunning = false; | ||||||
|     private long currentFileProgress = 0; |     private long currentFileProgress = 0; | ||||||
|     public long CurrentFileProgress => currentFileProgress; |     public long CurrentFileProgress => currentFileProgress; | ||||||
|  |  | ||||||
| @@ -72,6 +82,8 @@ public class PeriodicFileScanner : IDisposable | |||||||
|     public void InvokeScan(bool forced = false) |     public void InvokeScan(bool forced = false) | ||||||
|     { |     { | ||||||
|         bool isForced = forced; |         bool isForced = forced; | ||||||
|  |         TotalFiles = 0; | ||||||
|  |         currentFileProgress = 0; | ||||||
|         _scanCancellationTokenSource?.Cancel(); |         _scanCancellationTokenSource?.Cancel(); | ||||||
|         _scanCancellationTokenSource = new CancellationTokenSource(); |         _scanCancellationTokenSource = new CancellationTokenSource(); | ||||||
|         var token = _scanCancellationTokenSource.Token; |         var token = _scanCancellationTokenSource.Token; | ||||||
| @@ -83,6 +95,8 @@ public class PeriodicFileScanner : IDisposable | |||||||
|                 if (!_pluginConfiguration.FileScanPaused || isForced) |                 if (!_pluginConfiguration.FileScanPaused || isForced) | ||||||
|                 { |                 { | ||||||
|                     isForced = false; |                     isForced = false; | ||||||
|  |                     TotalFiles = 0; | ||||||
|  |                     currentFileProgress = 0; | ||||||
|                     PeriodicFileScan(token); |                     PeriodicFileScan(token); | ||||||
|                 } |                 } | ||||||
|                 _timeUntilNextScan = TimeSpan.FromSeconds(timeBetweenScans); |                 _timeUntilNextScan = TimeSpan.FromSeconds(timeBetweenScans); | ||||||
| @@ -163,44 +177,75 @@ public class PeriodicFileScanner : IDisposable | |||||||
|                                 .Select(s => s.ToLowerInvariant()); |                                 .Select(s => s.ToLowerInvariant()); | ||||||
|  |  | ||||||
|         var scannedFiles = new Dictionary<string, bool>(penumbraFiles.Concat(cacheFiles).Select(c => new KeyValuePair<string, bool>(c, false))); |         var scannedFiles = new Dictionary<string, bool>(penumbraFiles.Concat(cacheFiles).Select(c => new KeyValuePair<string, bool>(c, false))); | ||||||
|  |  | ||||||
|         List<FileCacheEntity> fileDbEntries; |  | ||||||
|         using (var db = new FileCacheContext()) |  | ||||||
|         { |  | ||||||
|             fileDbEntries = db.FileCaches.AsNoTracking().ToList(); |  | ||||||
|         } |  | ||||||
|         TotalFiles = scannedFiles.Count; |         TotalFiles = scannedFiles.Count; | ||||||
|  |  | ||||||
|         Logger.Debug("Database contains " + fileDbEntries.Count + " files, local system contains " + TotalFiles); |  | ||||||
|         // scan files from database |         // scan files from database | ||||||
|         var cpuCount = (int)(Environment.ProcessorCount / 2.0f); |         var cpuCount = (int)(Environment.ProcessorCount / 2.0f); | ||||||
|         foreach (var chunk in fileDbEntries.Chunk(cpuCount)) |         Task[] dbTasks = Enumerable.Range(0, cpuCount).Select(c => Task.CompletedTask).ToArray(); | ||||||
|  |         using (var db = new FileCacheContext()) | ||||||
|         { |         { | ||||||
|             Task[] tasks = chunk.Select(c => Task.Run(() => |             Logger.Debug("Database contains " + db.FileCaches.Count() + " files, local system contains " + TotalFiles); | ||||||
|  |             ConcurrentBag<FileCacheEntity> entitiesToRemove = new(); | ||||||
|  |             try | ||||||
|  |             { | ||||||
|  |                 foreach (var entry in db.FileCaches.AsNoTracking()) | ||||||
|  |                 { | ||||||
|  |                     var idx = Task.WaitAny(dbTasks, ct); | ||||||
|  |                     dbTasks[idx] = Task.Run(() => | ||||||
|                     { |                     { | ||||||
|                         try |                         try | ||||||
|                         { |                         { | ||||||
|                     var file = _fileDbManager.ValidateFileCacheEntity(c); |                             var file = _fileDbManager.ValidateFileCacheEntity(entry); | ||||||
|                             if (file != null) |                             if (file != null) | ||||||
|                             { |                             { | ||||||
|                                 scannedFiles[file.Filepath] = true; |                                 scannedFiles[file.Filepath] = true; | ||||||
|                             } |                             } | ||||||
|  |                             else | ||||||
|  |                             { | ||||||
|  |                                 entitiesToRemove.Add(entry); | ||||||
|  |                             } | ||||||
|                         } |                         } | ||||||
|                         catch (Exception ex) |                         catch (Exception ex) | ||||||
|                         { |                         { | ||||||
|                     Logger.Warn("Failed validating " + c.Filepath); |                             Logger.Warn("Failed validating " + entry.Filepath); | ||||||
|                             Logger.Warn(ex.Message); |                             Logger.Warn(ex.Message); | ||||||
|  |                             entitiesToRemove.Add(entry); | ||||||
|                         } |                         } | ||||||
|  |  | ||||||
|                         Interlocked.Increment(ref currentFileProgress); |                         Interlocked.Increment(ref currentFileProgress); | ||||||
|             })).ToArray(); |                         Thread.Sleep(1); | ||||||
|  |                     }, ct); | ||||||
|             Task.WaitAll(tasks, ct); |  | ||||||
|  |  | ||||||
|             Thread.Sleep(3); |  | ||||||
|  |  | ||||||
|                     if (ct.IsCancellationRequested) return; |                     if (ct.IsCancellationRequested) return; | ||||||
|                 } |                 } | ||||||
|  |             } | ||||||
|  |             catch (Exception ex) | ||||||
|  |             { | ||||||
|  |                 Logger.Warn("Error during enumerating FileCaches: " + ex.Message); | ||||||
|  |             } | ||||||
|  |  | ||||||
|  |             try | ||||||
|  |             { | ||||||
|  |                 if (entitiesToRemove.Any()) | ||||||
|  |                 { | ||||||
|  |                     foreach (var entry in entitiesToRemove) | ||||||
|  |                     { | ||||||
|  |                         Logger.Debug("Removing " + entry.Filepath); | ||||||
|  |                         var toRemove = db.FileCaches.First(f => f.Filepath == entry.Filepath && f.Hash == entry.Hash); | ||||||
|  |                         db.FileCaches.Remove(toRemove); | ||||||
|  |                     } | ||||||
|  |  | ||||||
|  |                     db.SaveChanges(); | ||||||
|  |                 } | ||||||
|  |             } | ||||||
|  |             catch (Exception ex) | ||||||
|  |             { | ||||||
|  |                 Logger.Warn(ex.Message); | ||||||
|  |             } | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         Task.WaitAll(dbTasks); | ||||||
|  |  | ||||||
|         Logger.Debug("Scanner validated existing db files"); |         Logger.Debug("Scanner validated existing db files"); | ||||||
|  |  | ||||||
|   | |||||||
| @@ -3,7 +3,7 @@ | |||||||
|   <PropertyGroup> |   <PropertyGroup> | ||||||
|     <Authors></Authors> |     <Authors></Authors> | ||||||
|     <Company></Company> |     <Company></Company> | ||||||
|     <Version>0.4.15</Version> |     <Version>0.4.16</Version> | ||||||
|     <Description></Description> |     <Description></Description> | ||||||
|     <Copyright></Copyright> |     <Copyright></Copyright> | ||||||
|     <PackageProjectUrl>https://github.com/Penumbra-Sync/client</PackageProjectUrl> |     <PackageProjectUrl>https://github.com/Penumbra-Sync/client</PackageProjectUrl> | ||||||
|   | |||||||
| @@ -152,7 +152,7 @@ namespace MareSynchronos.UI | |||||||
|                 ImGui.Text("Scan is running"); |                 ImGui.Text("Scan is running"); | ||||||
|                 ImGui.Text("Current Progress:"); |                 ImGui.Text("Current Progress:"); | ||||||
|                 ImGui.SameLine(); |                 ImGui.SameLine(); | ||||||
|                 ImGui.Text(_cacheScanner.TotalFiles <= 1 |                 ImGui.Text(_cacheScanner.TotalFiles == 1 | ||||||
|                     ? "Collecting files" |                     ? "Collecting files" | ||||||
|                     : $"Processing {_cacheScanner.CurrentFileProgress} / {_cacheScanner.TotalFiles} files"); |                     : $"Processing {_cacheScanner.CurrentFileProgress} / {_cacheScanner.TotalFiles} files"); | ||||||
|             } |             } | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user
	 Stanley Dimant
					Stanley Dimant