welsonjs/WelsonJS.Toolkit/WelsonJS.Launcher/Program.cs
Namhyeon, Go 2e92255a3c Use 'using' statement for InstancesForm disposal
Replaces manual disposal of InstancesForm with a 'using' statement to ensure proper resource management and exception safety when recording to the metadata database.
2025-11-20 17:31:50 +09:00

440 lines
15 KiB
C#

// Program.cs
// SPDX-License-Identifier: GPL-3.0-or-later
// SPDX-FileCopyrightText: 2025 Catswords OSS and WelsonJS Contributors
// https://github.com/gnh1201/welsonjs
//
using System;
using System.Collections.Generic;
using System.Configuration;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Threading;
using System.Windows.Forms;
namespace WelsonJS.Launcher
{
internal static class Program
{
private static readonly ICompatibleLogger _logger;
public static Mutex _mutex;
public static ResourceServer _resourceServer;
public static string _dateTimeFormat;
static Program()
{
// get the date time format
_dateTimeFormat = GetAppConfig("DateTimeFormat");
// set up logger
_logger = new TraceLogger();
// load native libraries
string appDataSubDirectory = "WelsonJS";
bool requireSigned = string.Equals(
GetAppConfig("NativeRequireSigned"),
"true",
StringComparison.OrdinalIgnoreCase);
NativeBootstrap.Init(
dllNames: new[] { "ChakraCore.dll" },
appDataSubdirectory: appDataSubDirectory,
logger: _logger,
requireSigned: requireSigned
);
}
[STAThread]
static void Main(string[] args)
{
// if set the target file path
string targetFilePath = GetTargetFilePath(args);
if (!string.IsNullOrEmpty(targetFilePath))
{
try {
HandleTargetFilePath(targetFilePath);
}
catch (Exception e)
{
_logger.Error($"Initialization failed: {e}");
}
return;
}
// create the mutex
_mutex = new Mutex(true, "WelsonJS.Launcher", out bool createdNew);
if (!createdNew)
{
_logger.Info("WelsonJS Launcher already running.");
return;
}
// draw the main form
Application.EnableVisualStyles();
Application.SetCompatibleTextRenderingDefault(false);
Application.Run(new MainForm(_logger));
// release the mutex
try
{
_mutex.ReleaseMutex();
}
catch { /* ignore if not owned */ }
_mutex.Dispose();
}
public static void RecordFirstDeployTime(string directory, string instanceId)
{
// get current time
DateTime now = DateTime.Now;
// record to the metadata database
using (InstancesForm instancesForm = new InstancesForm())
{
try
{
instancesForm.GetDatabaseInstance().Insert(new Dictionary<string, object>
{
["InstanceId"] = instanceId,
["FirstDeployTime"] = now
}, out _);
}
catch (Exception ex)
{
_logger.Error($"Failed to record first deploy time: {ex.Message}");
}
}
// record to the instance directory
try
{
string filePath = Path.Combine(directory, ".welsonjs_first_deploy_time");
string text = now.ToString(_dateTimeFormat);
File.WriteAllText(filePath, text);
}
catch (Exception ex)
{
_logger.Error($"Failed to record first deploy time: {ex.Message}");
}
}
private static string GetTargetFilePath(string[] args)
{
if (args == null || args.Length == 0) return null;
for (int i = 0; i < args.Length; i++)
{
string token = args[i];
if (string.Equals(token, "--file", StringComparison.OrdinalIgnoreCase) ||
string.Equals(token, "/file", StringComparison.OrdinalIgnoreCase))
{
if (i + 1 < args.Length)
{
return args[i + 1];
}
}
if (token.StartsWith("--file=", StringComparison.OrdinalIgnoreCase))
{
return token.Substring("--file=".Length).Trim('"');
}
}
return null;
}
private static void HandleTargetFilePath(string filePath)
{
string fileExtension = Path.GetExtension(filePath);
if (String.IsNullOrEmpty(fileExtension))
{
throw new ArgumentException("The file extension is null or empty");
}
if (fileExtension.Equals(".zip", StringComparison.OrdinalIgnoreCase))
{
throw new NotImplementedException("Not implemented yet.");
}
if (fileExtension.Equals(".js", StringComparison.OrdinalIgnoreCase))
{
string instanceId = Guid.NewGuid().ToString();
string workingDirectory = CreateInstanceDirectory(instanceId);
string appRoot = GetAppRootDirectory();
string appBaseSource = Path.Combine(appRoot, "app.js");
if (!File.Exists(appBaseSource))
{
throw new FileNotFoundException("app.js not found in application root.", appBaseSource);
}
string appBaseDestination = Path.Combine(workingDirectory, "app.js");
File.Copy(appBaseSource, appBaseDestination, overwrite: true);
string assetsSource = Path.Combine(appRoot, "app", "assets", "js");
string assetsDestination = Path.Combine(workingDirectory, "app", "assets", "js");
CopyDirectoryRecursive(assetsSource, assetsDestination);
string libSource = Path.Combine(appRoot, "lib");
string libDestination = Path.Combine(workingDirectory, "lib");
CopyDirectoryRecursive(libSource, libDestination);
string entrypointDestination = Path.Combine(workingDirectory, "bootstrap.js");
File.Copy(filePath, entrypointDestination, overwrite: true);
RecordFirstDeployTime(workingDirectory, instanceId);
RunCommandPrompt(
workingDirectory: workingDirectory,
entryFileName: "app.js",
scriptName: "bootstrap",
isConsoleApplication: true,
isInteractiveServiceApplication: false
);
return;
}
throw new NotSupportedException($"Unsupported file type: {fileExtension}");
}
private static string GetAppRootDirectory()
{
string[] candidates = new[]
{
GetAppConfig("AppRootDirectory"),
AppDomain.CurrentDomain.BaseDirectory,
Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.ProgramFilesX86), "WelsonJS"),
Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.ProgramFiles), "WelsonJS"),
};
foreach (string dir in candidates)
{
if (string.IsNullOrEmpty(dir))
continue;
string appJs = Path.Combine(dir, "app.js");
if (File.Exists(appJs))
return dir;
}
throw new FileNotFoundException("Could not locate app.js in any known application root directory.");
}
private static string CreateInstanceDirectory(string instanceId)
{
string workingDirectory = GetWorkingDirectory(instanceId);
try
{
// check if the working directory exists
if (Directory.Exists(workingDirectory))
{
throw new InvalidOperationException("GUID validation failed. Directory already exists.");
}
Directory.CreateDirectory(workingDirectory);
}
catch
{
throw new Exception("Instance Initialization failed");
}
return workingDirectory;
}
private static void CopyDirectoryRecursive(string sourceDir, string destDir)
{
if (!Directory.Exists(sourceDir))
{
throw new DirectoryNotFoundException("Source directory not found: " + sourceDir);
}
Directory.CreateDirectory(destDir);
foreach (var file in Directory.GetFiles(sourceDir, "*", SearchOption.AllDirectories))
{
string normalizedSource = sourceDir.TrimEnd(Path.DirectorySeparatorChar, Path.AltDirectorySeparatorChar);
string relativePath = file.Substring(normalizedSource.Length + 1).TrimStart(
Path.DirectorySeparatorChar,
Path.AltDirectorySeparatorChar
);
string targetPath = Path.Combine(destDir, relativePath);
string targetDir = Path.GetDirectoryName(targetPath);
if (!Directory.Exists(targetDir))
{
Directory.CreateDirectory(targetDir);
}
File.Copy(file, targetPath, overwrite: true);
}
}
public static void RunCommandPrompt(string workingDirectory, string entryFileName, string scriptName, bool isConsoleApplication = false, bool isInteractiveServiceApplication = false)
{
if (!isConsoleApplication)
{
if (!File.Exists(Path.Combine(workingDirectory, entryFileName)))
{
throw new Exception("Not Found: " + entryFileName);
}
}
else
{
if (!Directory.EnumerateFiles(workingDirectory, scriptName + ".*").Any())
{
throw new Exception("Not found matches file: " + scriptName);
}
}
Process process = new Process
{
StartInfo = new ProcessStartInfo("cmd")
{
UseShellExecute = false,
RedirectStandardInput = true,
RedirectStandardOutput = true,
CreateNoWindow = true,
Arguments = "/k",
}
};
process.Start();
StreamWriter input = process.StandardInput;
StreamReader output = process.StandardOutput;
input.WriteLine("pushd " + workingDirectory);
input.WriteLine();
input.Flush();
output.ReadLine();
if (isInteractiveServiceApplication)
{
input.WriteLine($"start cmd /c startInteractiveService.bat");
input.WriteLine();
input.Flush();
output.ReadLine();
}
else if (!isConsoleApplication)
{
input.WriteLine(entryFileName);
input.WriteLine();
input.Flush();
output.ReadLine();
}
else
{
input.WriteLine($"start cmd /c cscript app.js {scriptName}");
input.WriteLine();
input.Flush();
output.ReadLine();
}
input.Close();
process.WaitForExit();
}
public static string GetFinalDirectory(string path)
{
string[] directories = Directory.GetDirectories(path);
while (directories.Length == 1)
{
path = directories[0];
directories = Directory.GetDirectories(path);
}
return path;
}
public static string GetAppDataPath()
{
string path = Path.Combine(
Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData),
"WelsonJS"
);
Directory.CreateDirectory(path);
if (!Directory.Exists(path))
{
throw new IOException("Failed to create directory: " + path);
}
return path;
}
public static string GetWorkingDirectory(string instanceId, bool followSubDirectory = false)
{
string workingDirectory = Path.Combine(GetAppDataPath(), instanceId);
if (followSubDirectory)
{
if (!Directory.Exists(workingDirectory))
{
workingDirectory = Path.Combine(Path.GetTempPath(), instanceId);
}
workingDirectory = GetFinalDirectory(workingDirectory);
}
return workingDirectory;
}
public static void InitializeResourceServer()
{
lock(typeof(Program))
{
if (_resourceServer == null)
{
_resourceServer = new ResourceServer(GetAppConfig("ResourceServerPrefix"), "editor.html", _logger);
}
}
}
public static void OpenWebBrowser(string url)
{
Uri resourceServerUri = new Uri(GetAppConfig("ResourceServerPrefix"));
Uri devToolsUri = new Uri(GetAppConfig("ChromiumDevToolsPrefix"));
string userDataDir = Path.Combine(GetAppDataPath(), "EdgeUserProfile");
string remoteAllowOrigins = $"{resourceServerUri.Scheme}://{resourceServerUri.Host}:{resourceServerUri.Port}";
int remoteDebuggingPort = devToolsUri.Port;
bool isAppMode = string.Equals(
GetAppConfig("ChromiumAppMode"),
"true",
StringComparison.OrdinalIgnoreCase);
string[] arguments = {
isAppMode ? $"\"--app={url}\"" : $"\"{url}\"",
$"--remote-debugging-port={remoteDebuggingPort}",
$"--remote-allow-origins={remoteAllowOrigins}", // for security reason
$"--user-data-dir=\"{userDataDir}\""
};
Process.Start(new ProcessStartInfo
{
FileName = Program.GetAppConfig("ChromiumExecutablePath"),
Arguments = string.Join(" ", arguments),
UseShellExecute = true
});
}
public static string GetAppConfig(string key)
{
string value = ConfigurationManager.AppSettings[key];
if (!string.IsNullOrEmpty(value))
{
return value;
}
value = Properties.Resources.ResourceManager.GetString(key);
if (!string.IsNullOrEmpty(value))
{
return value;
}
return null;
}
}
}