From 0ca897672a68117c35350f06c7f57bbbb6c241a9 Mon Sep 17 00:00:00 2001 From: AlphaDelta Date: Wed, 31 May 2023 16:37:57 +1200 Subject: [PATCH] 0.3.0-RC.1 - Expand options & info --- WizTreeCompare/FormMain.Designer.cs | 230 +++++++++++------- WizTreeCompare/FormMain.cs | 53 ++++- WizTreeCompare/Program.cs | 69 ++++-- WizTreeCompare/WTComparer.cs | 337 +++++++++++++++++++++++---- WizTreeCompare/WizTreeCompare.csproj | 5 +- 5 files changed, 532 insertions(+), 162 deletions(-) diff --git a/WizTreeCompare/FormMain.Designer.cs b/WizTreeCompare/FormMain.Designer.cs index ea9a1c6..1ac5bce 100644 --- a/WizTreeCompare/FormMain.Designer.cs +++ b/WizTreeCompare/FormMain.Designer.cs @@ -28,123 +28,169 @@ protected override void Dispose(bool disposing) /// private void InitializeComponent() { - this.gbFiles = new System.Windows.Forms.GroupBox(); - this.btnFuture = new System.Windows.Forms.Button(); - this.btnPast = new System.Windows.Forms.Button(); - this.txtFuture = new System.Windows.Forms.TextBox(); - this.lblPast = new System.Windows.Forms.Label(); - this.txtPast = new System.Windows.Forms.TextBox(); - this.lblFuture = new System.Windows.Forms.Label(); - this.btnCompare = new System.Windows.Forms.Button(); - this.gbFiles.SuspendLayout(); - this.SuspendLayout(); + gbFiles = new GroupBox(); + btnFuture = new Button(); + btnPast = new Button(); + txtFuture = new TextBox(); + lblPast = new Label(); + txtPast = new TextBox(); + lblFuture = new Label(); + btnCompare = new Button(); + gbOptions = new GroupBox(); + chkDry = new CheckBox(); + chkIncludeUnchanged = new CheckBox(); + chkIncludeNegatives = new CheckBox(); + gbFiles.SuspendLayout(); + gbOptions.SuspendLayout(); + SuspendLayout(); // // gbFiles // - this.gbFiles.Anchor = ((System.Windows.Forms.AnchorStyles)(((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Left) - | System.Windows.Forms.AnchorStyles.Right))); - this.gbFiles.Controls.Add(this.btnFuture); - this.gbFiles.Controls.Add(this.btnPast); - this.gbFiles.Controls.Add(this.txtFuture); - this.gbFiles.Controls.Add(this.lblPast); - this.gbFiles.Controls.Add(this.txtPast); - this.gbFiles.Controls.Add(this.lblFuture); - this.gbFiles.Location = new System.Drawing.Point(12, 12); - this.gbFiles.Name = "gbFiles"; - this.gbFiles.Size = new System.Drawing.Size(460, 84); - this.gbFiles.TabIndex = 0; - this.gbFiles.TabStop = false; - this.gbFiles.Text = "Files"; + gbFiles.Anchor = AnchorStyles.Top | AnchorStyles.Left | AnchorStyles.Right; + gbFiles.Controls.Add(btnFuture); + gbFiles.Controls.Add(btnPast); + gbFiles.Controls.Add(txtFuture); + gbFiles.Controls.Add(lblPast); + gbFiles.Controls.Add(txtPast); + gbFiles.Controls.Add(lblFuture); + gbFiles.Location = new Point(12, 12); + gbFiles.Name = "gbFiles"; + gbFiles.Size = new Size(460, 84); + gbFiles.TabIndex = 0; + gbFiles.TabStop = false; + gbFiles.Text = "Files"; // // btnFuture // - this.btnFuture.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Right))); - this.btnFuture.Location = new System.Drawing.Point(425, 50); - this.btnFuture.Name = "btnFuture"; - this.btnFuture.Size = new System.Drawing.Size(29, 23); - this.btnFuture.TabIndex = 3; - this.btnFuture.Text = "..."; - this.btnFuture.UseVisualStyleBackColor = true; - this.btnFuture.Click += new System.EventHandler(this.btnFuture_Click); + btnFuture.Anchor = AnchorStyles.Top | AnchorStyles.Right; + btnFuture.Location = new Point(425, 50); + btnFuture.Name = "btnFuture"; + btnFuture.Size = new Size(29, 23); + btnFuture.TabIndex = 3; + btnFuture.Text = "..."; + btnFuture.UseVisualStyleBackColor = true; + btnFuture.Click += btnFuture_Click; // // btnPast // - this.btnPast.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Right))); - this.btnPast.Location = new System.Drawing.Point(425, 21); - this.btnPast.Name = "btnPast"; - this.btnPast.Size = new System.Drawing.Size(29, 24); - this.btnPast.TabIndex = 1; - this.btnPast.Text = "..."; - this.btnPast.UseVisualStyleBackColor = true; - this.btnPast.Click += new System.EventHandler(this.btnPast_Click); + btnPast.Anchor = AnchorStyles.Top | AnchorStyles.Right; + btnPast.Location = new Point(425, 21); + btnPast.Name = "btnPast"; + btnPast.Size = new Size(29, 24); + btnPast.TabIndex = 1; + btnPast.Text = "..."; + btnPast.UseVisualStyleBackColor = true; + btnPast.Click += btnPast_Click; // // txtFuture // - this.txtFuture.Anchor = ((System.Windows.Forms.AnchorStyles)(((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Left) - | System.Windows.Forms.AnchorStyles.Right))); - this.txtFuture.BackColor = System.Drawing.SystemColors.Control; - this.txtFuture.Location = new System.Drawing.Point(80, 51); - this.txtFuture.Name = "txtFuture"; - this.txtFuture.Size = new System.Drawing.Size(339, 23); - this.txtFuture.TabIndex = 2; + txtFuture.Anchor = AnchorStyles.Top | AnchorStyles.Left | AnchorStyles.Right; + txtFuture.BackColor = SystemColors.Control; + txtFuture.Location = new Point(80, 51); + txtFuture.Name = "txtFuture"; + txtFuture.Size = new Size(339, 23); + txtFuture.TabIndex = 2; // // lblPast // - this.lblPast.AutoSize = true; - this.lblPast.Location = new System.Drawing.Point(18, 25); - this.lblPast.Name = "lblPast"; - this.lblPast.Size = new System.Drawing.Size(56, 15); - this.lblPast.TabIndex = 0; - this.lblPast.Text = "Past CSV:"; + lblPast.AutoSize = true; + lblPast.Location = new Point(18, 25); + lblPast.Name = "lblPast"; + lblPast.Size = new Size(56, 15); + lblPast.TabIndex = 0; + lblPast.Text = "Past CSV:"; // // txtPast // - this.txtPast.Anchor = ((System.Windows.Forms.AnchorStyles)(((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Left) - | System.Windows.Forms.AnchorStyles.Right))); - this.txtPast.BackColor = System.Drawing.SystemColors.Control; - this.txtPast.Location = new System.Drawing.Point(80, 22); - this.txtPast.Name = "txtPast"; - this.txtPast.Size = new System.Drawing.Size(339, 23); - this.txtPast.TabIndex = 0; + txtPast.Anchor = AnchorStyles.Top | AnchorStyles.Left | AnchorStyles.Right; + txtPast.BackColor = SystemColors.Control; + txtPast.Location = new Point(80, 22); + txtPast.Name = "txtPast"; + txtPast.Size = new Size(339, 23); + txtPast.TabIndex = 0; // // lblFuture // - this.lblFuture.AutoSize = true; - this.lblFuture.Location = new System.Drawing.Point(6, 54); - this.lblFuture.Name = "lblFuture"; - this.lblFuture.Size = new System.Drawing.Size(68, 15); - this.lblFuture.TabIndex = 0; - this.lblFuture.Text = "Future CSV:"; + lblFuture.AutoSize = true; + lblFuture.Location = new Point(6, 54); + lblFuture.Name = "lblFuture"; + lblFuture.Size = new Size(68, 15); + lblFuture.TabIndex = 0; + lblFuture.Text = "Future CSV:"; // // btnCompare // - this.btnCompare.Anchor = ((System.Windows.Forms.AnchorStyles)((((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Bottom) - | System.Windows.Forms.AnchorStyles.Left) - | System.Windows.Forms.AnchorStyles.Right))); - this.btnCompare.Location = new System.Drawing.Point(12, 102); - this.btnCompare.Name = "btnCompare"; - this.btnCompare.Size = new System.Drawing.Size(460, 43); - this.btnCompare.TabIndex = 4; - this.btnCompare.Text = "Compare..."; - this.btnCompare.UseVisualStyleBackColor = true; - this.btnCompare.Click += new System.EventHandler(this.btnCompare_Click); + btnCompare.Anchor = AnchorStyles.Bottom | AnchorStyles.Left | AnchorStyles.Right; + btnCompare.Location = new Point(12, 208); + btnCompare.Name = "btnCompare"; + btnCompare.Size = new Size(460, 43); + btnCompare.TabIndex = 4; + btnCompare.Text = "Compare..."; + btnCompare.UseVisualStyleBackColor = true; + btnCompare.Click += btnCompare_Click; + // + // gbOptions + // + gbOptions.Anchor = AnchorStyles.Top | AnchorStyles.Bottom | AnchorStyles.Left | AnchorStyles.Right; + gbOptions.Controls.Add(chkDry); + gbOptions.Controls.Add(chkIncludeUnchanged); + gbOptions.Controls.Add(chkIncludeNegatives); + gbOptions.Location = new Point(12, 102); + gbOptions.Name = "gbOptions"; + gbOptions.Size = new Size(460, 100); + gbOptions.TabIndex = 5; + gbOptions.TabStop = false; + gbOptions.Text = "Options"; + // + // chkDry + // + chkDry.AutoSize = true; + chkDry.Location = new Point(17, 72); + chkDry.Name = "chkDry"; + chkDry.Size = new Size(286, 19); + chkDry.TabIndex = 0; + chkDry.Text = "Dry run (provide information only, no file output)"; + chkDry.UseVisualStyleBackColor = true; + // + // chkIncludeUnchanged + // + chkIncludeUnchanged.AutoSize = true; + chkIncludeUnchanged.Location = new Point(17, 47); + chkIncludeUnchanged.Name = "chkIncludeUnchanged"; + chkIncludeUnchanged.Size = new Size(319, 19); + chkIncludeUnchanged.TabIndex = 0; + chkIncludeUnchanged.Text = "Include unchanged (includes files with no size changes)"; + chkIncludeUnchanged.UseVisualStyleBackColor = true; + // + // chkIncludeNegatives + // + chkIncludeNegatives.AutoSize = true; + chkIncludeNegatives.ForeColor = Color.Red; + chkIncludeNegatives.Location = new Point(17, 22); + chkIncludeNegatives.Name = "chkIncludeNegatives"; + chkIncludeNegatives.Size = new Size(343, 19); + chkIncludeNegatives.TabIndex = 0; + chkIncludeNegatives.Text = "Include negatives (includes deleted files and size reductions)"; + chkIncludeNegatives.UseVisualStyleBackColor = true; // // FormMain // - this.AutoScaleDimensions = new System.Drawing.SizeF(7F, 15F); - this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font; - this.BackColor = System.Drawing.SystemColors.Window; - this.ClientSize = new System.Drawing.Size(484, 157); - this.Controls.Add(this.btnCompare); - this.Controls.Add(this.gbFiles); - this.MaximumSize = new System.Drawing.Size(1200, 196); - this.MinimumSize = new System.Drawing.Size(300, 196); - this.Name = "FormMain"; - this.Text = "WizTree Compare"; - this.gbFiles.ResumeLayout(false); - this.gbFiles.PerformLayout(); - this.ResumeLayout(false); - + AutoScaleDimensions = new SizeF(7F, 15F); + AutoScaleMode = AutoScaleMode.Font; + BackColor = SystemColors.Window; + ClientSize = new Size(484, 263); + Controls.Add(gbOptions); + Controls.Add(btnCompare); + Controls.Add(gbFiles); + MaximumSize = new Size(1200, 420); + MinimumSize = new Size(300, 196); + Name = "FormMain"; + Text = "WizTree Compare"; + gbFiles.ResumeLayout(false); + gbFiles.PerformLayout(); + gbOptions.ResumeLayout(false); + gbOptions.PerformLayout(); + ResumeLayout(false); } #endregion @@ -157,5 +203,9 @@ private void InitializeComponent() private Button btnFuture; private Button btnPast; private Button btnCompare; + private GroupBox gbOptions; + private CheckBox chkIncludeNegatives; + private CheckBox chkDry; + private CheckBox chkIncludeUnchanged; } } \ No newline at end of file diff --git a/WizTreeCompare/FormMain.cs b/WizTreeCompare/FormMain.cs index b4d1bf4..c04597c 100644 --- a/WizTreeCompare/FormMain.cs +++ b/WizTreeCompare/FormMain.cs @@ -14,11 +14,19 @@ public FormMain() txtPast.DragDrop += Txt_DragDrop; txtFuture.DragDrop += Txt_DragDrop; + + ToolTip tipNegatives = new ToolTip(); + tipNegatives.InitialDelay = 1000; + tipNegatives.SetToolTip(chkIncludeNegatives, "Not currently compatiable with WizTree"); + +#if DEBUG + chkDry.Checked = true; +#endif } private void Txt_DragEnter(object sender, DragEventArgs e) { - if(e.Data.GetDataPresent(DataFormats.FileDrop)) + if (e.Data.GetDataPresent(DataFormats.FileDrop)) { if (((string[])e.Data.GetData(DataFormats.FileDrop)).Length == 1) { @@ -60,11 +68,40 @@ private void SelectFile(TextBox tb) } } + CancellationTokenSource token = new CancellationTokenSource(); + Task tcompare = Task.CompletedTask; private void btnCompare_Click(object sender, EventArgs e) { + if (!tcompare.IsCompleted) + { + token.Cancel(); + return; + } + string output = null; + tcompare = new Task(() => + { + WTComparer comparer = new WTComparer(txtPast.Text, txtFuture.Text) + { + IncludeNegatives = chkIncludeNegatives.Checked, + IncludeUnchanged = chkIncludeUnchanged.Checked, + Dry = chkDry.Checked, + CancellationToken = token.Token + }; + comparer.CompareAndSave(output); + + this.Invoke(() => + { + btnCompare.Text = "Compare..."; + btnCompare.Enabled = true; - using(SaveFileDialog sfd = new SaveFileDialog()) + token.Dispose(); + token = new CancellationTokenSource(); + }); + }); + + /* Save file dialog */ + using (SaveFileDialog sfd = new SaveFileDialog()) { sfd.FileName = $"WizTreeCompare_{DateTime.Now:yyyyMMddHHmmss}.csv"; sfd.Filter = "CSV File (*.csv)|*.csv"; @@ -75,8 +112,16 @@ private void btnCompare_Click(object sender, EventArgs e) output = sfd.FileName; } - WTComparer comparer = new WTComparer(txtPast.Text, txtFuture.Text); - comparer.CompareAndSave(output); + btnCompare.Enabled = false; + btnCompare.Text = "Cancel"; + Task.Run(async () => + { + await Task.Delay(1000); + this.Invoke(() => { btnCompare.Enabled = true; }); + }); + + /* Compare */ + tcompare.Start(); } } } \ No newline at end of file diff --git a/WizTreeCompare/Program.cs b/WizTreeCompare/Program.cs index d61f6a8..bf7e71a 100644 --- a/WizTreeCompare/Program.cs +++ b/WizTreeCompare/Program.cs @@ -9,22 +9,26 @@ internal static class Program static void Main(string[] args) { //args = new string[] { "help" }; + bool probemode = args.Length >= 1 && args[0].Contains('p'); var prevfg = Console.ForegroundColor; var prevbg = Console.BackgroundColor; - Console.ForegroundColor = ConsoleColor.Black; - Console.BackgroundColor = ConsoleColor.Cyan; - Console.Write($" WizTreeCompare "); - Console.ForegroundColor = prevfg; - Console.BackgroundColor = prevbg; - Console.Write($" "); - Console.ForegroundColor = ConsoleColor.Black; - Console.BackgroundColor = ConsoleColor.Gray; - Console.WriteLine($" v{typeof(FormMain).Assembly.GetName().Version.ToString(3)} "); - Console.ForegroundColor = prevfg; - Console.BackgroundColor = prevbg; - Console.WriteLine(); + if (!probemode) + { + Console.ForegroundColor = ConsoleColor.Black; + Console.BackgroundColor = ConsoleColor.Cyan; + Console.Write($" WizTreeCompare "); + Console.ForegroundColor = prevfg; + Console.BackgroundColor = prevbg; + Console.Write($" "); + Console.ForegroundColor = ConsoleColor.Black; + Console.BackgroundColor = ConsoleColor.Gray; + Console.WriteLine($" v{typeof(FormMain).Assembly.GetName().Version.ToString(3)}-RC.1 "); + Console.ForegroundColor = prevfg; + Console.BackgroundColor = prevbg; + Console.WriteLine(); + } if (args.Length < 1) { @@ -36,6 +40,18 @@ static void Main(string[] args) WTComparer comparer = new WTComparer(args[0], args[1], true); comparer.CompareAndSave(args[2]); } + else if (args.Length == 4) + { + WTComparer comparer = new WTComparer(args[1], args[2], true) + { + IncludeNegatives = args[0].Contains('f') || args[0].Contains('n'), + IncludeUnchanged = args[0].Contains('f') || args[0].Contains('u'), + Dry = args[0].Contains('d'), + ForceYes = args[0].Contains('Y'), + ProbeMode = args[0].Contains('p'), + }; + comparer.CompareAndSave(args[3]); + } else { Console.ForegroundColor = ConsoleColor.Black; @@ -43,7 +59,20 @@ static void Main(string[] args) Console.WriteLine("Usage:"); Console.ForegroundColor = prevfg; Console.BackgroundColor = prevbg; - Console.WriteLine("WizTreeCompare "); + Console.WriteLine("WizTreeCompare [options] "); + Console.WriteLine(); + + Console.ForegroundColor = ConsoleColor.Black; + Console.BackgroundColor = ConsoleColor.White; + Console.WriteLine("Options:"); + Console.ForegroundColor = prevfg; + Console.BackgroundColor = prevbg; + Console.WriteLine("d - Dry run"); + Console.WriteLine("f - Full differential (implies 'n' and 'u')"); + Console.WriteLine("n - Include negative differences (deleted files and reduced/trunacted file sizes)"); + Console.WriteLine("u - Include zero differences (unchanged file sizes)"); + Console.WriteLine("p - Probe (reduced verbosity for easier post-processing, consider using 'Y' as well)"); + Console.WriteLine("Y - Force yes on all questions"); Console.WriteLine(); Console.ForegroundColor = ConsoleColor.Black; @@ -51,7 +80,7 @@ static void Main(string[] args) Console.WriteLine("Example:"); Console.ForegroundColor = prevfg; Console.BackgroundColor = prevbg; - Console.WriteLine("WizTreeCompare \"C:\\past.csv\" \"C:\\future.csv\" \"C:\\output.csv\""); + Console.WriteLine("WizTreeCompare dfY \"C:\\past.csv\" \"C:\\future.csv\" \"C:\\output.csv\""); Console.WriteLine(); Console.Write("Load the output CSV in WizTree via "); @@ -61,12 +90,12 @@ static void Main(string[] args) Console.ForegroundColor = prevfg; Console.BackgroundColor = prevbg; -//#if DEBUG -// try -// { -// Console.ReadKey(); -// }catch { } -//#endif + //#if DEBUG + // try + // { + // Console.ReadKey(); + // }catch { } + //#endif } } } diff --git a/WizTreeCompare/WTComparer.cs b/WizTreeCompare/WTComparer.cs index 1f7b9bd..806189d 100644 --- a/WizTreeCompare/WTComparer.cs +++ b/WizTreeCompare/WTComparer.cs @@ -2,7 +2,10 @@ using CsvHelper.TypeConversion; using System; using System.Collections.Generic; +using System.Diagnostics; +using System.Drawing; using System.Linq; +using System.Runtime.CompilerServices; using System.Text; using System.Threading.Tasks; @@ -10,10 +13,9 @@ namespace WizTreeCompare { public class WTComparer { - enum LogType { Info, Warning, Error } - string PastPath, FuturePath; - bool IsConsole; + public bool IsConsole, IncludeNegatives = false, Dry = false, ForceYes = false, IncludeUnchanged = false, ProbeMode = false; + public CancellationToken CancellationToken; public WTComparer(string pastpath, string futurepath, bool console = false) { this.PastPath = pastpath; @@ -24,7 +26,7 @@ public WTComparer(string pastpath, string futurepath, bool console = false) ConsoleColor prevfg; ConsoleColor prevbg; - public void CompareAndSave(string outputpath) + public void CompareAndSave(string outputpath) //TODO: Check full differential, add option to include zeroes { prevfg = Console.ForegroundColor; prevbg = Console.BackgroundColor; @@ -41,12 +43,16 @@ public void CompareAndSave(string outputpath) return; } - if (File.Exists(outputpath)) + if (File.Exists(outputpath) && !Dry) { if (!IsConsole) { - if (MessageBox.Show($"A file already exists at '{outputpath}'. Would you like you overwrite it?", "Warning", MessageBoxButtons.YesNo, MessageBoxIcon.Warning) != DialogResult.Yes) - return; + //if (MessageBox.Show($"A file already exists at '{outputpath}'. Would you like you overwrite it?", "Warning", MessageBoxButtons.YesNo, MessageBoxIcon.Warning) != DialogResult.Yes) + // return; + } + else if (ForceYes) + { + Log($"Force yes is on... the output file '{outputpath}' exists but will be overwritten", LogType.Warning); } else { @@ -58,10 +64,31 @@ public void CompareAndSave(string outputpath) } } + /* Set up progress logger */ + int nrow = 0; + TimeSpan tickrate = TimeSpan.FromMilliseconds(300); + var action = (ProgressContext ctx) => { }; + if (!ProbeMode) + action = (ProgressContext ctx) => + { + if (ctx.ProgressCurrent >= 0 && ctx.ProgressTotal > 0) + ctx.AnnounceProgress($"Accessing row {nrow}, {(ctx.ProgressCurrent / ctx.ProgressTotal) * 100:0.00}% complete, running for {(DateTime.Now - ctx.StartTime):m'm 's's 'fff'ms'}"); + else + ctx.AnnounceProgress($"Accessing row {nrow}, running for {(DateTime.Now - ctx.StartTime):m'm 's's 'fff'ms'}"); + }; + + + /* PAST CSV */ LogToConsole("Reading past CSV and populating dictionary..."); Dictionary pastrows = new Dictionary(); + if (CancellationToken.IsCancellationRequested) return; // + using (var progress = new ProgressContext(tickrate, action)) using (var sr = new StreamReader(PastPath, Encoding.UTF8)) { + progress.StartTime = DateTime.Now; + progress.ProgressTotal = sr.BaseStream.CanSeek ? sr.BaseStream.Length : -1; + progress.Action(progress); + if (sr.Peek() == 'G') sr.ReadLine(); @@ -69,6 +96,14 @@ public void CompareAndSave(string outputpath) { foreach (WTCsvRow row in csv.GetRecords()) { + if (CancellationToken.IsCancellationRequested) return; // + + /* Progress */ + nrow = csv.Context.Parser.Row; + progress.ProgressCurrent = progress.ProgressTotal > 0 ? sr.BaseStream.Position : -1; + progress.InvokeLater(); + + /* Work */ if (row.FileName.EndsWith('\\') || row.FileName.EndsWith('/')) continue; // It's a folder, we don't care about folders @@ -76,72 +111,283 @@ public void CompareAndSave(string outputpath) } } } + LogToConsole($"Past dictionary populated with {pastrows.Count} entries", LogType.InfoImportant); + /* FUTURE CSV subtract PAST CSV */ LogToConsole("Reading future CSV and populating output differential file..."); - using (var sr = new StreamReader(FuturePath, Encoding.UTF8)) + int additions = 0, modifications = 0, deletions = 0, nochange = 0; + long addbyte = 0, subbyte = 0, diffbyte = 0; + using (var writer = Dry ? StreamWriter.Null : new StreamWriter(outputpath, false, Encoding.UTF8)) + using (var csvoutput = new CsvWriter(writer, new CsvHelper.Configuration.CsvConfiguration(System.Globalization.CultureInfo.InvariantCulture) { - if (sr.Peek() == 'G') - sr.ReadLine(); + ShouldQuote = (e) => e.FieldType == typeof(string) + })) + { + HashSet seen = new HashSet(pastrows.Count); - using (var csv = new CsvReader(sr, System.Globalization.CultureInfo.InvariantCulture)) - using (var writer = new StreamWriter(outputpath, false, Encoding.UTF8)) - using (var csvoutput = new CsvWriter(writer, new CsvHelper.Configuration.CsvConfiguration(System.Globalization.CultureInfo.InvariantCulture) - { - ShouldQuote = (e) => e.FieldType == typeof(string) - })) + using (var progress = new ProgressContext(tickrate, action)) + using (var sr = new StreamReader(FuturePath, Encoding.UTF8)) { - var options = new TypeConverterOptions { Formats = new[] { "yyyy-MM-dd HH:mm:ss" } }; - csvoutput.Context.TypeConverterOptionsCache.AddOptions(options); - csvoutput.Context.TypeConverterOptionsCache.AddOptions(options); + /* Progress */ + progress.StartTime = DateTime.Now; + progress.ProgressTotal = sr.BaseStream.CanSeek ? sr.BaseStream.Length : -1; + progress.Action(progress); - csvoutput.WriteHeader(); - foreach (WTCsvRow futurerow in csv.GetRecords()) + /* Work */ + if (sr.Peek() == 'G') + sr.ReadLine(); + + using (var csv = new CsvReader(sr, System.Globalization.CultureInfo.InvariantCulture)) { - if (futurerow.FileName.EndsWith('\\') || futurerow.FileName.EndsWith('/')) - continue; // It's a folder, we don't care about folders + var options = new TypeConverterOptions { Formats = new[] { "yyyy-MM-dd HH:mm:ss" } }; + csvoutput.Context.TypeConverterOptionsCache.AddOptions(options); + csvoutput.Context.TypeConverterOptionsCache.AddOptions(options); - if (!pastrows.ContainsKey(futurerow.FileName)) + csvoutput.WriteHeader(); + foreach (WTCsvRow futurerow in csv.GetRecords()) { - csvoutput.NextRecord(); - csvoutput.WriteRecord(futurerow); - } - else + if (CancellationToken.IsCancellationRequested) return; // + + /* Progress */ + nrow = csv.Context.Parser.Row; + progress.ProgressCurrent = progress.ProgressTotal > 0 ? sr.BaseStream.Position : -1; + progress.InvokeLater(); + + /* Work */ + if (futurerow.FileName.EndsWith('\\') || futurerow.FileName.EndsWith('/')) + continue; // It's a folder, we don't care about folders + + if (!pastrows.ContainsKey(futurerow.FileName)) + { + //It's a new file, add it as-is + csvoutput.NextRecord(); + csvoutput.WriteRecord(futurerow); + additions++; + addbyte += futurerow.Size; + } + else + { + seen.Add(futurerow.FileName); + + //Same file, calc difference + WTCsvRow pastrow = pastrows[futurerow.FileName]; + + long size = futurerow.Size - pastrow.Size; + if (!IncludeNegatives && size < 0) continue; //Negative size change + if (!IncludeUnchanged && size == 0) continue; + + long allocated = futurerow.Allocated - pastrow.Allocated; + if (!IncludeNegatives && allocated < 0) allocated = 0; //Allocated chunks can be removed despite file being logically larger -- What a weird bug + + WTCsvRow newrow = new WTCsvRow(); + newrow.FileName = futurerow.FileName; + newrow.Size = size; + newrow.Allocated = allocated; + newrow.Modified = futurerow.Modified; + newrow.Attributes = futurerow.Attributes; + + csvoutput.NextRecord(); + csvoutput.WriteRecord(newrow); + + if(newrow.Size == 0) + nochange++; + else + modifications++; + + if (newrow.Size > 0) + addbyte += newrow.Size; + else + subbyte += -newrow.Size; + } + }/*end foreach*/ + }/*end using CsvReader*/ + }/*end using StreamReader*/ + //LogToConsole($"Populated output csv with {additions} additions and {modifications} differences", LogType.InfoImportant); + + /* Full */ + if (IncludeNegatives) + { + LogToConsole("Finding and populating deletions..."); + + using (var progress = new ProgressContext(tickrate, action)) + { + /* Progress */ + nrow = 0; + progress.StartTime = DateTime.Now; + progress.ProgressTotal = pastrows.Count; + progress.Action(progress); + + /* Work */ + foreach (var kv in pastrows) { - WTCsvRow pastrow = pastrows[futurerow.FileName]; + if (CancellationToken.IsCancellationRequested) return; // - long size = futurerow.Size - pastrow.Size; - if (size < 1) continue; + progress.ProgressCurrent = ++nrow; + progress.InvokeLater(); - long allocated = futurerow.Allocated - pastrow.Allocated; - if (allocated < 0) allocated = 0; //Allocated chunks can be removed despite file being logically larger -- What a weird bug + if (seen.Contains(kv.Key)) continue; WTCsvRow newrow = new WTCsvRow(); - newrow.FileName = futurerow.FileName; - newrow.Size = size; - newrow.Allocated = allocated; - newrow.Modified = futurerow.Modified; - newrow.Attributes = futurerow.Attributes; + newrow.FileName = kv.Value.FileName; + newrow.Size = -kv.Value.Size; + newrow.Allocated = 0; + newrow.Modified = kv.Value.Modified; + newrow.Attributes = kv.Value.Attributes; csvoutput.NextRecord(); csvoutput.WriteRecord(newrow); + deletions++; + subbyte += kv.Value.Size; } } + + //LogToConsole($"Populated output csv with {deletions} deletions", LogType.InfoImportant); } + }/*end using CsvWriter*/ + + long bytediff = addbyte - subbyte; + if (!IncludeNegatives) subbyte = deletions = -1; + if (ProbeMode) + { + LogToConsole($"Files - {additions} additions - {deletions:0;'N/A';0} deletions - {modifications} modifications", LogType.InfoImportant); + LogToConsole($"Bytes - {addbyte} added - {subbyte:0;'N/A';0} subtracted - {bytediff:+0;-0;0} differential", LogType.InfoImportant); + } + else + { + LogToConsole($"Files - {additions:#,0} additions - {deletions:#,0;'N/A';0} deletions - {modifications:#,0} modifications", LogType.InfoImportant); + LogToConsole($"Bytes - {BytesToString(addbyte)} added - {(subbyte < 0 ? "N/A" : BytesToString(subbyte))} subtracted - {bytediff:+;-;}{BytesToString(bytediff)} differential", LogType.InfoImportant); } - Log($"Differential complete at '{outputpath}'"); + if (!Dry) + Log($"Differential complete at '{outputpath}'", LogType.Success); + else + Log($"Dry run complete", LogType.Success); + Console.WriteLine(); } + /* ========================== + * Helpers + * ========================== + */ + static string BytesToString(long byteCount) + { + //https://stackoverflow.com/a/4975942 + string[] suf = { " B", " KiB", " MiB", " GiB", " TiB", " PiB", " EiB" }; //Longs run out around EB + if (byteCount == 0) + return "0" + suf[0]; + long bytes = Math.Abs(byteCount); + int place = Convert.ToInt32(Math.Floor(Math.Log(bytes, 1024))); + double num = Math.Round(bytes / Math.Pow(1024, place), 1); + return (Math.Sign(byteCount) * num).ToString("0.###") + suf[place]; + } + + protected class LaterContext + { + public TimeSpan Delay; + public Action Action; + public Task Task = Task.CompletedTask; + public Dictionary Memory = new Dictionary(8); + + public CancellationTokenSource TokenSource = new CancellationTokenSource(); + + public LaterContext(TimeSpan tickrate, Action action) + { + Delay = tickrate; + Action = action; + } + + public Task InvokeLater() + { + + if (!this.Task.IsCompleted) return this.Task; + + this.Task = Task.Run(async () => + { + await Task.Delay(this.Delay, TokenSource.Token); + + if (!TokenSource.Token.IsCancellationRequested) + this.Action.DynamicInvoke(this); + }); + + return this.Task; + } + } + + protected class ProgressContext : LaterContext, IDisposable + { + public int CursorX = -1, CursorY = -1; + + public float ProgressCurrent = -1, ProgressTotal = -1; + + public DateTime StartTime = DateTime.Now; + + public ProgressContext(TimeSpan tickrate, Action action) : base(tickrate, action) { } + + public void AnnounceProgress(string msg) + { + /* Console */ + if (this.CursorX < 0 || this.CursorY < 0) + { + this.CursorX = Console.CursorLeft; + this.CursorY = Console.CursorTop; + Console.WriteLine(); + } + + int prevcursorX = Console.CursorLeft; + int prevcursorY = Console.CursorTop; + Console.CursorLeft = this.CursorX; + Console.CursorTop = this.CursorY; + + ConsoleColor prevfg = Console.ForegroundColor; + Console.ForegroundColor = ConsoleColor.DarkGreen; + Console.Write($" % "); + Console.ForegroundColor = ProgressCurrent >= ProgressTotal ? ConsoleColor.Green : (prevfg == ConsoleColor.Gray ? ConsoleColor.DarkGray : (ConsoleColor)((byte)prevfg ^ 0x08)); + Console.Write($" "); + Console.WriteLine(msg); + Console.ForegroundColor = prevfg; + + Console.CursorLeft = prevcursorX; + Console.CursorTop = prevcursorY; + } + + public void Dispose() + { + TokenSource.Cancel(); + this.Action(this); + + this.ProgressCurrent = 0; + this.CursorX = -1; + this.CursorY = -1; + } + } + + enum LogType { Success = 0, Info = 1, InfoImportant = 1 | 0x08, Warning = 2 | 0x08, Error = 3 | 0x08 } void LogToConsole(string msg, LogType type = LogType.Info, bool writeline = true) { + if (ProbeMode) + { + if (((int)type & 0x08) == 0x08) Console.WriteLine(msg); + return; + } + switch (type) { default: - case LogType.Info: + case LogType.InfoImportant: Console.ForegroundColor = ConsoleColor.Black; Console.BackgroundColor = ConsoleColor.Cyan; Console.Write($" i "); break; + case LogType.Info: + Console.ForegroundColor = ConsoleColor.Cyan; + Console.BackgroundColor = ConsoleColor.Black; + Console.Write($" i "); + break; + case LogType.Success: + Console.ForegroundColor = ConsoleColor.Green; + Console.BackgroundColor = ConsoleColor.Black; + Console.Write($" ~ "); + break; case LogType.Warning: Console.ForegroundColor = ConsoleColor.Black; Console.BackgroundColor = ConsoleColor.Yellow; @@ -163,16 +409,15 @@ void LogToConsole(string msg, LogType type = LogType.Info, bool writeline = true void Log(string msg, LogType type = LogType.Info, bool writeline = true) { - if (IsConsole) - { - LogToConsole(msg, type, writeline); - } - else + LogToConsole(msg, type, writeline); + + if (!IsConsole) { switch (type) { default: case LogType.Info: + case LogType.Success: MessageBox.Show(msg, "Info", MessageBoxButtons.OK, MessageBoxIcon.Information); break; case LogType.Warning: @@ -183,6 +428,6 @@ void Log(string msg, LogType type = LogType.Info, bool writeline = true) break; } } - } + }/*end Log()*/ } } diff --git a/WizTreeCompare/WizTreeCompare.csproj b/WizTreeCompare/WizTreeCompare.csproj index 62998a9..bc6b583 100644 --- a/WizTreeCompare/WizTreeCompare.csproj +++ b/WizTreeCompare/WizTreeCompare.csproj @@ -6,8 +6,9 @@ disable true enable - 0.2.0.0 - 0.2.0.0 + 0.3.0.0 + $(AssemblyVersion) + $(AssemblyVersion)$(VersionPrefix)