Beispiel #1
0
        public SyncLogEntry(FileEntryType entryType, string decrFileName, DateTime decrModified, string encrFileName, DateTime encrModified)
        {
            if (string.IsNullOrWhiteSpace(decrFileName))
            {
                throw new ArgumentNullException(nameof(decrFileName));
            }
            if (string.IsNullOrWhiteSpace(encrFileName))
            {
                throw new ArgumentNullException(nameof(encrFileName));
            }
            if (!HelixUtil.IsValidPath(decrFileName))
            {
                throw new ArgumentOutOfRangeException(nameof(decrFileName), "Invalid Path '" + decrFileName + "'");
            }
            if (!HelixUtil.IsValidPath(encrFileName))
            {
                throw new ArgumentOutOfRangeException(nameof(encrFileName), "Invalid Path '" + encrFileName + "'");
            }

            this.EntryType = entryType;

            this.DecrFileName = HelixUtil.PathUniversal(decrFileName);

            if (entryType != FileEntryType.File)
            {
                this.DecrModified = DateTime.MinValue;
            }
            else
            {
                this.DecrModified = HelixUtil.TruncateTicks(decrModified);
            }

            this.EncrFileName = HelixUtil.PathUniversal(encrFileName);
            this.EncrModified = HelixUtil.TruncateTicks(encrModified);
        }
Beispiel #2
0
        public static FileEntry FromFile(string fullPath, string root)
        {
            root     = HelixUtil.PathNative(root);
            fullPath = HelixUtil.PathNative(fullPath);

            string    casedFullPath = HelixUtil.GetExactPathName(fullPath);
            FileEntry fileInfo      = new FileEntry
            {
                FileName = HelixUtil.PathUniversal(HelixUtil.RemoveRootFromPath(fullPath, root))
            };

            var fi = new FileInfo(casedFullPath);

            //Check to ensure ends with to address case changes
            if (fi.Exists && fi.FullName.EndsWith(fullPath, StringComparison.Ordinal))
            {
                fileInfo.LastWriteTimeUtc = HelixUtil.TruncateTicks(fi.LastWriteTimeUtc);
                fileInfo.Length           = fi.Length;
                fileInfo.EntryType        = FileEntryType.File;
                return(fileInfo);
            }

            var di = new DirectoryInfo(casedFullPath);

            if (di.Exists && di.FullName.EndsWith(fullPath, StringComparison.Ordinal))
            {
                fileInfo.LastWriteTimeUtc = DateTime.MinValue;
                fileInfo.EntryType        = FileEntryType.Directory;
                return(fileInfo);
            }

            fileInfo.LastWriteTimeUtc = DateTime.MinValue;
            fileInfo.EntryType        = FileEntryType.Removed;
            return(fileInfo);
        }
Beispiel #3
0
 public override string ToString()
 {
     return(string.Format("{0} {1} {2} {3} {4}",
                          EntryTypeFlag,
                          (DecrModified == DateTime.MinValue ? dateZero : DecrModified.ToString(dateFormat)),
                          HelixUtil.Quote(DecrFileName),
                          (EncrModified == DateTime.MinValue ? dateZero : EncrModified.ToString(dateFormat)),
                          HelixUtil.Quote(EncrFileName)));
 }
Beispiel #4
0
 private void EncrWatcher_Changed(object sender, FileSystemEventArgs e)
 {
     if (e.ChangeType == WatcherChangeTypes.Deleted)
     {
         Enqueue(new HelixSync.DirectoryChange(PairSide.Encrypted, HelixUtil.RemoveRootFromPath(e.FullPath, DecrDirectory)));
     }
     else if (e.ChangeType == WatcherChangeTypes.Created ||
              e.ChangeType == WatcherChangeTypes.Changed)
     {
         Enqueue(new HelixSync.DirectoryChange(PairSide.Encrypted, HelixUtil.RemoveRootFromPath(e.FullPath, DecrDirectory)));
     }
     else if (e.ChangeType == WatcherChangeTypes.Renamed)
     {
         Enqueue(new HelixSync.DirectoryChange(PairSide.Encrypted, HelixUtil.RemoveRootFromPath((e as RenamedEventArgs).OldFullPath, DecrDirectory)));
         Enqueue(new HelixSync.DirectoryChange(PairSide.Encrypted, HelixUtil.RemoveRootFromPath(e.FullPath, DecrDirectory)));
     }
 }
Beispiel #5
0
        public static SyncLogEntry TryParseFromString(string line)
        {
            var match = lineParse.Match(line);

            if (!match.Success)
            {
                return(null);
            }

            var typeFlag = match.Groups["Type"].Value;

            DateTime decrModified;

            if (match.Groups["DecrModified"].Value == dateZero)
            {
                decrModified = DateTime.MinValue;
            }
            else if (!DateTime.TryParseExact(match.Groups["DecrModified"].Value, dateFormat, CultureInfo.InvariantCulture, DateTimeStyles.AssumeUniversal | DateTimeStyles.AdjustToUniversal, out decrModified))
            {
                return(null);
            }

            string decrFileName = HelixUtil.Unquote(match.Groups["DecrFileName"].Value);

            DateTime encrModified;

            if (match.Groups["EncrModified"].Value == dateZero)
            {
                encrModified = DateTime.MinValue;
            }
            else if (!DateTime.TryParseExact(match.Groups["EncrModified"].Value, dateFormat, CultureInfo.InvariantCulture, DateTimeStyles.AssumeUniversal | DateTimeStyles.AdjustToUniversal, out encrModified))
            {
                return(null);
            }

            string encrFileName = HelixUtil.Unquote(match.Groups["EncrFileName"].Value);

            return(new SyncLogEntry
            {
                DecrFileName = HelixUtil.PathUniversal(decrFileName),
                DecrModified = HelixUtil.TruncateTicks(decrModified),
                EncrFileName = HelixUtil.PathUniversal(encrFileName),
                EncrModified = HelixUtil.TruncateTicks(encrModified),
                EntryTypeFlag = typeFlag,
            });
        }
Beispiel #6
0
        /// <summary>
        /// Loads the EncrHeaders for new and updated encripted files
        /// </summary>
        private void FindChanges_St05_RefreshEncrHeaders(ConsoleEx console, List <ChangeBuilder> matchesA)
        {
            int statsRefreshHeaderCount = 0;

            foreach (ChangeBuilder preSyncDetails in matchesA.Where(m => m.EncrInfo != null && m.EncrInfo.LastWriteTimeUtc != m.LogEntry?.EncrModified))
            {
                string encrFullPath = Path.Combine(EncrDirectory.FullName, HelixUtil.PathNative(preSyncDetails.EncrFileName));
                if (File.Exists(encrFullPath))
                {
                    preSyncDetails.EncrHeader = HelixFile.DecryptHeader(encrFullPath, this.DerivedBytesProvider);

                    //Updates the DecrFileName (if necessary)
                    if (string.IsNullOrEmpty(preSyncDetails.DecrFileName) &&
                        FileNameEncoder.EncodeName(preSyncDetails.EncrHeader.FileName) == preSyncDetails.EncrInfo.RelativePath)
                    {
                        preSyncDetails.DecrFileName = preSyncDetails.EncrHeader.FileName;
                    }
                }

                statsRefreshHeaderCount++;
            }
            console?.WriteLine(VerbosityLevel.Diagnostic, 2, $"Updated {statsRefreshHeaderCount} headers");
        }
Beispiel #7
0
        public SyncResults TrySync(PreSyncDetails entry, ConsoleEx console = null)
        {
            const int encrTimespanPrecisionMS = 1000;

            //todo: ensure the entry direction and operation end up being the same
            //todo: ensure all the calling methods do something with the results

            //if (WhatIf)
            //    throw new InvalidOperationException("Unable to perform sync when WhatIf mode is set to true");
            if (entry == null)
            {
                throw new ArgumentNullException(nameof(entry));
            }

            if (entry.SyncMode == PreSyncMode.DecryptedSide)
            {
                SyncLogEntry logEntry = entry.LogEntry;


                string             encrPath = Path.Combine(EncrDirectory.FullName, HelixUtil.PathNative(entry.EncrFileName));
                string             decrPath = Path.Combine(DecrDirectory.FullName, HelixUtil.PathNative(entry.DecrFileName));
                FileEncryptOptions options  = new FileEncryptOptions();
                FileEntry          header   = null;
                options.BeforeWriteHeader = (h) => header = h;
                options.StoredFileName    = entry.DecrFileName;
                options.FileVersion       = Header.FileVersion;
                options.Log = (s) => console?.WriteLine(VerbosityLevel.Diagnostic, 1, s);

                if (WhatIf)
                {
                    EncrDirectory.WhatIfReplaceFile(encrPath, entry.DisplayFileLength);
                    if (entry.DecrInfo == null)
                    {
                        header = new FileEntry()
                        {
                            EntryType = FileEntryType.Removed,
                            FileName  = entry.DecrFileName,
                        };
                    }
                    else
                    {
                        header = entry.DecrInfo.ToFileEntry();
                    }
                }
                else
                {
                    HelixFile.Encrypt(decrPath, encrPath, DerivedBytesProvider, options);


                    //forces a change if the file was modified to quickly
                    if (logEntry != null && (File.GetLastWriteTimeUtc(encrPath) - logEntry.EncrModified).TotalMilliseconds < encrTimespanPrecisionMS)
                    {
                        File.SetLastWriteTimeUtc(encrPath, logEntry.EncrModified + TimeSpan.FromMilliseconds(encrTimespanPrecisionMS));
                    }

                    EncrDirectory.RefreshEntry(encrPath);
                }
                var newLogEntry = CreateEntryFromHeader(header, EncrDirectory.TryGetEntry(entry.EncrFileName));
                SyncLog.Add(newLogEntry);


                return(SyncResults.Success());
            }
            else if (entry.SyncMode == PreSyncMode.EncryptedSide)
            {
                if (entry.DisplayOperation == PreSyncOperation.Purge)
                {
                    SyncLog.Add(entry.GetUpdatedLogEntry());
                    return(SyncResults.Success());
                }
                else
                {
                    //todo: use the DisplayOperation to determine what to do (ensures the counts stay consistant)

                    SyncLogEntry logEntry        = entry.LogEntry;
                    SyncLogEntry fileSystemEntry = CreateNewLogEntryFromEncrPath(entry.EncrFileName);
                    if (logEntry?.ToString() == fileSystemEntry?.ToString())
                    {
                        return(SyncResults.Success()); //Unchanged
                    }
                    //todo: test to see if there are illegal characters
                    //todo: check if the name matches

                    string encrPath = HelixUtil.JoinNative(EncrDirectory.FullName, fileSystemEntry.EncrFileName);
                    string decrPath = HelixUtil.JoinNative(DecrDirectory.FullName, fileSystemEntry.DecrFileName);

                    //todo: if file exists with different case - skip file
                    var     exactPath = HelixUtil.GetExactPathName(decrPath);
                    FSEntry decrEntry = DecrDirectory.TryGetEntry(fileSystemEntry.DecrFileName);

                    if (decrEntry != null && decrEntry.RelativePath != fileSystemEntry.DecrFileName)
                    {
                        //todo: throw more specific exception
                        return(SyncResults.Failure(new HelixException($"Case only conflict file \"{decrPath}\" exists as \"{exactPath}\".")));
                    }

                    if (WhatIf)
                    {
                        if (entry.EncrHeader.EntryType == FileEntryType.File)
                        {
                            DecrDirectory.WhatIfReplaceFile(decrPath, entry.EncrHeader.Length, entry.EncrHeader.LastWriteTimeUtc);
                        }
                        else if (entry.EncrHeader.EntryType == FileEntryType.Removed)
                        {
                            DecrDirectory.WhatIfDeleteFile(decrPath);
                        }
                        else if (entry.EncrHeader.EntryType == FileEntryType.Directory)
                        {
                            DecrDirectory.WhatIfAddDirectory(decrPath);
                        }
                        else
                        {
                            throw new NotSupportedException();
                        }
                    }
                    else
                    {
                        HelixFile.Decrypt(encrPath, decrPath, DerivedBytesProvider);
                        //todo: get the date on the file system (needed if the filesystem has less precision
                        DecrDirectory.RefreshEntry(decrPath);
                    }

                    SyncLog.Add(fileSystemEntry);

                    return(SyncResults.Success());
                }
            }
            else if (entry.SyncMode == PreSyncMode.Match)
            {
                //Add to Log file (changed to be equal on both sides)
                SyncLogEntry fileSystemEntry = CreateNewLogEntryFromDecrPath(entry.DecrFileName);
                SyncLog.Add(fileSystemEntry);
                return(SyncResults.Success());
            }
            else if (entry.SyncMode == PreSyncMode.Unchanged)
            {
                //do nothing
                return(SyncResults.Success());
            }

            return(SyncResults.Failure(new HelixException($"Invalid sync mode {entry.SyncMode}")));
        }
Beispiel #8
0
        public override string ToString()
        {
            StringBuilder builder = new StringBuilder();

            if (DisplayEntryType == FileEntryType.Directory)
            {
                builder.Append("<DIR> ");
            }
            else if (DisplayEntryType == FileEntryType.Purged)
            {
                builder.Append("<PUR> ");
            }
            else
            {
                builder.Append(HelixUtil.FormatBytes5(DisplayFileLength) + " "); //ex 1.5KB
            }
            if (DisplayOperation == PreSyncOperation.Add)
            {
                builder.Append("+ ");
            }
            else if (DisplayOperation == PreSyncOperation.Remove)
            {
                builder.Append("- ");
            }
            else if (DisplayOperation == PreSyncOperation.Change)
            {
                builder.Append("c ");
            }
            else if (DisplayOperation == PreSyncOperation.Purge)
            {
                builder.Append("~ ");
            }
            else if (DisplayOperation == PreSyncOperation.Error)
            {
                builder.Append("! ");
            }
            else
            {
                builder.Append("  ");
            }

            if (SyncMode == PreSyncMode.Match || SyncMode == PreSyncMode.Unchanged)
            {
                builder.Append("         ");
            }
            else if (SyncMode == PreSyncMode.DecryptedSide)
            {
                builder.Append("DEC=>ENC ");
            }
            else if (SyncMode == PreSyncMode.EncryptedSide)
            {
                builder.Append("ENC=>DEC ");
            }
            else if (SyncMode == PreSyncMode.Conflict)
            {
                builder.Append("CONFLICT ");
            }
            else //(SyncMode == PreSyncMode.Unknown)
            {
                builder.Append("UNKNOWN  ");
            }

            builder.Append((EncrFileName ?? "------").Substring(0, 6) + "... ");

            builder.Append(DecrFileName ?? "");
            return(builder.ToString());
        }
Beispiel #9
0
        public static SyncSummary Sync(SyncOptions options, ConsoleEx consoleEx = null, HelixFileVersion fileVersion = null)
        {
            var sum = new SyncSummary();

            consoleEx ??= new ConsoleEx();
            consoleEx.Verbosity = options.Verbosity;

            consoleEx.WriteLine("Sync");
            if (options.WhatIf)
            {
                consoleEx.WriteLine("..Options: WhatIf");
            }
            consoleEx.WriteLine($"..DecrDir: {options.DecrDirectory}");
            consoleEx.WriteLine($"..EncrDir: {options.EncrDirectory}");
            //consoleEx.WriteLine($"..Direction: {options.Direction}");
            consoleEx.WriteLine($"..Verbosity: {options.Verbosity}");

            consoleEx.WriteLine();

            if (options.WhatIf)
            {
                consoleEx.WriteLine("** WhatIf Mode - No Changes Made **");
                consoleEx.WriteLine("");
            }


            DerivedBytesProvider derivedBytesProvider = DerivedBytesProvider.FromPassword(options.Password, options.KeyFile);

            using DirectoryPair pair = new DirectoryPair(options.DecrDirectory, options.EncrDirectory, derivedBytesProvider, options.WhatIf);
            pair.PreInitializationCheck();

            if (pair.InitializeFullNeeded())
            {
                if (options.Initialize)
                {
                    //continue, unprompted
                    if (pair.InitializeMergeWarning())
                    {
                        consoleEx.WriteLine("WARNING: Decrypted directory is not empty and will be merged");
                    }
                }
                else
                {
                    consoleEx.WriteLine("Directories require initialization...");

                    if (!consoleEx.PromptBool("Initialized encrypted and decrypted directories now? [y/N] ", false))
                    {
                        consoleEx.WriteErrorLine("Operation cancelled");
                        sum.Error = new InitializationCanceledException();
                        return(sum);
                    }

                    if (pair.InitializeMergeWarning())
                    {
                        if (!consoleEx.PromptBool("Decrypted directory is not empty and will be merged, continue? [y/N] ", false))
                        {
                            consoleEx.WriteErrorLine("Operation cancelled");
                            sum.Error = new InitializationCanceledException();
                            return(sum);
                        }
                    }
                }

                pair.InitializeFull(consoleEx);
            }

            pair.OpenEncr(consoleEx);

            if (pair.InitializeDecrNeeded())
            {
                if (options.Initialize)
                {
                    //continue, unprompted
                    if (pair.InitializeMergeWarning())
                    {
                        consoleEx.WriteLine("WARNING: Decrypted directory is not empty and will be merged");
                    }
                }
                else
                {
                    consoleEx.WriteLine("Decrypted directory require initialization...");

                    if (!consoleEx.PromptBool("Initialized decrypted directories now? [y/N] ", false))
                    {
                        consoleEx.WriteErrorLine("Operation cancelled");
                        sum.Error = new InitializationCanceledException();
                        return(sum);
                    }

                    if (pair.InitializeMergeWarning())
                    {
                        if (!consoleEx.PromptBool("Decrypted directory is not empty and will be merged, continue? [y/N] ", false))
                        {
                            consoleEx.WriteErrorLine("Operation cancelled");
                            sum.Error = new InitializationCanceledException();
                            return(sum);
                        }
                    }
                }

                pair.InitializeDecr(consoleEx);
            }

            pair.OpenDecr(consoleEx);

            pair.Cleanup(consoleEx);

            List <PreSyncDetails> changes = pair.FindChanges(clearCache: false, console: consoleEx);

            if (changes.Count == 0)
            {
                consoleEx.WriteLine("--No Changes--");
            }



            var defaultConflictAction = "";

            consoleEx.WriteLine(VerbosityLevel.Normal, 0, "Performing Sync...");
            foreach (PreSyncDetails change in changes)
            {
                consoleEx.WriteLine(change);

                if (change.SyncMode == PreSyncMode.Conflict)
                {
                    var decrModified = change.DecrInfo == null ? (object)null : change.DecrInfo.LastWriteTimeUtc.ToLocalTime();
                    var decrSize     = change.DecrInfo == null ? (object)null : HelixUtil.FormatBytes5(change.DecrInfo.Length);
                    var encrModified = change.EncrInfo == null ? (object)null : change.EncrInfo.LastWriteTimeUtc.ToLocalTime();
                    var encrSize     = change.EncrHeader == null ? (object)null : HelixUtil.FormatBytes5(change.EncrHeader.Length);


                    string response;
                    if (defaultConflictAction != "")
                    {
                        response = defaultConflictAction;
                    }
                    else
                    {
                        consoleEx.WriteLine($"    Decrypted - Modified: {decrModified}, Size: {decrSize}");
                        consoleEx.WriteLine($"    Encrypted - Modified: {encrModified}, Size: {encrSize}");
                        consoleEx.WriteLine($"");
                        consoleEx.WriteLine($"    D - Decrypted, E - Encrypted, S - Skip, + Always perform this action"); //todo: support newer, support always
                        response = consoleEx.PromptChoice("    Select Option [D,E,S,D+,E+,S+]? ", new string[] { "D", "E", "S", "D+", "E+", "S+" }, "S");
                        if (response.EndsWith("+"))
                        {
                            response = response.Substring(0, 1);
                            defaultConflictAction = response;
                        }
                    }
                    if (response == "D")
                    {
                        change.SyncMode = PreSyncMode.DecryptedSide;
                    }
                    else if (response == "E")
                    {
                        change.SyncMode = PreSyncMode.EncryptedSide;
                    }

                    if (change.SyncMode != PreSyncMode.Conflict)
                    {
                        consoleEx.WriteLine(change);
                    }
                }

                if (change.SyncMode == PreSyncMode.EncryptedSide)
                {
                    //todo: make display operation be the actual operation
                    if (change.DisplayOperation == PreSyncOperation.Add)
                    {
                        sum.EncrAdd++;
                    }
                    else if (change.DisplayOperation == PreSyncOperation.Remove)
                    {
                        sum.EncrRemove++;
                    }
                    else if (change.DisplayOperation == PreSyncOperation.Change)
                    {
                        sum.EncrChange++;
                    }
                    else
                    {
                        sum.EncrOther++;
                    }
                }
                else if (change.SyncMode == PreSyncMode.DecryptedSide)
                {
                    if (change.DisplayOperation == PreSyncOperation.Add)
                    {
                        sum.DecrAdd++;
                    }
                    else if (change.DisplayOperation == PreSyncOperation.Remove)
                    {
                        sum.DecrRemove++;
                    }
                    else if (change.DisplayOperation == PreSyncOperation.Change)
                    {
                        sum.DecrChange++;
                    }
                    else
                    {
                        sum.DecrOther++;
                    }
                }
                else if (change.SyncMode == PreSyncMode.Conflict)
                {
                    sum.Conflict++;
                }


                var syncResult = pair.TrySync(change, consoleEx);
                //todo: add to error log
                if (syncResult.Exception != null)
                {
                    consoleEx.WriteErrorLine("..." + syncResult.Exception.Message);
                }
            }

            consoleEx.WriteLine("== Summary ==");
            consoleEx.WriteLine(sum);

            //todo: fix unchanged
            consoleEx.WriteLine();


            //consoleEx.WriteLine(VerbosityLevel.Diagnostic, 0, "");
            //consoleEx.WriteLine(VerbosityLevel.Diagnostic, 0, "==Decr Directory==");
            //foreach (var entry in decrDirectory.FSDirectory.GetEntries(SearchOption.AllDirectories))
            //{
            //    if (entry is FSDirectory dirEntry)
            //        consoleEx.WriteLine(VerbosityLevel.Diagnostic, 1, $"<dir> {entry.RelativePath}");
            //    else if (entry is FSFile fileEntry)
            //        consoleEx.WriteLine(VerbosityLevel.Diagnostic, 1, $"{HelixUtil.FormatBytes5(entry.Length)} {entry.RelativePath}");
            //}

            //consoleEx.WriteLine(VerbosityLevel.Diagnostic, 0, "");
            //consoleEx.WriteLine(VerbosityLevel.Diagnostic, 0, "==Encr Directory==");
            //foreach (var entry in encrDirectory.FSDirectory.GetEntries(SearchOption.AllDirectories))
            //{
            //    if (entry is FSDirectory dirEntry)
            //        consoleEx.WriteLine(VerbosityLevel.Diagnostic, 1, $"<dir> {entry.RelativePath}");
            //    else if (entry is FSFile fileEntry)
            //        consoleEx.WriteLine(VerbosityLevel.Diagnostic, 1, $"{HelixUtil.FormatBytes5(entry.Length)} {entry.RelativePath}");
            //}

            return(sum);
        }