Check-in [b80ceff467]
Not logged in

Many hyperlinks are disabled.
Use anonymous login to enable hyperlinks.

Overview
Comment:Bugfix: /? help now works. Enhanced: when the /verbose flag is specified, show the full stack track for exceptions. Refactor: increased code readability by not using `var` when it's not apparent at first glance what the type is going to be.
Downloads: Tarball | ZIP archive
Timelines: family | ancestors | descendants | both | develop
Files: files | file ages | folders
SHA1: b80ceff46759d458b40d4dbcf2b86d364d225a14
User & Date: tinus 2018-05-31 05:59:46.848
Context
2018-06-28
19:57
Small code optimizations. check-in: 3bd88a0184 user: tinus tags: develop
2018-05-31
05:59
Bugfix: /? help now works. Enhanced: when the /verbose flag is specified, show the full stack track for exceptions. Refactor: increased code readability by not using `var` when it's not apparent at first glance what the type is going to be. check-in: b80ceff467 user: tinus tags: develop
2018-03-23
21:16
Implemented command-line help. check-in: 41b3d45087 user: tinus tags: develop
Changes
Unified Diff Ignore Whitespace Patch
Changes to ZTUpdater/Program.cs.
8
9
10
11
12
13
14

15
16
17
18
19
20
21
22
23
24
25
namespace ZTUpdater
{
    class Program
    {
        [STAThread]
        static int Main(string[] args)
        {

            try
            {
                // Read the command-line options
                var MinimumLevel = TraceLevel.Info;
                string OptName = null;
                var Options = new Dictionary<string, string>();
                var Arguments = new List<string>();
                foreach (var arg in args)
                {
                    if (OptName != null)
                    {







>



<







8
9
10
11
12
13
14
15
16
17
18

19
20
21
22
23
24
25
namespace ZTUpdater
{
    class Program
    {
        [STAThread]
        static int Main(string[] args)
        {
            var MinimumLevel = TraceLevel.Info;
            try
            {
                // Read the command-line options

                string OptName = null;
                var Options = new Dictionary<string, string>();
                var Arguments = new List<string>();
                foreach (var arg in args)
                {
                    if (OptName != null)
                    {
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88








89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
                    var Dialog = new System.Windows.Forms.FolderBrowserDialog
                    {
                        SelectedPath = @"C:\ZTree",
                        ShowNewFolderButton = false,
                        Description = "Please select the folder where ZTreeWin is installed"
                    };
                    if (Dialog.ShowDialog() == System.Windows.Forms.DialogResult.OK)
                       Updater.ZTreeHome = Dialog.SelectedPath;
                }
                if (Options.TryGetValue("archive", out var Value))
                    Updater.ArchiveDir = new System.IO.DirectoryInfo(Value);
                else if (Options.TryGetValue("backup", out Value))
                    Updater.BackupDir = new System.IO.DirectoryInfo(Value);

                // Perform the actual update
                int FilesUpdated;
                if (Arguments.Count > 0)
                {
                    FilesUpdated = Updater.UpdateFromFile(Arguments[0]);
                }
                else
                {
                    var UpdateTask = Updater.UpdateAsync();
                    UpdateTask.Wait();
                    FilesUpdated = UpdateTask.Result;
                }
                WriteLog($"{FilesUpdated} files updated.", TraceLevel.Info);
                return FilesUpdated > 0 ? 0 : 1;
            }
            catch (Exception ex)
            {








                var BackupColor = Console.ForegroundColor;
                Console.ForegroundColor = ConsoleColor.Magenta;
                if (ex is AggregateException)
                    ((AggregateException)ex).Handle((iex) =>
                    {
                        Console.Error.WriteLine(iex.Message);
                        return true;
                    });
                else
                    Console.Error.WriteLine(ex.Message);
                Console.ForegroundColor = BackupColor;
                return 255;
            }
        }

        private static void ShowHelp()
        {
            string Name = Path.GetFileNameWithoutExtension(typeof(Program).Assembly.Location);
            var Lines = new List<string>
            {
                $@"{Name} [/v | /verbose] [/home ZTHOMEDIR] [/backup BACKUPDIR] [/archive ARCHIVEDIR] [UPDATEZIPFILE]",
                "",
                $@"/verbose             Display more messages.",
                $@"/home ZTHOMEDIR      ZTreeWin's installation directory. If not specified, {Name} attempts to determine it automatically. Failing that, it must be browsed interactively.",
                $@"/backup BACKUPDIR    Where to save backups of files replaced during an update. Default is %#ZTHome%\Backup.",







|


|

|


















>
>
>
>
>
>
>
>



|
<
<
<
<

|







|







58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100




101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
                    var Dialog = new System.Windows.Forms.FolderBrowserDialog
                    {
                        SelectedPath = @"C:\ZTree",
                        ShowNewFolderButton = false,
                        Description = "Please select the folder where ZTreeWin is installed"
                    };
                    if (Dialog.ShowDialog() == System.Windows.Forms.DialogResult.OK)
                        Updater.ZTreeHome = Dialog.SelectedPath;
                }
                if (Options.TryGetValue("archive", out var Value))
                    Updater.ArchiveDir = new DirectoryInfo(Value);
                else if (Options.TryGetValue("backup", out Value))
                    Updater.BackupDir = new DirectoryInfo(Value);

                // Perform the actual update
                int FilesUpdated;
                if (Arguments.Count > 0)
                {
                    FilesUpdated = Updater.UpdateFromFile(Arguments[0]);
                }
                else
                {
                    var UpdateTask = Updater.UpdateAsync();
                    UpdateTask.Wait();
                    FilesUpdated = UpdateTask.Result;
                }
                WriteLog($"{FilesUpdated} files updated.", TraceLevel.Info);
                return FilesUpdated > 0 ? 0 : 1;
            }
            catch (Exception ex)
            {
                Func<Exception,bool> WriteException = (iex) =>
                {
                    if (MinimumLevel >= TraceLevel.Verbose)
                        Console.Error.WriteLine(iex.ToString());
                    else
                        Console.Error.WriteLine(iex.Message);
                    return true;
                };
                var BackupColor = Console.ForegroundColor;
                Console.ForegroundColor = ConsoleColor.Magenta;
                if (ex is AggregateException)
                    ((AggregateException)ex).Handle(WriteException);




                else
                    WriteException(ex);
                Console.ForegroundColor = BackupColor;
                return 255;
            }
        }

        private static void ShowHelp()
        {
            string Name = Path.GetFileNameWithoutExtension(System.Reflection.Assembly.GetEntryAssembly().Location);
            var Lines = new List<string>
            {
                $@"{Name} [/v | /verbose] [/home ZTHOMEDIR] [/backup BACKUPDIR] [/archive ARCHIVEDIR] [UPDATEZIPFILE]",
                "",
                $@"/verbose             Display more messages.",
                $@"/home ZTHOMEDIR      ZTreeWin's installation directory. If not specified, {Name} attempts to determine it automatically. Failing that, it must be browsed interactively.",
                $@"/backup BACKUPDIR    Where to save backups of files replaced during an update. Default is %#ZTHome%\Backup.",
145
146
147
148
149
150
151

152
153
154
155
156
157
158
                    }
                    else
                    {
                        // TODO: break on word boundaries, and omit spaces at the start of a new line?
                        Console.Write(Description.Extract(0, Console.BufferWidth - TabPos));
                        while (Description.Length > 0)
                        {

                            Console.Write(Prefix);
                            Console.Write(Description.Extract(0, Console.BufferWidth - TabPos));
                        }
                        Console.WriteLine();
                    }
                }
            }







>







149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
                    }
                    else
                    {
                        // TODO: break on word boundaries, and omit spaces at the start of a new line?
                        Console.Write(Description.Extract(0, Console.BufferWidth - TabPos));
                        while (Description.Length > 0)
                        {
                            Console.WriteLine();
                            Console.Write(Prefix);
                            Console.Write(Description.Extract(0, Console.BufferWidth - TabPos));
                        }
                        Console.WriteLine();
                    }
                }
            }
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
        {
            if (_levelColors.Count == 0)
            {
                _levelColors.Add(TraceLevel.Verbose, ConsoleColor.DarkGray);
                _levelColors.Add(TraceLevel.Warning, ConsoleColor.Yellow);
                _levelColors.Add(TraceLevel.Error, ConsoleColor.Red);
            }
            var Output = level >= TraceLevel.Warning ? Console.Error : Console.Out;
            var BackupColor = Console.ForegroundColor;
            if (_levelColors.TryGetValue(level, out var Color))
                Console.ForegroundColor = Color;
            Output.WriteLine(message);
            Console.ForegroundColor = BackupColor;
        }








|







180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
        {
            if (_levelColors.Count == 0)
            {
                _levelColors.Add(TraceLevel.Verbose, ConsoleColor.DarkGray);
                _levelColors.Add(TraceLevel.Warning, ConsoleColor.Yellow);
                _levelColors.Add(TraceLevel.Error, ConsoleColor.Red);
            }
            var Output = level >= TraceLevel.Warning ? Console.Out : Console.Error;
            var BackupColor = Console.ForegroundColor;
            if (_levelColors.TryGetValue(level, out var Color))
                Console.ForegroundColor = Color;
            Output.WriteLine(message);
            Console.ForegroundColor = BackupColor;
        }

Changes to ZTUpdater/ZTUpdater.cs.
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
        {
            get => _ZTreeHome;
            set
            {
                _ZTreeHome = value;
                if (_ZTreeHome != null)
                {
                    var Executables = Directory.GetFiles(ZTreeHome, Executable);
                    if (Executables.Length > 0)
                        Executable = Executables[0];
                    else
                        Executable = Path.Combine(ZTreeHome, Executable);

                    // determine version of local executable
                    CurrentVersion = FileVersionInfo.GetVersionInfo(Executable).FileVersion;







|







37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
        {
            get => _ZTreeHome;
            set
            {
                _ZTreeHome = value;
                if (_ZTreeHome != null)
                {
                    string[] Executables = Directory.GetFiles(ZTreeHome, Executable);
                    if (Executables.Length > 0)
                        Executable = Executables[0];
                    else
                        Executable = Path.Combine(ZTreeHome, Executable);

                    // determine version of local executable
                    CurrentVersion = FileVersionInfo.GetVersionInfo(Executable).FileVersion;
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105

            // from the environment
            string ZTHome = Environment.GetEnvironmentVariable("#ZTHome");
            if (Directory.Exists(ZTHome))
                return ZTHome;

            // from registry (both Win32 and Win64)
            var InstallDir = Registry.GetValue(@"HKEY_LOCAL_MACHINE\SOFTWARE\ZTreeWin", "Install_Dir", null);
            if (InstallDir is string && Directory.Exists(ZTHome = (string)InstallDir))
                return ZTHome;
            InstallDir = Registry.GetValue(@"HKEY_LOCAL_MACHINE\SOFTWARE\WOW6432Node\ZTreeWin", "Install_Dir", null);
            if (InstallDir is string && Directory.Exists(ZTHome = (string)InstallDir))
                return ZTHome;

            return null;
        }

        private Regex _rexZetaAvailable = new Regex(@"forum_entry.php\?id=(\d+)""[^\<]*?v(\d+\.\S+) Now Available", RegexOptions.Compiled | RegexOptions.IgnoreCase);
        private Regex _rexWhatsNew = new Regex(@"<div class=""posting"">(.*?)</div><p class=""signature"">", RegexOptions.Compiled | RegexOptions.IgnoreCase | RegexOptions.Singleline);
        private Regex _rexLink = new Regex(@"""(https?://www.z(edtek|tree).com\/[^""]*\.(zip|exe))""", RegexOptions.Compiled | RegexOptions.IgnoreCase);

        public async Task<Uri> GetUriLatestVersion()
        {
            using (var Client = new HttpClient())
            {
                // get the forum home page
                Log("Looking for announcement on ZTreeWin Forum...");







|











|







79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105

            // from the environment
            string ZTHome = Environment.GetEnvironmentVariable("#ZTHome");
            if (Directory.Exists(ZTHome))
                return ZTHome;

            // from registry (both Win32 and Win64)
            object InstallDir = Registry.GetValue(@"HKEY_LOCAL_MACHINE\SOFTWARE\ZTreeWin", "Install_Dir", null);
            if (InstallDir is string && Directory.Exists(ZTHome = (string)InstallDir))
                return ZTHome;
            InstallDir = Registry.GetValue(@"HKEY_LOCAL_MACHINE\SOFTWARE\WOW6432Node\ZTreeWin", "Install_Dir", null);
            if (InstallDir is string && Directory.Exists(ZTHome = (string)InstallDir))
                return ZTHome;

            return null;
        }

        private Regex _rexZetaAvailable = new Regex(@"forum_entry.php\?id=(\d+)""[^\<]*?v(\d+\.\S+) Now Available", RegexOptions.Compiled | RegexOptions.IgnoreCase);
        private Regex _rexWhatsNew = new Regex(@"<div class=""posting"">(.*?)</div><p class=""signature"">", RegexOptions.Compiled | RegexOptions.IgnoreCase | RegexOptions.Singleline);
        private Regex _rexLink = new Regex(@"""(https?://(www.)?z(edtek|tree).com\/[^""]*\.(zip|exe))""", RegexOptions.Compiled | RegexOptions.IgnoreCase);

        public async Task<Uri> GetUriLatestVersion()
        {
            using (var Client = new HttpClient())
            {
                // get the forum home page
                Log("Looking for announcement on ZTreeWin Forum...");
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182

        /// <summary>
        /// Checks for an updated version of ZTreeWin; and if found, download and install it.
        /// </summary>
        /// <returns>The number of files that were updated.</returns>
        public async Task<int> UpdateAsync()
        {
            Log($"ZTreeWin version {CurrentVersion} located in \"{ZTreeHome}\".", TraceLevel.Info);
            if (!Directory.Exists(ZTreeHome))
                throw new DirectoryNotFoundException($"Directory \"{ZTreeHome}\" not found");

            var DownloadUri = await GetUriLatestVersion();
            if (DownloadUri == null)
                return 0;

            // Prepare to save the file in the user’s Downloads folder
            string Folder = (ArchiveDir ?? BackupDir)?.FullName ?? KnownFolders.Downloads.Path;
            string OrgZipFile = DownloadUri.AbsolutePath;
            int Pos = OrgZipFile.LastIndexOf("/");







|

|

|







164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182

        /// <summary>
        /// Checks for an updated version of ZTreeWin; and if found, download and install it.
        /// </summary>
        /// <returns>The number of files that were updated.</returns>
        public async Task<int> UpdateAsync()
        {
            Log($@"ZTreeWin version {CurrentVersion} located in ""{ZTreeHome}"".", TraceLevel.Info);
            if (!Directory.Exists(ZTreeHome))
                throw new DirectoryNotFoundException($@"Directory ""{ZTreeHome}"" not found");

            Uri DownloadUri = await GetUriLatestVersion();
            if (DownloadUri == null)
                return 0;

            // Prepare to save the file in the user’s Downloads folder
            string Folder = (ArchiveDir ?? BackupDir)?.FullName ?? KnownFolders.Downloads.Path;
            string OrgZipFile = DownloadUri.AbsolutePath;
            int Pos = OrgZipFile.LastIndexOf("/");
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
                Counter++;
                ZipFile = Base + $" ({Counter})" + Extension;
            }

            Log($@"Downloading update file to ""{OrgZipFile}""...");
            using (var Client = new HttpClient())
            {
                var ZipStream = await Client.GetStreamAsync(DownloadUri);
                using (var FileStream = new FileStream(ZipFile, FileMode.CreateNew, FileAccess.Write, FileShare.Read))
                {
                    await ZipStream.CopyToAsync(FileStream);
                }
            }

            // If the newly downloaded file is identical to the existing one, delete the new one and just reuse the old one.







|







193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
                Counter++;
                ZipFile = Base + $" ({Counter})" + Extension;
            }

            Log($@"Downloading update file to ""{OrgZipFile}""...");
            using (var Client = new HttpClient())
            {
                Stream ZipStream = await Client.GetStreamAsync(DownloadUri);
                using (var FileStream = new FileStream(ZipFile, FileMode.CreateNew, FileAccess.Write, FileShare.Read))
                {
                    await ZipStream.CopyToAsync(FileStream);
                }
            }

            // If the newly downloaded file is identical to the existing one, delete the new one and just reuse the old one.
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
                        }

                        // Try moving to version-based folder first; only if that doesn't work, try to rename
                        //  to a version-based file name in the same folder.
                        Log($"\t{Entry.FullName}\tbacking up existing file.");
                        try
                        {
                            var BackupName = VersionBasedFolder(TargetFile);
                            try
                            {
                                File.Move(TargetFile.FullName, BackupName);
                                TargetFile.Refresh();
                                if (TargetFile.Exists)
                                    // Then, the move was NOT successful
                                    throw new Exception("Unable to move the file.");







|







248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
                        }

                        // Try moving to version-based folder first; only if that doesn't work, try to rename
                        //  to a version-based file name in the same folder.
                        Log($"\t{Entry.FullName}\tbacking up existing file.");
                        try
                        {
                            string BackupName = VersionBasedFolder(TargetFile);
                            try
                            {
                                File.Move(TargetFile.FullName, BackupName);
                                TargetFile.Refresh();
                                if (TargetFile.Exists)
                                    // Then, the move was NOT successful
                                    throw new Exception("Unable to move the file.");
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
            }
            return NumFiles;
        }

        private string VersionBasedFolder(FileInfo File)
        {
            var SourceDir = new DirectoryInfo(ZTreeHome);
            var TargetDir = BackupDir ?? SourceDir.CreateSubdirectory($@"Backup");
            TargetDir = TargetDir.CreateSubdirectory(CurrentVersion);
            return Path.Combine(TargetDir.FullName, File.FullName.Substring(SourceDir.FullName.Length + 1));
        }

        private string VersionBasedFilename(FileInfo File)
        {
            var Extension = File.Extension;







|







281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
            }
            return NumFiles;
        }

        private string VersionBasedFolder(FileInfo File)
        {
            var SourceDir = new DirectoryInfo(ZTreeHome);
            var TargetDir = BackupDir ?? SourceDir.CreateSubdirectory("Backup");
            TargetDir = TargetDir.CreateSubdirectory(CurrentVersion);
            return Path.Combine(TargetDir.FullName, File.FullName.Substring(SourceDir.FullName.Length + 1));
        }

        private string VersionBasedFilename(FileInfo File)
        {
            var Extension = File.Extension;
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362

            // decode all HTML entities
            var rexEntity = new Regex(@"&(#x?[0-9a-f]{1,4}|\w{2,8});", RegexOptions.Compiled | RegexOptions.IgnoreCase);
            Match Match;
            int StartAt = 0;
            while ((Match = rexEntity.Match(Text, StartAt)).Success)
            {
                var Entity = Match.Groups[1].Value;
                var Replacement = "?";
                if (Entity.StartsWith("#"))
                {
                    int CharCode = 0;
                    if (Entity.Substring(1, 1) == "x")
                        // hexadecimal char code
                        CharCode = int.Parse(Entity.Substring(2), System.Globalization.NumberStyles.HexNumber);
                    else







|
|







347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362

            // decode all HTML entities
            var rexEntity = new Regex(@"&(#x?[0-9a-f]{1,4}|\w{2,8});", RegexOptions.Compiled | RegexOptions.IgnoreCase);
            Match Match;
            int StartAt = 0;
            while ((Match = rexEntity.Match(Text, StartAt)).Success)
            {
                string Entity = Match.Groups[1].Value;
                string Replacement = "?";
                if (Entity.StartsWith("#"))
                {
                    int CharCode = 0;
                    if (Entity.Substring(1, 1) == "x")
                        // hexadecimal char code
                        CharCode = int.Parse(Entity.Substring(2), System.Globalization.NumberStyles.HexNumber);
                    else