diff --git a/src/Common/CsprojReader.cs b/src/Common/CsprojReader.cs new file mode 100644 index 0000000..f21c084 --- /dev/null +++ b/src/Common/CsprojReader.cs @@ -0,0 +1,200 @@ +using System; +using System.Diagnostics; +using System.IO; +using System.Linq; +using System.Xml.Linq; + +namespace GeneralUpdate.Tool.Avalonia.Common; + +/// +/// Utility class for reading .csproj files +/// +public static class CsprojReader +{ + /// + /// Read MainAppName from .csproj file + /// + public static string ReadMainAppName(string releaseDirectory) + { + try + { + var csprojFile = FindCsprojFile(releaseDirectory); + if (string.IsNullOrEmpty(csprojFile)) + return string.Empty; + + var doc = XDocument.Load(csprojFile); + var outputType = GetElementValue(doc, "OutputType"); + + // Check if OutputType contains WinExe/Exe (case-insensitive) + if (string.IsNullOrEmpty(outputType) || + (!outputType.Equals("WinExe", StringComparison.OrdinalIgnoreCase) && + !outputType.Equals("Exe", StringComparison.OrdinalIgnoreCase))) + { + return string.Empty; + } + + // Extract .csproj filename without extension + var projectName = Path.GetFileNameWithoutExtension(csprojFile); + + // Search for matching .exe file recursively + var exeFile = FindExeFile(releaseDirectory, projectName); + if (!string.IsNullOrEmpty(exeFile)) + { + return Path.GetFileNameWithoutExtension(exeFile); + } + + // Fallback to AssemblyName or OutputName + var assemblyName = GetElementValue(doc, "AssemblyName"); + if (!string.IsNullOrEmpty(assemblyName)) + return assemblyName; + + var outputName = GetElementValue(doc, "OutputName"); + if (!string.IsNullOrEmpty(outputName)) + return outputName; + + return string.Empty; + } + catch (Exception ex) + { + Trace.WriteLine($"Error reading MainAppName: {ex.Message}"); + return string.Empty; + } + } + + /// + /// Read ClientVersion from .csproj file or .exe file version + /// + public static string ReadClientVersion(string releaseDirectory) + { + try + { + var csprojFile = FindCsprojFile(releaseDirectory); + if (string.IsNullOrEmpty(csprojFile)) + return string.Empty; + + var doc = XDocument.Load(csprojFile); + + // Try to read Version tag + var version = GetElementValue(doc, "Version"); + if (!string.IsNullOrEmpty(version)) + return version; + + // Fallback to .exe file version + var projectName = Path.GetFileNameWithoutExtension(csprojFile); + var exeFile = FindExeFile(releaseDirectory, projectName); + + if (!string.IsNullOrEmpty(exeFile) && File.Exists(exeFile)) + { + var versionInfo = FileVersionInfo.GetVersionInfo(exeFile); + if (!string.IsNullOrEmpty(versionInfo.FileVersion)) + return versionInfo.FileVersion; + } + + return string.Empty; + } + catch (Exception ex) + { + Trace.WriteLine($"Error reading ClientVersion: {ex.Message}"); + return string.Empty; + } + } + + /// + /// Read OutputPath from .csproj file + /// + public static string ReadOutputPath(string releaseDirectory) + { + try + { + var csprojFile = FindCsprojFile(releaseDirectory); + if (string.IsNullOrEmpty(csprojFile)) + return string.Empty; + + var doc = XDocument.Load(csprojFile); + var outputPath = GetElementValue(doc, "OutputPath"); + + return outputPath ?? string.Empty; + } + catch (Exception ex) + { + Trace.WriteLine($"Error reading OutputPath: {ex.Message}"); + return string.Empty; + } + } + + /// + /// Find .csproj file in the directory + /// + private static string FindCsprojFile(string directory) + { + if (!Directory.Exists(directory)) + return string.Empty; + + var csprojFiles = Directory.GetFiles(directory, "*.csproj", SearchOption.TopDirectoryOnly); + + if (csprojFiles.Length == 0) + return string.Empty; + + if (csprojFiles.Length > 1) + { + Trace.WriteLine($"Warning: Multiple .csproj files found in {directory}. Using the first one: {csprojFiles[0]}"); + } + + return csprojFiles[0]; + } + + /// + /// Find .exe file with matching name recursively + /// Note: Uses SearchOption.AllDirectories which may be slow for large directory trees. + /// This is acceptable as release directories are typically small. + /// + private static string FindExeFile(string directory, string baseName) + { + if (!Directory.Exists(directory)) + return string.Empty; + + try + { + // First try to find .exe file (Windows) + var exeFiles = Directory.GetFiles(directory, $"{baseName}.exe", SearchOption.AllDirectories); + if (exeFiles.Any()) + return exeFiles.First(); + + // Then try to find executable without extension (Linux/Mac) + var allFiles = Directory.GetFiles(directory, baseName, SearchOption.AllDirectories); + foreach (var file in allFiles) + { + var fileInfo = new FileInfo(file); + // Check if file is executable (on Unix systems) or if it's an exact match + if (fileInfo.Name == baseName) + return file; + } + + return string.Empty; + } + catch (Exception ex) + { + Trace.WriteLine($"Error searching for exe file: {ex.Message}"); + return string.Empty; + } + } + + /// + /// Get element value from XDocument + /// + private static string GetElementValue(XDocument doc, string elementName) + { + try + { + // Search in all PropertyGroup elements + var elements = doc.Descendants() + .Where(e => e.Name.LocalName == elementName); + + return elements.FirstOrDefault()?.Value?.Trim() ?? string.Empty; + } + catch + { + return string.Empty; + } + } +} diff --git a/src/Models/PacketConfigModel.cs b/src/Models/PacketConfigModel.cs index 33ffb58..3333195 100644 --- a/src/Models/PacketConfigModel.cs +++ b/src/Models/PacketConfigModel.cs @@ -5,6 +5,7 @@ namespace GeneralUpdate.Tool.Avalonia.Models; public class PacketConfigModel : ObservableObject { private string _appDirectory, _releaseDirectory, _patchDirectory, _name, _path, _driverDirectory; + private string _reportUrl, _updateUrl, _appName, _mainAppName, _clientVersion; private PlatformModel _platform; private FormatModel _format; private EncodingModel _encoding; @@ -109,4 +110,69 @@ public string DriverDirectory OnPropertyChanged(nameof(DriverDirectory)); } } + + /// + /// 报告地址 + /// + public string ReportUrl + { + get => _reportUrl; + set + { + _reportUrl = value; + OnPropertyChanged(nameof(ReportUrl)); + } + } + + /// + /// 更新地址 + /// + public string UpdateUrl + { + get => _updateUrl; + set + { + _updateUrl = value; + OnPropertyChanged(nameof(UpdateUrl)); + } + } + + /// + /// 应用程序名称 + /// + public string AppName + { + get => _appName; + set + { + _appName = value; + OnPropertyChanged(nameof(AppName)); + } + } + + /// + /// 主应用程序名称 + /// + public string MainAppName + { + get => _mainAppName; + set + { + _mainAppName = value; + OnPropertyChanged(nameof(MainAppName)); + } + } + + /// + /// 客户端版本 + /// + public string ClientVersion + { + get => _clientVersion; + set + { + _clientVersion = value; + OnPropertyChanged(nameof(ClientVersion)); + } + } } \ No newline at end of file diff --git a/src/ViewModels/PacketViewModel.cs b/src/ViewModels/PacketViewModel.cs index 762d1bb..fc50e9e 100644 --- a/src/ViewModels/PacketViewModel.cs +++ b/src/ViewModels/PacketViewModel.cs @@ -9,8 +9,11 @@ using CommunityToolkit.Mvvm.ComponentModel; using CommunityToolkit.Mvvm.Input; using GeneralUpdate.Common.Compress; +using GeneralUpdate.Common.Shared.Object; using GeneralUpdate.Differential; +using GeneralUpdate.Tool.Avalonia.Common; using GeneralUpdate.Tool.Avalonia.Models; +using Newtonsoft.Json; using Nlnet.Avalonia.Controls; namespace GeneralUpdate.Tool.Avalonia.ViewModels; @@ -94,6 +97,11 @@ private void ResetAction() ConfigModel.AppDirectory = GetPlatformSpecificPath(); ConfigModel.PatchDirectory = GetPlatformSpecificPath(); ConfigModel.DriverDirectory = string.Empty; + ConfigModel.ReportUrl = string.Empty; + ConfigModel.UpdateUrl = string.Empty; + ConfigModel.AppName = string.Empty; + ConfigModel.MainAppName = string.Empty; + ConfigModel.ClientVersion = string.Empty; ConfigModel.Encoding = Encodings.First(); ConfigModel.Format = Formats.First(); } @@ -142,6 +150,13 @@ private async Task BuildPacketAction() { try { + // Validate required fields + if (!await ValidateRequiredFields()) + return; + + // Read configuration from .csproj + ReadProjectConfiguration(); + await DifferentialCore.Instance.Clean(ConfigModel.AppDirectory, ConfigModel.ReleaseDirectory, ConfigModel.PatchDirectory); @@ -165,6 +180,9 @@ await DifferentialCore.Instance.Clean(ConfigModel.AppDirectory, } } + // Create and save ConfigInfo JSON file + var configInfoPath = await CreateConfigInfoFile(); + var directoryInfo = new DirectoryInfo(ConfigModel.PatchDirectory); var parentDirectory = directoryInfo.Parent!.FullName; var operationType = ConfigModel.Format.Value; @@ -255,4 +273,91 @@ private void CopyDriverFiles(string sourceDir, string targetDir) CopyDriverFiles(dir, destDir); } } + + /// + /// Validate required fields + /// + private async Task ValidateRequiredFields() + { + var errors = new System.Collections.Generic.List(); + + if (string.IsNullOrWhiteSpace(ConfigModel.UpdateUrl)) + errors.Add("UpdateUrl"); + + if (string.IsNullOrWhiteSpace(ConfigModel.ReportUrl)) + errors.Add("ReportUrl"); + + if (string.IsNullOrWhiteSpace(ConfigModel.AppDirectory)) + errors.Add("AppDirectory"); + + if (string.IsNullOrWhiteSpace(ConfigModel.ReleaseDirectory)) + errors.Add("ReleaseDirectory"); + + if (string.IsNullOrWhiteSpace(ConfigModel.PatchDirectory)) + errors.Add("PatchDirectory"); + + if (errors.Any()) + { + var message = $"The following required fields must be filled:\n{string.Join(", ", errors)}"; + await MessageBox.ShowAsync(message, "Validation Error", Buttons.OK); + return false; + } + + return true; + } + + /// + /// Read project configuration from .csproj file + /// + private void ReadProjectConfiguration() + { + try + { + // Read MainAppName + ConfigModel.MainAppName = CsprojReader.ReadMainAppName(ConfigModel.ReleaseDirectory); + + // Read ClientVersion + ConfigModel.ClientVersion = CsprojReader.ReadClientVersion(ConfigModel.ReleaseDirectory); + + // Set AppName to MainAppName if MainAppName is not empty + if (!string.IsNullOrEmpty(ConfigModel.MainAppName)) + { + ConfigModel.AppName = ConfigModel.MainAppName; + } + } + catch (Exception ex) + { + Trace.WriteLine($"Error reading project configuration: {ex.Message}"); + } + } + + /// + /// Create Configinfo JSON file in patch directory + /// + private async Task CreateConfigInfoFile() + { + try + { + var configInfo = new Configinfo + { + ReportUrl = ConfigModel.ReportUrl, + UpdateUrl = ConfigModel.UpdateUrl, + AppName = ConfigModel.AppName, + MainAppName = ConfigModel.MainAppName, + ClientVersion = ConfigModel.ClientVersion + }; + + var json = JsonConvert.SerializeObject(configInfo, Formatting.Indented); + var configFilePath = Path.Combine(ConfigModel.PatchDirectory, "config.json"); + + await File.WriteAllTextAsync(configFilePath, json, Encoding.UTF8); + + return configFilePath; + } + catch (Exception ex) + { + Trace.WriteLine($"Error creating config info file: {ex.Message}"); + throw; + } + } } \ No newline at end of file diff --git a/src/Views/PacketView.axaml b/src/Views/PacketView.axaml index 76c3c24..8f700d1 100644 --- a/src/Views/PacketView.axaml +++ b/src/Views/PacketView.axaml @@ -14,123 +14,221 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +