From 10ce610db0746fb873439064f7a9bd686170d5af Mon Sep 17 00:00:00 2001 From: rlv-dan Date: Tue, 14 Apr 2020 20:34:03 +0200 Subject: [PATCH] Refactoring to avoid unsafe thread calls --- Snap2HTML/Models.cs | 67 +++++++++ Snap2HTML/Snap2HTML.csproj | 5 +- Snap2HTML/{frmMain_Helpers.cs => Utils.cs} | 54 ++----- Snap2HTML/frmMain.cs | 63 +++++++- Snap2HTML/frmMain_BackgroundWorker.cs | 159 ++++++--------------- 5 files changed, 186 insertions(+), 162 deletions(-) create mode 100644 Snap2HTML/Models.cs rename Snap2HTML/{frmMain_Helpers.cs => Utils.cs} (71%) diff --git a/Snap2HTML/Models.cs b/Snap2HTML/Models.cs new file mode 100644 index 0000000..ba64e85 --- /dev/null +++ b/Snap2HTML/Models.cs @@ -0,0 +1,67 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; + +namespace Snap2HTML +{ + public class SnapSettings + { + public string rootFolder { get; set; } + public string title { get; set; } + public string outputFile { get; set; } + public bool skipHiddenItems { get; set; } + public bool skipSystemItems { get; set; } + public bool openInBrowser { get; set; } + public bool linkFiles { get; set; } + public string linkRoot { get; set; } + } + + + public class SnappedFile + { + public SnappedFile( string name ) + { + this.Name = name; + this.Properties = new Dictionary(); + } + + public string Name { get; set; } + public Dictionary Properties { get; set; } + + public string GetProp( string key ) + { + if( this.Properties.ContainsKey( key ) ) + return this.Properties[key]; + else + return ""; + } + + } + + public class SnappedFolder + { + public SnappedFolder( string name, string path ) + { + this.Name = name; + this.Path = path; + this.Properties = new Dictionary(); + this.Files = new List(); + this.FullPath = ( this.Path + "\\" + this.Name ).Replace( "\\\\", "\\" ); + } + + public string Name { get; set; } + public string Path { get; set; } + public string FullPath { get; set; } + public Dictionary Properties { get; set; } + public List Files { get; set; } + + public string GetProp( string key ) + { + if( this.Properties.ContainsKey( key ) ) + return this.Properties[key]; + else + return ""; + } + } +} diff --git a/Snap2HTML/Snap2HTML.csproj b/Snap2HTML/Snap2HTML.csproj index 0c8f46b..f1cfdeb 100644 --- a/Snap2HTML/Snap2HTML.csproj +++ b/Snap2HTML/Snap2HTML.csproj @@ -78,9 +78,8 @@ Form - - Form - + + diff --git a/Snap2HTML/frmMain_Helpers.cs b/Snap2HTML/Utils.cs similarity index 71% rename from Snap2HTML/frmMain_Helpers.cs rename to Snap2HTML/Utils.cs index b99bb77..062f9ce 100644 --- a/Snap2HTML/frmMain_Helpers.cs +++ b/Snap2HTML/Utils.cs @@ -1,47 +1,20 @@ using System; using System.Collections.Generic; using System.ComponentModel; -using System.Data; -using System.Drawing; -using System.Text; -using System.Windows.Forms; -using CommandLine.Utility; +//using System.Data; +//using System.Drawing; +//using System.Text; +//using System.Windows.Forms; +//using CommandLine.Utility; using System.IO; using System.Diagnostics; namespace Snap2HTML { - public partial class frmMain : Form + class Utils { - // Sets the root path input box and makes related gui parts ready to use - private void SetRootPath( string path, bool pathIsValid = true ) - { - if( pathIsValid ) - { - txtRoot.Text = path; - cmdCreate.Enabled = true; - toolStripStatusLabel1.Text = ""; - if( initDone ) - { - txtLinkRoot.Text = txtRoot.Text; - txtTitle.Text = "Snapshot of " + txtRoot.Text; - } - } - else - { - txtRoot.Text = ""; - cmdCreate.Enabled = false; - toolStripStatusLabel1.Text = ""; - if( initDone ) - { - txtLinkRoot.Text = txtRoot.Text; - txtTitle.Text = ""; - } - } - } - // Recursive function to get all folders and subfolders of given path path - public void DirSearch( string sDir, List lstDirs, bool skipHidden, bool skipSystem, Stopwatch stopwatch ) + public static void DirSearch( string sDir, List lstDirs, bool skipHidden, bool skipSystem, Stopwatch stopwatch, BackgroundWorker backgroundWorker) { if( backgroundWorker.CancellationPending ) return; @@ -51,7 +24,7 @@ namespace Snap2HTML { bool includeThisFolder = true; - if( d.ToUpper().EndsWith( "SYSTEM VOLUME INFORMATION" ) ) includeThisFolder = false; + //if( d.ToUpper().EndsWith( "SYSTEM VOLUME INFORMATION" ) ) includeThisFolder = false; // exclude folders that have the system or hidden attr set (if required) if( skipHidden || skipSystem ) @@ -86,7 +59,7 @@ namespace Snap2HTML stopwatch.Restart(); } - DirSearch( d, lstDirs, skipHidden, skipSystem, stopwatch ); + DirSearch( d, lstDirs, skipHidden, skipSystem, stopwatch, backgroundWorker ); } } } @@ -97,7 +70,7 @@ namespace Snap2HTML } // Hack to sort folders correctly even if they have spaces/periods in them - private List SortDirList( List lstDirs ) + public static List SortDirList( List lstDirs ) { for( int n = 0; n < lstDirs.Count; n++ ) { @@ -115,7 +88,7 @@ namespace Snap2HTML // Replaces characters that may appear in filenames/paths that have special meaning to JavaScript // Info on u2028/u2029: https://en.wikipedia.org/wiki/Newline#Unicode - private string MakeCleanJsString( string s ) + public static string MakeCleanJsString( string s ) { return s.Replace( "\\", "\\\\" ) .Replace( "&", "&" ) @@ -125,7 +98,7 @@ namespace Snap2HTML } // Test string for matches against a wildcard pattern. Use ? and * as wildcards. (Wrapper around RegEx) - private bool IsWildcardMatch( String wildcard, String text, bool casesensitive ) + public static bool IsWildcardMatch( String wildcard, String text, bool casesensitive ) { System.Text.StringBuilder sb = new System.Text.StringBuilder( wildcard.Length + 10 ); sb.Append( "^" ); @@ -155,10 +128,9 @@ namespace Snap2HTML return regex.IsMatch( text ); } - private int ToUnixTimestamp(DateTime value) + public static int ToUnixTimestamp( DateTime value ) { return (int)Math.Truncate( ( value.ToUniversalTime().Subtract( new DateTime( 1970, 1, 1 ) ) ).TotalSeconds ); } - } } diff --git a/Snap2HTML/frmMain.cs b/Snap2HTML/frmMain.cs index 27aec48..b7cc161 100644 --- a/Snap2HTML/frmMain.cs +++ b/Snap2HTML/frmMain.cs @@ -155,14 +155,14 @@ namespace Snap2HTML // ensure source path format txtRoot.Text = System.IO.Path.GetFullPath( txtRoot.Text ); if (txtRoot.Text.EndsWith(@"\")) txtRoot.Text = txtRoot.Text.Substring(0, txtRoot.Text.Length - 1); - if ( IsWildcardMatch( "?:" , txtRoot.Text , false ) ) txtRoot.Text += @"\"; // add backslash to path if only letter and colon eg "c:" + if( Utils.IsWildcardMatch( "?:", txtRoot.Text, false ) ) txtRoot.Text += @"\"; // add backslash to path if only letter and colon eg "c:" // add slash or backslash to end of link (in cases where it is clearthat we we can) if( !txtLinkRoot.Text.EndsWith( @"/" ) && txtLinkRoot.Text.ToLower().StartsWith( @"http" ) ) // web site { txtLinkRoot.Text += @"/"; } - if( !txtLinkRoot.Text.EndsWith( @"\" ) && IsWildcardMatch( "?:*" , txtLinkRoot.Text , false )) // local disk + if( !txtLinkRoot.Text.EndsWith( @"\" ) && Utils.IsWildcardMatch( "?:*", txtLinkRoot.Text, false ) ) // local disk { txtLinkRoot.Text += @"\"; } @@ -201,8 +201,38 @@ namespace Snap2HTML Cursor.Current = Cursors.WaitCursor; this.Text = "Snap2HTML (Working... Press Escape to Cancel)"; tabControl1.Enabled = false; - backgroundWorker.RunWorkerAsync(); + var settings = new SnapSettings() + { + rootFolder = txtRoot.Text, + title = txtTitle.Text, + outputFile = saveFileDialog1.FileName, + skipHiddenItems = !chkHidden.Checked, + skipSystemItems = !chkSystem.Checked, + openInBrowser = chkOpenOutput.Checked, + linkFiles = chkLinkFiles.Checked, + linkRoot = txtLinkRoot.Text, + }; + backgroundWorker.RunWorkerAsync(argument: settings); + + } + + private void backgroundWorker_ProgressChanged( object sender, ProgressChangedEventArgs e ) + { + toolStripStatusLabel1.Text = e.UserState.ToString(); + } + + private void backgroundWorker_RunWorkerCompleted( object sender, RunWorkerCompletedEventArgs e ) + { + Cursor.Current = Cursors.Default; + tabControl1.Enabled = true; + this.Text = "Snap2HTML"; + + // Quit when finished if automated via command line + if( outFile != "" ) + { + Application.Exit(); + } } private void chkLinkFiles_CheckedChanged(object sender, EventArgs e) @@ -278,5 +308,32 @@ namespace Snap2HTML } } + // Sets the root path input box and makes related gui parts ready to use + private void SetRootPath( string path, bool pathIsValid = true ) + { + if( pathIsValid ) + { + txtRoot.Text = path; + cmdCreate.Enabled = true; + toolStripStatusLabel1.Text = ""; + if( initDone ) + { + txtLinkRoot.Text = txtRoot.Text; + txtTitle.Text = "Snapshot of " + txtRoot.Text; + } + } + else + { + txtRoot.Text = ""; + cmdCreate.Enabled = false; + toolStripStatusLabel1.Text = ""; + if( initDone ) + { + txtLinkRoot.Text = txtRoot.Text; + txtTitle.Text = ""; + } + } + } + } } diff --git a/Snap2HTML/frmMain_BackgroundWorker.cs b/Snap2HTML/frmMain_BackgroundWorker.cs index cc108d0..74c4cc5 100644 --- a/Snap2HTML/frmMain_BackgroundWorker.cs +++ b/Snap2HTML/frmMain_BackgroundWorker.cs @@ -13,31 +13,23 @@ namespace Snap2HTML { public partial class frmMain : Form { + // This runs on a separate thread from the GUI private void backgroundWorker_DoWork( object sender, DoWorkEventArgs e ) { - var skipHidden = ( chkHidden.CheckState == CheckState.Unchecked ); - var skipSystem = ( chkSystem.CheckState == CheckState.Unchecked ); + var settings = (SnapSettings)e.Argument; // Get files & folders - var content = GetContent( txtRoot.Text, skipHidden, skipSystem ); + var content = GetContent( settings, backgroundWorker ); + if( backgroundWorker.CancellationPending ) + { + backgroundWorker.ReportProgress( 0, "User cancelled" ); + return; + } if( content == null ) { backgroundWorker.ReportProgress( 0, "Error reading source" ); return; } - if( backgroundWorker.CancellationPending ) - { - backgroundWorker.ReportProgress( 0, "User cancelled" ); - return; - } - - // Convert to string with JS data object - var jsContent = BuildJavascriptContentArray( content, 0 ); - if( backgroundWorker.CancellationPending ) - { - backgroundWorker.ReportProgress( 0, "User cancelled" ); - return; - } // Calculate some stats int totDirs = 0; @@ -52,6 +44,14 @@ namespace Snap2HTML totSize += Int64.Parse( file.GetProp( "Size" ) ); } } + + // Convert to string with JS data object + var jsContent = BuildJavascriptContentArray( content, 0, backgroundWorker ); + if( backgroundWorker.CancellationPending ) + { + backgroundWorker.ReportProgress( 0, "User cancelled" ); + return; + } // Let's generate the output @@ -75,7 +75,7 @@ namespace Snap2HTML // Build HTML sbContent.Replace( "[DIR DATA]", jsContent ); - sbContent.Replace( "[TITLE]", txtTitle.Text ); + sbContent.Replace( "[TITLE]", settings.title ); sbContent.Replace( "[APP LINK]", "http://www.rlvision.com" ); sbContent.Replace( "[APP NAME]", Application.ProductName ); sbContent.Replace( "[APP VER]", Application.ProductVersion.Split( '.' )[0] + "." + Application.ProductVersion.Split( '.' )[1] ); @@ -87,11 +87,11 @@ namespace Snap2HTML if( chkLinkFiles.Checked ) { sbContent.Replace( "[LINK FILES]", "true" ); - sbContent.Replace( "[LINK ROOT]", txtLinkRoot.Text.Replace( @"\", "/" ) ); - sbContent.Replace( "[SOURCE ROOT]", txtRoot.Text.Replace( @"\", "/" ) ); + sbContent.Replace( "[LINK ROOT]", settings.linkRoot.Replace( @"\", "/" ) ); + sbContent.Replace( "[SOURCE ROOT]", settings.rootFolder.Replace( @"\", "/" ) ); - string link_root = txtLinkRoot.Text.Replace( @"\", "/" ); - if( IsWildcardMatch( @"?:/*", link_root, false ) ) // "file://" is needed in the browser if path begins with drive letter, else it should not be used + string link_root = settings.linkRoot.Replace( @"\", "/" ); + if( Utils.IsWildcardMatch( @"?:/*", link_root, false ) ) // "file://" is needed in the browser if path begins with drive letter, else it should not be used { sbContent.Replace( "[LINK PROTOCOL]", @"file://" ); } @@ -105,25 +105,25 @@ namespace Snap2HTML sbContent.Replace( "[LINK FILES]", "false" ); sbContent.Replace( "[LINK PROTOCOL]", "" ); sbContent.Replace( "[LINK ROOT]", "" ); - sbContent.Replace( "[SOURCE ROOT]", txtRoot.Text.Replace( @"\", "/" ) ); + sbContent.Replace( "[SOURCE ROOT]", settings.rootFolder.Replace( @"\", "/" ) ); } // Write output file try { - using( System.IO.StreamWriter writer = new System.IO.StreamWriter( saveFileDialog1.FileName ) ) + using( System.IO.StreamWriter writer = new System.IO.StreamWriter( settings.outputFile ) ) { writer.Write( sbContent.ToString() ); } - if( chkOpenOutput.Checked == true ) + if( settings.openInBrowser ) { - System.Diagnostics.Process.Start( saveFileDialog1.FileName ); + System.Diagnostics.Process.Start( settings.outputFile ); } } - catch( System.Exception excpt ) + catch( Exception ex ) { - MessageBox.Show( "Failed to open file for writing:\n\n" + excpt, "Error", MessageBoxButtons.OK, MessageBoxIcon.Error ); + MessageBox.Show( "Failed to open file for writing:\n\n" + ex, "Error", MessageBoxButtons.OK, MessageBoxIcon.Error ); backgroundWorker.ReportProgress( 0, "An error occurred..." ); return; } @@ -133,77 +133,7 @@ namespace Snap2HTML backgroundWorker.ReportProgress( 100, "Ready!" ); } - private void backgroundWorker_ProgressChanged( object sender, ProgressChangedEventArgs e ) - { - toolStripStatusLabel1.Text = e.UserState.ToString(); - } - - private void backgroundWorker_RunWorkerCompleted( object sender, RunWorkerCompletedEventArgs e ) - { - Cursor.Current = Cursors.Default; - tabControl1.Enabled = true; - this.Text = "Snap2HTML"; - - // Quit when finished if automated via command line - if( outFile != "" ) - { - Application.Exit(); - } - } - - - // -------------------------------------------------------------------- - - public class SnappedFile - { - public SnappedFile( string name ) - { - this.Name = name; - this.Properties = new Dictionary(); - } - - public string Name { get; set; } - public Dictionary Properties { get; set; } - - public string GetProp( string key ) - { - if( this.Properties.ContainsKey( key ) ) - return this.Properties[key]; - else - return ""; - } - - } - - public class SnappedFolder - { - public SnappedFolder( string name, string path ) - { - this.Name = name; - this.Path = path; - this.Properties = new Dictionary(); - this.Files = new List(); - this.FullPath = ( this.Path + "\\" + this.Name ).Replace( "\\\\", "\\" ); - } - - public string Name { get; set; } - public string Path { get; set; } - public string FullPath { get; set; } - public Dictionary Properties { get; set; } - public List Files { get; set; } - - public string GetProp( string key ) - { - if( this.Properties.ContainsKey( key ) ) - return this.Properties[key]; - else - return ""; - } - } - - // -------------------------------------------------------------------- - - private List GetContent( string rootFolder, bool skipHidden, bool skipSystem ) + private static List GetContent( SnapSettings settings, BackgroundWorker bgWorker ) { var stopwatch = new Stopwatch(); stopwatch.Start(); @@ -212,11 +142,11 @@ namespace Snap2HTML // Get all folders var dirs = new List(); - dirs.Insert( 0, rootFolder ); - DirSearch( rootFolder, dirs, skipHidden, skipSystem, stopwatch ); - dirs = SortDirList( dirs ); + dirs.Insert( 0, settings.rootFolder ); + Utils.DirSearch( settings.rootFolder, dirs, settings.skipHiddenItems, settings.skipSystemItems, stopwatch, bgWorker ); + dirs = Utils.SortDirList( dirs ); - if( backgroundWorker.CancellationPending ) + if( bgWorker.CancellationPending ) { return null; } @@ -245,8 +175,8 @@ namespace Snap2HTML created_date = ""; try { - modified_date = ToUnixTimestamp(System.IO.Directory.GetLastWriteTime( dirName ).ToLocalTime()).ToString(); - created_date = ToUnixTimestamp( System.IO.Directory.GetCreationTime( dirName ).ToLocalTime() ).ToString(); + modified_date = Utils.ToUnixTimestamp( System.IO.Directory.GetLastWriteTime( dirName ).ToLocalTime() ).ToString(); + created_date = Utils.ToUnixTimestamp( System.IO.Directory.GetCreationTime( dirName ).ToLocalTime() ).ToString(); } catch( Exception ex ) { @@ -255,7 +185,6 @@ namespace Snap2HTML currentDir.Properties.Add( "Modified", modified_date ); currentDir.Properties.Add( "Created", created_date ); - // Get files in folder List files; try @@ -276,11 +205,11 @@ namespace Snap2HTML totFiles++; if(stopwatch.ElapsedMilliseconds >= 50) { - backgroundWorker.ReportProgress( 0, "Reading files... " + totFiles + " (" + sFile + ")" ); + bgWorker.ReportProgress( 0, "Reading files... " + totFiles + " (" + sFile + ")" ); stopwatch.Restart(); } - if( backgroundWorker.CancellationPending ) + if( bgWorker.CancellationPending ) { return null; } @@ -292,7 +221,7 @@ namespace Snap2HTML var isHidden = ( fi.Attributes & System.IO.FileAttributes.Hidden ) == System.IO.FileAttributes.Hidden; var isSystem = ( fi.Attributes & System.IO.FileAttributes.System ) == System.IO.FileAttributes.System; - if( ( isHidden && skipHidden ) || ( isSystem && skipSystem ) ) + if( ( isHidden && settings.skipHiddenItems ) || ( isSystem && settings.skipSystemItems ) ) { continue; } @@ -303,8 +232,8 @@ namespace Snap2HTML created_date = "-"; try { - modified_date = ToUnixTimestamp( fi.LastWriteTime.ToLocalTime() ).ToString(); - created_date = ToUnixTimestamp( fi.CreationTime.ToLocalTime() ).ToString(); + modified_date = Utils.ToUnixTimestamp( fi.LastWriteTime.ToLocalTime() ).ToString(); + created_date = Utils.ToUnixTimestamp( fi.CreationTime.ToLocalTime() ).ToString(); } catch( Exception ex ) { @@ -334,7 +263,7 @@ namespace Snap2HTML return result; } - private string BuildJavascriptContentArray(List content, int startIndex) + private static string BuildJavascriptContentArray(List content, int startIndex, BackgroundWorker bgWorker) { // Data format: // Each index in "dirs" array is an array representing a directory: @@ -346,7 +275,7 @@ namespace Snap2HTML // ID is the item index in dirs array. // Note: Modified date is in UNIX format - backgroundWorker.ReportProgress( 0, "Processing content..." ); + bgWorker.ReportProgress( 0, "Processing content..." ); var result = new StringBuilder(); @@ -394,13 +323,13 @@ namespace Snap2HTML sbCurrentDirArrays.Append( "D.p([" + lineBreakSymbol ); var sDirWithForwardSlash = currentDir.FullPath.Replace( @"\", "/" ); - sbCurrentDirArrays.Append( "\"" ).Append( MakeCleanJsString( sDirWithForwardSlash ) ).Append( "*" ).Append( "0" ).Append( "*" ).Append( currentDir.GetProp("Modified") ).Append( "\"," + lineBreakSymbol ); + sbCurrentDirArrays.Append( "\"" ).Append( Utils.MakeCleanJsString( sDirWithForwardSlash ) ).Append( "*" ).Append( "0" ).Append( "*" ).Append( currentDir.GetProp( "Modified" ) ).Append( "\"," + lineBreakSymbol ); long dirSize = 0; foreach( var currentFile in currentDir.Files ) { - sbCurrentDirArrays.Append( "\"" ).Append( MakeCleanJsString( currentFile.Name ) ).Append( "*" ).Append( currentFile.GetProp( "Size" ) ).Append( "*" ).Append( currentFile.GetProp("Modified") ).Append( "\"," + lineBreakSymbol ); + sbCurrentDirArrays.Append( "\"" ).Append( Utils.MakeCleanJsString( currentFile.Name ) ).Append( "*" ).Append( currentFile.GetProp( "Size" ) ).Append( "*" ).Append( currentFile.GetProp( "Modified" ) ).Append( "\"," + lineBreakSymbol ); try { dirSize += Int64.Parse( currentFile.GetProp("Size") ); @@ -420,7 +349,7 @@ namespace Snap2HTML sbCurrentDirArrays.Append( "\n" ); result.Append( sbCurrentDirArrays.ToString() ); - if( backgroundWorker.CancellationPending ) + if( bgWorker.CancellationPending ) { return null; }