void FswUpdate(SyncJob syncJob, string syncJobFile, FileSystemEventArgs e) { logger.LogInformation($"FileSystem Event: {e.ChangeType.ToString()} {e.FullPath}"); lock (syncOpLock) { bool isA = e.FullPath.StartsWith(syncJob.PathA); string key = Path.GetRelativePath(isA ? syncJob.PathA : syncJob.PathB, e.FullPath); string counterPath = Path.Join(isA ? syncJob.PathB : syncJob.PathA, key); SyncItem <FileStatusLine> itemEvent = null; if (e.ChangeType != WatcherChangeTypes.Deleted) { itemEvent = new SyncItem <FileStatusLine>(e.FullPath, key, new FileStatusLine { Key = key, LastModified = (new FileInfo(e.FullPath)).LastWriteTimeUtc }); } SyncItem <FileStatusLine> itemCounter = null; logger.LogDebug($"counterPath = {counterPath}"); if (File.Exists(counterPath)) { logger.LogDebug($"counterPath exists"); itemCounter = new SyncItem <FileStatusLine>(counterPath, key, new FileStatusLine { Key = key, LastModified = (new FileInfo(counterPath)).LastWriteTimeUtc }); } SyncItem <FileStatusLine> itemStatus = null; var statusLine = syncJob.StatusLines.Where(sl => sl.Key == key).SingleOrDefault(); if (statusLine != null) { itemStatus = new SyncItem <FileStatusLine>("", key, statusLine); } SyncItem <FileStatusLine> itemA, itemB; if (isA) { itemA = itemEvent; itemB = itemCounter; } else { itemA = itemCounter; itemB = itemEvent; } var op = syncEngine.GetOpForKey(itemA, itemB, itemStatus); var ops = new [] { op }; if (op != null && (op.ItemOperation != ItemOperation.None || op.StatusOperation != StatusOperation.None)) { logger.LogInformation("Operation to perform:"); logger.LogInformation(op.ToString()); } else { logger.LogDebug("No changes to make"); } try { syncer.Sync(syncJob, ops); syncJob.Save(syncJobFile); } catch (Exception ex) { logger.LogError(ex, "Error performing sync"); } } }
static void RunSyncJob(SyncOptions options) { var syncJob = SyncJob.Load(options.SyncJobFile); // only allow one instance running against any sync file if (syncJob.CurrentPid > 0 && Process.GetProcessesByName(Process.GetCurrentProcess().ProcessName).Select(p => p.Id).Contains(syncJob.CurrentPid)) { Console.WriteLine($"Unable to run sync on {options.SyncJobFile}. Sync already running in process {syncJob.CurrentPid}"); return; } syncJob.CurrentPid = Process.GetCurrentProcess().Id; syncJob.Save(options.SyncJobFile); try { var logger = new LoggerFactory() .AddSerilog(new LoggerConfiguration() .MinimumLevel.Is(options.Debug ? LogEventLevel.Debug : LogEventLevel.Information) .WriteTo.Console(restrictedToMinimumLevel: options.Quiet ? LogEventLevel.Warning : LogEventLevel.Information, outputTemplate: "{Message:lj}{NewLine}") .WriteTo.File(syncJob.LogPath, rollingInterval: Serilog.RollingInterval.Day, retainedFileCountLimit: syncJob.LogFileLimit) .CreateLogger()) .CreateLogger("sync_run"); logger.LogInformation(""); logger.LogInformation("------------------------------------------------"); logger.LogInformation($"START sync: {syncJob.PathA} <-> {syncJob.PathB}"); logger.LogInformation("------------------------------------------------"); logger.LogInformation(""); var syncEngine = new SyncEngine <FileStatusLine>(logger, FileMatcher); var fileSyncer = new FileSyncer(logger); var stopwatch = new Stopwatch(); stopwatch.Start(); var sourceFiles = new DirectoryParser(logger).Parse(syncJob.PathA) .Select(fi => CreateSyncItem(fi, syncJob.PathA)) .ToHashSet(); logger.LogInformation($"Parsing PathA ({syncJob.PathA}) took {stopwatch.Elapsed.TotalSeconds} seconds"); stopwatch.Restart(); var destFiles = new DirectoryParser(logger).Parse(syncJob.PathB) .Select(fi => CreateSyncItem(fi, syncJob.PathB)) .ToHashSet(); logger.LogInformation($"Parsing PathB ({syncJob.PathB}) took {stopwatch.Elapsed.TotalSeconds} seconds"); stopwatch.Restart(); var statusLines = syncJob.StatusLines .Select(sl => new SyncItem <FileStatusLine>("", sl.Key, sl)) .ToHashSet(); logger.LogInformation($"Parsing Job Status took {stopwatch.Elapsed.TotalSeconds} seconds"); Console.WriteLine(new string(' ', Console.WindowWidth)); stopwatch.Restart(); var changeset = syncEngine.GetChangeSet(sourceFiles, destFiles, statusLines); logger.LogInformation($"Calculating changeset took {stopwatch.Elapsed.TotalSeconds} seconds"); PrintOperations(); if (!GetUserConfirmation()) { return; } stopwatch.Restart(); fileSyncer.Sync(syncJob, changeset); logger.LogInformation($"File sync took {stopwatch.Elapsed.TotalSeconds} seconds"); syncJob.Save(options.SyncJobFile); if (options.Realtime) { logger.LogInformation(""); logger.LogInformation("Entering realtime file system monitoring..."); var fileMonitor = new RealtimeFileMonitor(logger, syncEngine, fileSyncer); fileMonitor.Monitor(syncJob, options.SyncJobFile); while (true) { System.Threading.Thread.Sleep(1); } } SyncItem <FileStatusLine> CreateSyncItem(FileInfo fileInfo, string basePath) { string key = Path.GetRelativePath(basePath, fileInfo.FullName); var syncLine = new FileStatusLine { Key = key, LastModified = fileInfo.LastWriteTimeUtc }; return(new SyncItem <FileStatusLine>(fileInfo.FullName, key, syncLine)); } SyncItem <FileStatusLine> FileMatcher(SyncItem <FileStatusLine> a, SyncItem <FileStatusLine> b) { logger.LogDebug($"Conflict Resolution: A = {a.Item.LastModified.ToString()}, B = {b.Item.LastModified.ToString()}"); return(a.Item.LastModified > b.Item.LastModified ? a : a.Item.LastModified < b.Item.LastModified ? b : null); } void PrintOperations() { if (changeset.Count() > 0) { Log("Operations to perform:"); int maxKeyLen = changeset.Select(s => s.Item.Key.Length).Max() + 2; foreach (var op in changeset) { if (op.ItemOperation == ItemOperation.None && op.StatusOperation == StatusOperation.None) { continue; } Log(op.ToString(maxKeyLen)); } } else { Log("\tDirectories are in-sync"); } void Log(string message) { logger.LogInformation(message); } } bool GetUserConfirmation() { if (!options.Force) { logger.LogInformation(""); logger.LogInformation("Confirm? (yes/NO): "); string confirm = Console.ReadLine(); if (confirm.ToLower() != "yes") { logger.LogDebug("User cancelled sync"); return(false); } logger.LogDebug("Sync confirmed"); } else { logger.LogDebug("Force option set. Confirmation skipped"); } return(true); } } finally { syncJob.CurrentPid = 0; syncJob.Save(options.SyncJobFile); } }