void FswRename(SyncJob syncJob, RenamedEventArgs e)
        {
            lock (syncOpLock) {
                bool isA = e.FullPath.StartsWith(syncJob.PathA);

                string oldKey = Path.GetRelativePath(isA ? syncJob.PathA : syncJob.PathB, e.OldFullPath);
                string key    = Path.GetRelativePath(isA ? syncJob.PathA : syncJob.PathB, e.FullPath);

                var oldItemStatus = syncJob.StatusLines.Where(sl => sl.Key == oldKey).SingleOrDefault();
                var itemStatus    = syncJob.StatusLines.Where(sl => sl.Key == key).SingleOrDefault();

                if (isA)
                {
                    File.Move(Path.Join(syncJob.PathB, oldKey), Path.Join(syncJob.PathB, key));
                }
                else
                {
                    File.Move(Path.Join(syncJob.PathA, oldKey), Path.Join(syncJob.PathA, key));
                }

                syncJob.StatusLines.Remove(oldItemStatus);
                syncJob.StatusLines.Add(new FileStatusLine {
                    Key = key, LastModified = new FileInfo(e.FullPath).LastWriteTimeUtc
                });
            }
        }
예제 #2
0
        static void NewSyncJob(NewOptions options)
        {
            if (!Directory.Exists(options.PathA))
            {
                throw new ArgumentException($"PathA ({options.PathA}) does not exist");
            }
            if (!Directory.Exists(options.PathB))
            {
                throw new ArgumentException($"PathB ({options.PathB}) does not exist");
            }

            SyncJob syncJob, oldSyncJob;

            if (File.Exists(options.SyncJobFile))
            {
                oldSyncJob = SyncJob.Load(options.SyncJobFile);
                syncJob    = new SyncJob(options.PathA, options.PathB, oldSyncJob.StatusLines, options.LogDirectory, oldSyncJob.CurrentPid, options.LogFileLimit);
            }
            else
            {
                syncJob = new SyncJob(options.PathA, options.PathB, options.LogDirectory, options.LogFileLimit);
            }

            syncJob.Save(options.SyncJobFile);
        }
        FileSystemWatcher CreateFsWatcher(SyncJob syncJob, string syncJobFile, string path)
        {
            var fsw = new FileSystemWatcher(path);

            fsw.IncludeSubdirectories = true;
            fsw.EnableRaisingEvents   = true;
            fsw.Changed += (sender, e) => FswUpdate(syncJob, syncJobFile, e);
            fsw.Created += (sender, e) => FswUpdate(syncJob, syncJobFile, e);
            fsw.Deleted += (sender, e) => FswUpdate(syncJob, syncJobFile, e);
            fsw.Renamed += (sender, e) => FswRename(syncJob, e);
            fsw.Error   += (sender, e) => logger.LogError(e.GetException(), "Error receiving file system events");
            return(fsw);
        }
        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");
                }
            }
        }
 public void Monitor(SyncJob syncJob, string syncJobFile)
 {
     var fswA = CreateFsWatcher(syncJob, syncJobFile, syncJob.PathA);
     var fswB = CreateFsWatcher(syncJob, syncJobFile, syncJob.PathB);
 }
예제 #6
0
        public void Sync(SyncJob job, IList <SyncOperation <FileStatusLine> > ops)
        {
            foreach (var op in ops)
            {
                if (op?.Item == null)
                {
                    logger.LogInformation("Nothing to do");
                    continue;
                }

                string fileA = Path.Join(job.PathA, op.Item.Key);
                string fileB = Path.Join(job.PathB, op.Item.Key);

                switch (op.ItemOperation)
                {
                case ItemOperation.CopyToA:
                    logger.LogInformation($"Copy B -> A: {op.Item.Key}");
                    Directory.CreateDirectory(Path.GetDirectoryName(fileA));
                    Copy(fileB, fileA);
                    break;

                case ItemOperation.CopyToB:
                    logger.LogInformation($"Copy A -> B: {op.Item.Key}");
                    Directory.CreateDirectory(Path.GetDirectoryName(fileB));
                    Copy(fileA, fileB);
                    break;

                case ItemOperation.DeleteFromA:
                    logger.LogInformation($"Delete From A: {op.Item.Key}");
                    Delete(fileA);
                    break;

                case ItemOperation.DeleteFromB:
                    logger.LogInformation($"Delete From B: {op.Item.Key}");
                    Delete(fileB);
                    break;
                }

                var            matchedLines = job.StatusLines.Where(sl => sl.Key == op.Item.Key);
                FileStatusLine statusLine;
                switch (op.StatusOperation)
                {
                case StatusOperation.AddToStatus:
                    statusLine = matchedLines.SingleOrDefault();
                    if (statusLine != null)
                    {
                        throw new DuplicateNameException($"The key already exists in the sync job status. {op.Item.Key}");
                    }
                    job.StatusLines.Add(new FileStatusLine {
                        Key = op.Item.Key, LastModified = op.Item.Item.LastModified
                    });
                    break;

                case StatusOperation.UpdateStatus:
                    statusLine = matchedLines.Single();
                    statusLine.LastModified = op.Item.Item.LastModified;
                    break;

                case StatusOperation.DeleteFromStatus:
                    statusLine = matchedLines.Single();
                    job.StatusLines.Remove(statusLine);
                    break;
                }
            }
        }
예제 #7
0
        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);
            }
        }