From edd36c33443df2a81162a38d7db96c924b1a73c6 Mon Sep 17 00:00:00 2001 From: "Namhyeon, Go" Date: Sat, 17 May 2025 01:54:51 +0900 Subject: [PATCH 1/6] Remove the dependency `System.IO.Compression.ZipFile` #254 Remove the dependency `System.IO.Compression.ZipFile` It will be fix the potentially license conflct issue from the `System.IO.Compression.ZipFile` package. --- .../WelsonJS.Launcher/MainForm.cs | 25 ++- .../WelsonJS.Launcher.csproj | 9 +- .../WelsonJS.Launcher/ZipExtractor.cs | 188 ++++++++++++++++++ .../WelsonJS.Launcher/packages.config | 4 - 4 files changed, 204 insertions(+), 22 deletions(-) create mode 100644 WelsonJS.Toolkit/WelsonJS.Launcher/ZipExtractor.cs delete mode 100644 WelsonJS.Toolkit/WelsonJS.Launcher/packages.config diff --git a/WelsonJS.Toolkit/WelsonJS.Launcher/MainForm.cs b/WelsonJS.Toolkit/WelsonJS.Launcher/MainForm.cs index dcb3ce2..0a1010b 100644 --- a/WelsonJS.Toolkit/WelsonJS.Launcher/MainForm.cs +++ b/WelsonJS.Toolkit/WelsonJS.Launcher/MainForm.cs @@ -1,7 +1,6 @@ using System; using System.Diagnostics; using System.IO; -using System.IO.Compression; using System.Security.Principal; using System.Threading.Tasks; using System.Windows.Forms; @@ -12,11 +11,14 @@ namespace WelsonJS.Launcher { private string workingDirectory; private string instanceId; - private string entryFileName; + private readonly string entryFileName; private string scriptName; + private readonly ZipExtractor zipExtractor; public MainForm() { + zipExtractor = new ZipExtractor(); + entryFileName = "bootstrap.bat"; InitializeComponent(); @@ -130,17 +132,18 @@ namespace WelsonJS.Launcher Directory.Delete(workingDirectory, true); } - // try to extact ZIP file - ZipFile.ExtractToDirectory(filePath, workingDirectory); + // try to extact ZIP compressed file + if (zipExtractor.Extract(filePath, workingDirectory)) + { + // record the first deploy time + RecordFirstDeployTime(workingDirectory); - // record the first deploy time - RecordFirstDeployTime(workingDirectory); + // follow the sub-directory + workingDirectory = Program.GetWorkingDirectory(instanceId, true); - // follow the sub-directory - workingDirectory = Program.GetWorkingDirectory(instanceId, true); - - // Run the appliction - Program.RunCommandPrompt(workingDirectory, entryFileName, scriptName, cbUseSpecificScript.Checked, cbInteractiveServiceApp.Checked); + // Run the appliction + Program.RunCommandPrompt(workingDirectory, entryFileName, scriptName, cbUseSpecificScript.Checked, cbInteractiveServiceApp.Checked); + } } catch (Exception ex) { diff --git a/WelsonJS.Toolkit/WelsonJS.Launcher/WelsonJS.Launcher.csproj b/WelsonJS.Toolkit/WelsonJS.Launcher/WelsonJS.Launcher.csproj index 21be0a4..9c69d5f 100644 --- a/WelsonJS.Toolkit/WelsonJS.Launcher/WelsonJS.Launcher.csproj +++ b/WelsonJS.Toolkit/WelsonJS.Launcher/WelsonJS.Launcher.csproj @@ -56,17 +56,12 @@ favicon.ico + - - - ..\packages\System.IO.Compression.ZipFile.4.3.0\lib\net46\System.IO.Compression.ZipFile.dll - True - True - @@ -107,6 +102,7 @@ GlobalSettingsForm.cs + EnvForm.cs @@ -125,7 +121,6 @@ GlobalSettingsForm.cs - SettingsSingleFileGenerator Settings.Designer.cs diff --git a/WelsonJS.Toolkit/WelsonJS.Launcher/ZipExtractor.cs b/WelsonJS.Toolkit/WelsonJS.Launcher/ZipExtractor.cs new file mode 100644 index 0000000..3e4df68 --- /dev/null +++ b/WelsonJS.Toolkit/WelsonJS.Launcher/ZipExtractor.cs @@ -0,0 +1,188 @@ +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.IO; +using System.Linq; +using System.Runtime.InteropServices; + +namespace WelsonJS.Launcher +{ + public class ZipExtractor + { + class Extractor + { + public string Name; + public string Path; + public Func ExtractCommand; + } + + private readonly List AvailableExtractors; + + public ZipExtractor() + { + AvailableExtractors = new List{ + new Extractor + { + Name = "7z", + Path = FindExecutable("7z.exe"), + ExtractCommand = (src, dest) => $"x \"{src}\" -o\"{dest}\" -y" + }, + new Extractor + { + Name = "WinRAR", + Path = FindExecutable("rar.exe"), + ExtractCommand = (src, dest) => $"x -o+ \"{src}\" \"{dest}\\\"" + }, + new Extractor + { + Name = "PeaZip", + Path = FindExecutable("peazip.exe"), + ExtractCommand = (src, dest) => $"-ext2simple \"{src}\" \"{dest}\"" + }, + new Extractor + { + Name = "tar (Windows)", + Path = FindExecutable("tar.exe"), + ExtractCommand = (src, dest) => $"-xf \"{src}\" -C \"{dest}\"" + }, + new Extractor + { + Name = "WinZip", + Path = FindExecutable("wzunzip.exe"), + ExtractCommand = (src, dest) => $"-d \"{dest}\" \"{src}\"" + }, + new Extractor + { + Name = "ALZip", + Path = FindExecutable("ALZipcon.exe"), + ExtractCommand = (src, dest) => $"-x \"{src}\" \"{dest}\"" + }, + new Extractor + { + Name = "Bandizip", + Path = FindExecutable("Bandizip.exe"), + ExtractCommand = (src, dest) => $"x -o:\"{dest}\" \"{src}\" -y" + } + }; + } + + public bool Extract(string filePath, string workingDirectory) + { + if (!File.Exists(filePath)) + throw new FileNotFoundException("The specified file does not exist.", filePath); + + if (!filePath.EndsWith(".zip", StringComparison.OrdinalIgnoreCase)) + throw new ArgumentException("The specified file is not a ZIP archive."); + + if (!IsValidFile(filePath)) + throw new InvalidDataException("The specified file is not a valid ZIP archive."); + + Directory.CreateDirectory(workingDirectory); + + var extractor = AvailableExtractors.FirstOrDefault(e => e.Path != null); + if (extractor != null) + { + return RunProcess(extractor.Path, extractor.ExtractCommand(filePath, workingDirectory)); + } + + return ExtractUsingShell(filePath, workingDirectory); + } + + private bool IsValidFile(string filePath) + { + if (!File.Exists(filePath)) + throw new FileNotFoundException("File does not exist.", filePath); + + byte[] signature = new byte[4]; + + using (var fs = File.OpenRead(filePath)) + { + if (fs.Length < 4) + return false; + + int bytesRead = fs.Read(signature, 0, 4); + if (bytesRead < 4) + return false; + } + + return signature.SequenceEqual(new byte[] { 0x50, 0x4B, 0x03, 0x04 }); + } + + private string FindExecutable(string executableFileName) + { + var paths = (Environment.GetEnvironmentVariable("PATH") ?? "").Split(';'); + foreach (var dir in paths) + { + var full = Path.Combine(dir.Trim(), executableFileName); + if (File.Exists(full)) + return full; + } + + // Check common install locations + var programDirs = new[] + { + Environment.GetFolderPath(Environment.SpecialFolder.ProgramFiles), + Environment.GetFolderPath(Environment.SpecialFolder.ProgramFilesX86) + }; + + foreach (var dir in programDirs) + { + try + { + var found = Directory.EnumerateFiles(dir, executableFileName, SearchOption.AllDirectories).FirstOrDefault(); + if (found != null) + return found; + } + catch { } + } + + return null; + } + + private bool RunProcess(string filePath, string arguments) + { + var psi = new ProcessStartInfo + { + FileName = filePath, + Arguments = arguments, + RedirectStandardOutput = true, + RedirectStandardError = true, + UseShellExecute = false, + CreateNoWindow = true + }; + + using (var process = Process.Start(psi)) + { + process.WaitForExit(); + + if (process.ExitCode != 0) + { + string errorOutput = process.StandardError.ReadToEnd(); + throw new InvalidOperationException( + $"Process '{filePath}' exited with code {process.ExitCode}. Error output: {errorOutput}"); + } + } + + return true; + } + + private bool ExtractUsingShell(string filePath, string workingDirectory) + { + var shellAppType = Type.GetTypeFromProgID("Shell.Application"); + dynamic shell = Activator.CreateInstance(shellAppType); + dynamic zip = shell.NameSpace(filePath); + dynamic dest = shell.NameSpace(workingDirectory); + + if (zip == null || dest == null) + return false; + + dest.CopyHere(zip.Items(), 16); + + Marshal.ReleaseComObject(zip); + Marshal.ReleaseComObject(dest); + Marshal.ReleaseComObject(shell); + + return true; + } + } +} diff --git a/WelsonJS.Toolkit/WelsonJS.Launcher/packages.config b/WelsonJS.Toolkit/WelsonJS.Launcher/packages.config deleted file mode 100644 index 888dd5e..0000000 --- a/WelsonJS.Toolkit/WelsonJS.Launcher/packages.config +++ /dev/null @@ -1,4 +0,0 @@ - - - - \ No newline at end of file From d9721bdb5a4821fc4552bebd502ea4744ca4ad8b Mon Sep 17 00:00:00 2001 From: "Namhyeon, Go" Date: Sat, 17 May 2025 02:32:27 +0900 Subject: [PATCH 2/6] Adopt the code review --- .../WelsonJS.Launcher/MainForm.cs | 2 +- .../WelsonJS.Launcher/ZipExtractor.cs | 40 +++++++++---------- 2 files changed, 21 insertions(+), 21 deletions(-) diff --git a/WelsonJS.Toolkit/WelsonJS.Launcher/MainForm.cs b/WelsonJS.Toolkit/WelsonJS.Launcher/MainForm.cs index 0a1010b..d403103 100644 --- a/WelsonJS.Toolkit/WelsonJS.Launcher/MainForm.cs +++ b/WelsonJS.Toolkit/WelsonJS.Launcher/MainForm.cs @@ -141,7 +141,7 @@ namespace WelsonJS.Launcher // follow the sub-directory workingDirectory = Program.GetWorkingDirectory(instanceId, true); - // Run the appliction + // Run the application Program.RunCommandPrompt(workingDirectory, entryFileName, scriptName, cbUseSpecificScript.Checked, cbInteractiveServiceApp.Checked); } } diff --git a/WelsonJS.Toolkit/WelsonJS.Launcher/ZipExtractor.cs b/WelsonJS.Toolkit/WelsonJS.Launcher/ZipExtractor.cs index 3e4df68..7640e8e 100644 --- a/WelsonJS.Toolkit/WelsonJS.Launcher/ZipExtractor.cs +++ b/WelsonJS.Toolkit/WelsonJS.Launcher/ZipExtractor.cs @@ -79,10 +79,12 @@ namespace WelsonJS.Launcher Directory.CreateDirectory(workingDirectory); - var extractor = AvailableExtractors.FirstOrDefault(e => e.Path != null); - if (extractor != null) + foreach (var extractor in AvailableExtractors.Where(e => e.Path != null)) { - return RunProcess(extractor.Path, extractor.ExtractCommand(filePath, workingDirectory)); + if (RunProcess(extractor.Path, extractor.ExtractCommand(filePath, workingDirectory))) + { + return true; + } } return ExtractUsingShell(filePath, workingDirectory); @@ -110,19 +112,20 @@ namespace WelsonJS.Launcher private string FindExecutable(string executableFileName) { - var paths = (Environment.GetEnvironmentVariable("PATH") ?? "").Split(';'); + var paths = (Environment.GetEnvironmentVariable("PATH") ?? "").Split(Path.PathSeparator); foreach (var dir in paths) { - var full = Path.Combine(dir.Trim(), executableFileName); - if (File.Exists(full)) - return full; + var fullpath = Path.Combine(dir.Trim(), executableFileName); + if (File.Exists(fullpath)) + return fullpath; } // Check common install locations var programDirs = new[] { Environment.GetFolderPath(Environment.SpecialFolder.ProgramFiles), - Environment.GetFolderPath(Environment.SpecialFolder.ProgramFilesX86) + Environment.GetFolderPath(Environment.SpecialFolder.ProgramFilesX86), + Path.Combine(Program.GetAppDataPath(), "bin") // find an extractor from APPDATA directory }; foreach (var dir in programDirs) @@ -139,11 +142,11 @@ namespace WelsonJS.Launcher return null; } - private bool RunProcess(string filePath, string arguments) + private bool RunProcess(string executableFilePath, string arguments) { var psi = new ProcessStartInfo { - FileName = filePath, + FileName = executableFilePath, Arguments = arguments, RedirectStandardOutput = true, RedirectStandardError = true, @@ -151,24 +154,21 @@ namespace WelsonJS.Launcher CreateNoWindow = true }; - using (var process = Process.Start(psi)) + using (var process = new Process { StartInfo = psi, EnableRaisingEvents = true }) { + process.Start(); process.WaitForExit(); - - if (process.ExitCode != 0) - { - string errorOutput = process.StandardError.ReadToEnd(); - throw new InvalidOperationException( - $"Process '{filePath}' exited with code {process.ExitCode}. Error output: {errorOutput}"); - } + return process.ExitCode == 0; } - - return true; } private bool ExtractUsingShell(string filePath, string workingDirectory) { var shellAppType = Type.GetTypeFromProgID("Shell.Application"); + + if (shellAppType == null) + return false; + dynamic shell = Activator.CreateInstance(shellAppType); dynamic zip = shell.NameSpace(filePath); dynamic dest = shell.NameSpace(workingDirectory); From 3361fc12708979d17b0fd6bda4f6f7ec7922e408 Mon Sep 17 00:00:00 2001 From: "Namhyeon, Go" Date: Sat, 17 May 2025 02:39:42 +0900 Subject: [PATCH 3/6] Adopt the code review --- WelsonJS.Toolkit/WelsonJS.Launcher/ZipExtractor.cs | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/WelsonJS.Toolkit/WelsonJS.Launcher/ZipExtractor.cs b/WelsonJS.Toolkit/WelsonJS.Launcher/ZipExtractor.cs index 7640e8e..a194fda 100644 --- a/WelsonJS.Toolkit/WelsonJS.Launcher/ZipExtractor.cs +++ b/WelsonJS.Toolkit/WelsonJS.Launcher/ZipExtractor.cs @@ -176,13 +176,19 @@ namespace WelsonJS.Launcher if (zip == null || dest == null) return false; + int expected = zip.Items().Count; dest.CopyHere(zip.Items(), 16); - + + // wait (max 30 s) until all files appear + var sw = Stopwatch.StartNew(); + while (dest.Items().Count < expected && sw.Elapsed < TimeSpan.FromSeconds(30)) + System.Threading.Thread.Sleep(200); + Marshal.ReleaseComObject(zip); Marshal.ReleaseComObject(dest); Marshal.ReleaseComObject(shell); - - return true; + + return dest.Items().Count == expected; } } } From db3f0eae0c54cdff01d522bdaf7c6d2fa0798ff2 Mon Sep 17 00:00:00 2001 From: "Namhyeon, Go" Date: Sat, 17 May 2025 02:43:34 +0900 Subject: [PATCH 4/6] Update WelsonJS.Toolkit/WelsonJS.Launcher/ZipExtractor.cs Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com> --- WelsonJS.Toolkit/WelsonJS.Launcher/ZipExtractor.cs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/WelsonJS.Toolkit/WelsonJS.Launcher/ZipExtractor.cs b/WelsonJS.Toolkit/WelsonJS.Launcher/ZipExtractor.cs index a194fda..bcf60d7 100644 --- a/WelsonJS.Toolkit/WelsonJS.Launcher/ZipExtractor.cs +++ b/WelsonJS.Toolkit/WelsonJS.Launcher/ZipExtractor.cs @@ -170,8 +170,11 @@ namespace WelsonJS.Launcher return false; dynamic shell = Activator.CreateInstance(shellAppType); + if (shell == null) + return false; dynamic zip = shell.NameSpace(filePath); dynamic dest = shell.NameSpace(workingDirectory); + dynamic dest = shell.NameSpace(workingDirectory); if (zip == null || dest == null) return false; From e454c22053a4deb3f8dd28e7af5d079ce1b5c07b Mon Sep 17 00:00:00 2001 From: "Namhyeon, Go" Date: Sat, 17 May 2025 02:46:48 +0900 Subject: [PATCH 5/6] Adopt the code review --- WelsonJS.Toolkit/WelsonJS.Launcher/ZipExtractor.cs | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/WelsonJS.Toolkit/WelsonJS.Launcher/ZipExtractor.cs b/WelsonJS.Toolkit/WelsonJS.Launcher/ZipExtractor.cs index bcf60d7..2b5a48c 100644 --- a/WelsonJS.Toolkit/WelsonJS.Launcher/ZipExtractor.cs +++ b/WelsonJS.Toolkit/WelsonJS.Launcher/ZipExtractor.cs @@ -92,9 +92,6 @@ namespace WelsonJS.Launcher private bool IsValidFile(string filePath) { - if (!File.Exists(filePath)) - throw new FileNotFoundException("File does not exist.", filePath); - byte[] signature = new byte[4]; using (var fs = File.OpenRead(filePath)) @@ -165,17 +162,15 @@ namespace WelsonJS.Launcher private bool ExtractUsingShell(string filePath, string workingDirectory) { var shellAppType = Type.GetTypeFromProgID("Shell.Application"); - if (shellAppType == null) return false; dynamic shell = Activator.CreateInstance(shellAppType); if (shell == null) return false; + dynamic zip = shell.NameSpace(filePath); dynamic dest = shell.NameSpace(workingDirectory); - dynamic dest = shell.NameSpace(workingDirectory); - if (zip == null || dest == null) return false; From 753ef4c6e561c5a4ea62f444749ad5d01f5da808 Mon Sep 17 00:00:00 2001 From: "Namhyeon, Go" Date: Sat, 17 May 2025 03:29:36 +0900 Subject: [PATCH 6/6] Fix performance issue when initial start --- .../WelsonJS.Launcher/ZipExtractor.cs | 66 ++++++++++++------- 1 file changed, 44 insertions(+), 22 deletions(-) diff --git a/WelsonJS.Toolkit/WelsonJS.Launcher/ZipExtractor.cs b/WelsonJS.Toolkit/WelsonJS.Launcher/ZipExtractor.cs index 2b5a48c..2a838bb 100644 --- a/WelsonJS.Toolkit/WelsonJS.Launcher/ZipExtractor.cs +++ b/WelsonJS.Toolkit/WelsonJS.Launcher/ZipExtractor.cs @@ -4,6 +4,7 @@ using System.Diagnostics; using System.IO; using System.Linq; using System.Runtime.InteropServices; +using System.Threading.Tasks; namespace WelsonJS.Launcher { @@ -11,9 +12,10 @@ namespace WelsonJS.Launcher { class Extractor { - public string Name; - public string Path; - public Func ExtractCommand; + public string Name { get; set; } + public string FileName { get; set; } + public string Path { get; set; } + public Func ExtractCommand { get; set; } } private readonly List AvailableExtractors; @@ -24,46 +26,48 @@ namespace WelsonJS.Launcher new Extractor { Name = "7z", - Path = FindExecutable("7z.exe"), + FileName = "7z.exe", ExtractCommand = (src, dest) => $"x \"{src}\" -o\"{dest}\" -y" }, new Extractor { Name = "WinRAR", - Path = FindExecutable("rar.exe"), + FileName = "rar.exe", ExtractCommand = (src, dest) => $"x -o+ \"{src}\" \"{dest}\\\"" }, new Extractor { Name = "PeaZip", - Path = FindExecutable("peazip.exe"), + FileName = "peazip.exe", ExtractCommand = (src, dest) => $"-ext2simple \"{src}\" \"{dest}\"" }, new Extractor { Name = "tar (Windows)", - Path = FindExecutable("tar.exe"), + FileName = "tar.exe", ExtractCommand = (src, dest) => $"-xf \"{src}\" -C \"{dest}\"" }, new Extractor { Name = "WinZip", - Path = FindExecutable("wzunzip.exe"), + FileName = "wzunzip.exe", ExtractCommand = (src, dest) => $"-d \"{dest}\" \"{src}\"" }, new Extractor { Name = "ALZip", - Path = FindExecutable("ALZipcon.exe"), + FileName = "ALZipcon.exe", ExtractCommand = (src, dest) => $"-x \"{src}\" \"{dest}\"" }, new Extractor { Name = "Bandizip", - Path = FindExecutable("Bandizip.exe"), + FileName = "Bandizip.exe", ExtractCommand = (src, dest) => $"x -o:\"{dest}\" \"{src}\" -y" } }; + + Task.Run(() => CheckAvailableExtractors()); } public bool Extract(string filePath, string workingDirectory) @@ -107,36 +111,54 @@ namespace WelsonJS.Launcher return signature.SequenceEqual(new byte[] { 0x50, 0x4B, 0x03, 0x04 }); } - private string FindExecutable(string executableFileName) + private void CheckAvailableExtractors() { + var fileNames = AvailableExtractors.Select(e => e.FileName).ToList(); + + // Check PATH environment variable var paths = (Environment.GetEnvironmentVariable("PATH") ?? "").Split(Path.PathSeparator); foreach (var dir in paths) { - var fullpath = Path.Combine(dir.Trim(), executableFileName); - if (File.Exists(fullpath)) - return fullpath; + foreach (var fileName in fileNames) + { + var path = Path.Combine(dir.Trim(), fileName); + if (File.Exists(path)) + { + var index = fileNames.IndexOf(fileName); + var extractor = AvailableExtractors[index]; + extractor.Path = path; + } + } } // Check common install locations var programDirs = new[] { + Path.Combine(Program.GetAppDataPath(), "bin"), // find an extractor from APPDATA directory Environment.GetFolderPath(Environment.SpecialFolder.ProgramFiles), - Environment.GetFolderPath(Environment.SpecialFolder.ProgramFilesX86), - Path.Combine(Program.GetAppDataPath(), "bin") // find an extractor from APPDATA directory + Environment.GetFolderPath(Environment.SpecialFolder.ProgramFilesX86) }; - foreach (var dir in programDirs) + foreach (var rootDir in programDirs) { + if (!Directory.Exists(rootDir)) + continue; + try { - var found = Directory.EnumerateFiles(dir, executableFileName, SearchOption.AllDirectories).FirstOrDefault(); - if (found != null) - return found; + foreach (var file in Directory.EnumerateFiles(rootDir, "*", SearchOption.AllDirectories)) + { + var fileName = Path.GetFileName(file); + if (fileNames.Contains(fileName)) + { + var index = fileNames.IndexOf(fileName); + var extractor = AvailableExtractors[index]; + extractor.Path = file; + } + } } catch { } } - - return null; } private bool RunProcess(string executableFilePath, string arguments)