public void InitializeFull(ConsoleEx consoleEx, HelixFileVersion fileVersion = null) { PreInitializationCheck(); //Initialize Encr Directory consoleEx?.WriteLine(VerbosityLevel.Detailed, 0, "Initializing Encrypted Directory..."); Header = DirectoryHeader.New(); EncrDirectory.Create(); if (WhatIf) { EncrDirectory.WhatIfAddFile(HelixConsts.HeaderFileName, 10); } else { Header.Save(EncrDirectory.PathFull(HelixConsts.HeaderFileName), DerivedBytesProvider, fileVersion); EncrDirectory.RefreshEntry(HelixConsts.HeaderFileName); } this.FileNameEncoder = new FileNameEncoder(Header.FileNameKey); consoleEx?.WriteLine(VerbosityLevel.Detailed, 1, "Encrypted Directory Initialized (" + Header.DirectoryId.Substring(0, 6) + "...)"); InitializeDecr(consoleEx); }
public void OpenEncr(ConsoleEx consoleEx) { if (Header == null) { consoleEx?.WriteLine(VerbosityLevel.Detailed, 0, "Opening Encrypted Directory..."); Header = DirectoryHeader.Load(EncrDirectory.PathFull(HelixConsts.HeaderFileName), DerivedBytesProvider); consoleEx?.WriteLine(VerbosityLevel.Detailed, 0, "Opened Encrypted Directory (" + Header.DirectoryId.Substring(0, 6) + "...)"); } this.FileNameEncoder = new FileNameEncoder(Header.FileNameKey); }
public void InitializeDecr(ConsoleEx consoleEx) { consoleEx?.WriteLine(VerbosityLevel.Detailed, 0, "Initializing Decrypted Directory..."); //Initialize Decr Directory DecrDirectory.Create(); DecrDirectory.CreateDirectory(HelixConsts.SyncLogDirectory); if (WhatIf) { DecrDirectory.WhatIfAddFile(SyncLogPath, 10); } else { using var stream = File.CreateText(DecrDirectory.PathFull(SyncLogPath)); stream.WriteLine(HelixConsts.SyncLogHeader); } consoleEx?.WriteLine(VerbosityLevel.Detailed, 1, "Decrypted Directory Initialized"); }
/// <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"); }
/// <summary> /// Matches files from Encr, Decr and Log Entry /// </summary> private List <ChangeBuilder> FindChanges_St04_ThreeWayJoin(List <FSEntry> encrDirectoryFiles, List <FSEntry> decrDirectoryFiles, SyncLog syncLog, ConsoleEx console) { List <ChangeBuilder> preSyncDetails = new List <ChangeBuilder>(); //Adds Logs console?.WriteLine(VerbosityLevel.Diagnostic, 2, "Merging in log..."); preSyncDetails.AddRange(syncLog.Select(entry => new ChangeBuilder { LogEntry = entry })); console?.WriteLine(VerbosityLevel.Diagnostic, 3, $"{preSyncDetails.Count} added"); //Updates/Adds Decrypted File Information console?.WriteLine(VerbosityLevel.Diagnostic, 2, "Merging in decrypted information..."); int decrStatAdd = 0; int decrStatMerge = 0; var decrJoin = decrDirectoryFiles .GroupJoin(preSyncDetails, o => o.RelativePath, i => i?.LogEntry?.DecrFileName, (o, i) => new Tuple <FSEntry, ChangeBuilder>(o, i.FirstOrDefault())); foreach (var entry in decrJoin.ToList()) { if (entry.Item2 == null) { //New Entry (not in log) preSyncDetails.Add(new ChangeBuilder { DecrInfo = entry.Item1 }); decrStatAdd++; } else { //Existing Entry (update only) entry.Item2.DecrInfo = entry.Item1; if (entry.Item1 != null) { decrStatMerge++; } } } console?.WriteLine(VerbosityLevel.Diagnostic, 3, $"{decrStatAdd} added, {decrStatMerge} merged"); //find encrypted file names foreach (var entry in preSyncDetails) { entry.DecrFileName = entry.LogEntry?.DecrFileName ?? entry.DecrInfo.RelativePath; entry.EncrFileName = FileNameEncoder.EncodeName(entry.DecrFileName); } //Updates/adds encrypted File Information console?.WriteLine(VerbosityLevel.Diagnostic, 2, "Merging in encrypted information..."); int encrStatAdd = 0; int encrStatMerge = 0; var encrJoin = encrDirectoryFiles .GroupJoin(preSyncDetails, o => o.RelativePath, i => i.EncrFileName, (o, i) => new Tuple <FSEntry, ChangeBuilder>(o, i.FirstOrDefault())); foreach (var entry in encrJoin.ToList()) { if (entry.Item2 == null) { //New Entry (not in log or decrypted file) preSyncDetails.Add(new ChangeBuilder { EncrInfo = entry.Item1, EncrFileName = entry.Item1.RelativePath }); encrStatAdd++; } else { //Existing Entry entry.Item2.EncrInfo = entry.Item1; if (entry.Item1 != null) { encrStatMerge++; } } } console?.WriteLine(VerbosityLevel.Diagnostic, 3, $"{encrStatAdd} added, {encrStatMerge} merged"); return(preSyncDetails); }
/// <summary> /// Returns a list of changes that need to be performed as part of the sync. /// </summary> public List <PreSyncDetails> FindChanges(bool clearCache = true, ConsoleEx console = null) { //todo: disable default for reset console?.WriteLine(VerbosityLevel.Diagnostic, 0, "Finding Changes..."); if (clearCache) { ClearCache(); } using var rng = RandomNumberGenerator.Create(); console?.WriteLine(VerbosityLevel.Diagnostic, 1, "Enumerating Encr Directory..."); List <FSEntry> encrDirectoryFiles = EncrDirectory.GetEntries(SearchOption.AllDirectories).Where(EncrFilter).ToList(); console?.WriteLine(VerbosityLevel.Diagnostic, 2, $"{encrDirectoryFiles.Count} files found"); console?.WriteLine(VerbosityLevel.Diagnostic, 1, $"Enumerating Decr Directory..."); console?.WriteLine(VerbosityLevel.Diagnostic, 2, $"Path: {DecrDirectory.FullName}"); List <FSEntry> decrDirectoryFiles = DecrDirectory.GetEntries(SearchOption.AllDirectories).Where(DecrFilter).ToList(); console?.WriteLine(VerbosityLevel.Diagnostic, 2, $"{decrDirectoryFiles.Count} files found"); console?.WriteLine(VerbosityLevel.Diagnostic, 1, "Reading sync log (decr side)..."); console?.WriteLine(VerbosityLevel.Diagnostic, 2, $"{SyncLog.Count()} entries found"); //todo: filter out log entries where the decr file name and the encr file name does not match console?.WriteLine(VerbosityLevel.Diagnostic, 1, "Performing 3 way join..."); List <ChangeBuilder> changes = FindChanges_St04_ThreeWayJoin(encrDirectoryFiles, decrDirectoryFiles, SyncLog, console).ToList(); console?.WriteLine(VerbosityLevel.Diagnostic, 1, "Refreshing EncrHeaders..."); FindChanges_St05_RefreshEncrHeaders(console, changes); //todo: detect conflicts console?.WriteLine(VerbosityLevel.Diagnostic, 1, "Adding Relationships..."); FindChanges_St06_AddRelationships(changes); #if DEBUG Debug.Assert(changes.All(c => c.RelationParents != null)); Debug.Assert(changes.All(c => c.RelationChildren != null)); Debug.Assert(changes.All(c => c.RelationCaseDifference != null)); #endif console?.WriteLine(VerbosityLevel.Diagnostic, 1, "Calculating Operation..."); FindChanges_St07_CalculateOperation(changes); console?.WriteLine(VerbosityLevel.Diagnostic, 1, "Calculating Sync Mode..."); FindChanges_St08_CalculateSyncMode(changes); console?.WriteLine(VerbosityLevel.Diagnostic, 1, "Calculating Dependencies..."); FindChanges_St09_CalculateDependencies(changes); console?.WriteLine(VerbosityLevel.Diagnostic, 1, "Sorting..."); changes = FindChanges_St10_Sort(rng, changes); return(changes .Where(m => m.SyncMode != PreSyncMode.Unchanged) .Select(m => m.ToSyncEntry()) .ToList()); }
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}"))); }
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); }
/// <summary> /// Displays a preview of the content (to the console), automatically determining the best viewer. /// </summary> /// <param name="stream">The stream for which to load the preview data</param> /// <param name="type">(option) used to override the automatic detected viewer</param> /// <param name="targetLength">(option) used to override the default length of preview</param> private static void ContentPreview(Stream stream, ConsoleEx consoleEx, string type = "auto", int targetLength = -1) { if (stream == null) { throw new ArgumentNullException(nameof(stream)); } if (!stream.CanRead) { throw new ArgumentException("steam must be readable", nameof(stream)); } const int defaultLength = 512; const int defaultBinaryLength = 256; int offset = 0; byte[] content = new byte[targetLength <= 0 ? defaultLength : targetLength]; int length = stream.Read(content, 0, content.Length); string display; if (string.IsNullOrEmpty(type) || type == "auto") { type = DetectType(content, offset, length); } //Reduces size by two for binary due to the fact that it takes a lot of screen realestate if (type == "binary" && targetLength < 0 && length > defaultBinaryLength) { length = defaultBinaryLength; } bool partial = length < stream.Length; if (length < stream.Length) { display = "== Content Preview (" + type + " " + FormatSizeBytes(length) + " of " + FormatSizeBytes(stream.Length) + ") ==" + Environment.NewLine; } else { display = "== Content Preview (" + type + ", size: " + FormatSizeBytes(stream.Length) + ") ==" + Environment.NewLine; } if (type == "text") { display += TextDisplay(content, offset, length); if (partial) { display += "..."; } } else if (type == "json") { display += JsonDisplay(content, offset, length); if (partial) { display += "..."; } } else { display += HexDisplay(content, offset, length); } consoleEx.WriteLine(display); }
public static int Inspect(InspectOptions options, ConsoleEx consoleEx = null) { if (options == null) { throw new ArgumentNullException(nameof(options)); } consoleEx ??= new ConsoleEx(); using (Stream fileIn = File.OpenRead(options.File)) using (HelixFileDecryptor decryptor = new HelixFileDecryptor(fileIn)) { consoleEx.WriteLine("== Outer Header =="); Dictionary <string, byte[]> header = null; decryptor.Initialize(DerivedBytesProvider.FromPassword(options.Password, options.KeyFile), (h) => header = h); consoleEx.WriteLine("Designator: " + BitConverter.ToString(header[nameof(FileHeader.fileDesignator)]).Replace("-", "") + " (" + new string(Encoding.ASCII.GetString(header[nameof(FileHeader.fileDesignator)]) .Select(c => Char.IsControl(c) ? '?' : c) .ToArray()) + ")"); consoleEx.WriteLine("Password Salt: " + BitConverter.ToString(header[nameof(FileHeader.passwordSalt)]).Replace("-", "")); consoleEx.WriteLine("HMAC Salt: " + BitConverter.ToString(header[nameof(FileHeader.hmacSalt)]).Replace("-", "")); consoleEx.WriteLine("IV: " + BitConverter.ToString(header[nameof(FileHeader.iv)]).Replace("-", "")); consoleEx.WriteLine("Authn (HMAC): " + BitConverter.ToString(header[nameof(FileHeader.headerAuthnDisk)]).Replace("-", "")); consoleEx.WriteLine(); consoleEx.WriteLine("== Inner Header =="); decryptor.ReadHeader( afterRawMetadata: (r) => consoleEx.WriteLine(JsonFormat(r))); consoleEx.WriteLine(); using Stream content = decryptor.GetContentStream(); ContentPreview(content, consoleEx, options.ContentFormat); consoleEx.WriteLine(); } //Reopen to calculate the checksum using (Stream fileIn = File.OpenRead(options.File)) using (HelixFileDecryptor decryptor = new HelixFileDecryptor(fileIn)) { decryptor.Initialize(DerivedBytesProvider.FromPassword(options.Password, options.KeyFile)); decryptor.ReadHeader(); using Stream content = decryptor.GetContentStream(); Checksum(content, consoleEx); return(0); } }