Skip to content

Commit 0b72701

Browse files
CopilotJusterZhu
andauthored
Add configuration extraction from .csproj and JSON packaging (#16)
* Initial plan * Add new fields and .csproj reading logic Co-authored-by: JusterZhu <11714536+JusterZhu@users.noreply.github.com> * Fix nullable warnings and improve exe file detection Co-authored-by: JusterZhu <11714536+JusterZhu@users.noreply.github.com> * Address code review feedback Co-authored-by: JusterZhu <11714536+JusterZhu@users.noreply.github.com> * Use existing Configinfo class from GeneralUpdate.Common.Shared.Object Co-authored-by: JusterZhu <11714536+JusterZhu@users.noreply.github.com> * Add visual separators between field categories in PacketView Co-authored-by: JusterZhu <11714536+JusterZhu@users.noreply.github.com> --------- Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com> Co-authored-by: JusterZhu <11714536+JusterZhu@users.noreply.github.com>
1 parent ad6d254 commit 0b72701

4 files changed

Lines changed: 586 additions & 117 deletions

File tree

src/Common/CsprojReader.cs

Lines changed: 200 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,200 @@
1+
using System;
2+
using System.Diagnostics;
3+
using System.IO;
4+
using System.Linq;
5+
using System.Xml.Linq;
6+
7+
namespace GeneralUpdate.Tool.Avalonia.Common;
8+
9+
/// <summary>
10+
/// Utility class for reading .csproj files
11+
/// </summary>
12+
public static class CsprojReader
13+
{
14+
/// <summary>
15+
/// Read MainAppName from .csproj file
16+
/// </summary>
17+
public static string ReadMainAppName(string releaseDirectory)
18+
{
19+
try
20+
{
21+
var csprojFile = FindCsprojFile(releaseDirectory);
22+
if (string.IsNullOrEmpty(csprojFile))
23+
return string.Empty;
24+
25+
var doc = XDocument.Load(csprojFile);
26+
var outputType = GetElementValue(doc, "OutputType");
27+
28+
// Check if OutputType contains WinExe/Exe (case-insensitive)
29+
if (string.IsNullOrEmpty(outputType) ||
30+
(!outputType.Equals("WinExe", StringComparison.OrdinalIgnoreCase) &&
31+
!outputType.Equals("Exe", StringComparison.OrdinalIgnoreCase)))
32+
{
33+
return string.Empty;
34+
}
35+
36+
// Extract .csproj filename without extension
37+
var projectName = Path.GetFileNameWithoutExtension(csprojFile);
38+
39+
// Search for matching .exe file recursively
40+
var exeFile = FindExeFile(releaseDirectory, projectName);
41+
if (!string.IsNullOrEmpty(exeFile))
42+
{
43+
return Path.GetFileNameWithoutExtension(exeFile);
44+
}
45+
46+
// Fallback to AssemblyName or OutputName
47+
var assemblyName = GetElementValue(doc, "AssemblyName");
48+
if (!string.IsNullOrEmpty(assemblyName))
49+
return assemblyName;
50+
51+
var outputName = GetElementValue(doc, "OutputName");
52+
if (!string.IsNullOrEmpty(outputName))
53+
return outputName;
54+
55+
return string.Empty;
56+
}
57+
catch (Exception ex)
58+
{
59+
Trace.WriteLine($"Error reading MainAppName: {ex.Message}");
60+
return string.Empty;
61+
}
62+
}
63+
64+
/// <summary>
65+
/// Read ClientVersion from .csproj file or .exe file version
66+
/// </summary>
67+
public static string ReadClientVersion(string releaseDirectory)
68+
{
69+
try
70+
{
71+
var csprojFile = FindCsprojFile(releaseDirectory);
72+
if (string.IsNullOrEmpty(csprojFile))
73+
return string.Empty;
74+
75+
var doc = XDocument.Load(csprojFile);
76+
77+
// Try to read Version tag
78+
var version = GetElementValue(doc, "Version");
79+
if (!string.IsNullOrEmpty(version))
80+
return version;
81+
82+
// Fallback to .exe file version
83+
var projectName = Path.GetFileNameWithoutExtension(csprojFile);
84+
var exeFile = FindExeFile(releaseDirectory, projectName);
85+
86+
if (!string.IsNullOrEmpty(exeFile) && File.Exists(exeFile))
87+
{
88+
var versionInfo = FileVersionInfo.GetVersionInfo(exeFile);
89+
if (!string.IsNullOrEmpty(versionInfo.FileVersion))
90+
return versionInfo.FileVersion;
91+
}
92+
93+
return string.Empty;
94+
}
95+
catch (Exception ex)
96+
{
97+
Trace.WriteLine($"Error reading ClientVersion: {ex.Message}");
98+
return string.Empty;
99+
}
100+
}
101+
102+
/// <summary>
103+
/// Read OutputPath from .csproj file
104+
/// </summary>
105+
public static string ReadOutputPath(string releaseDirectory)
106+
{
107+
try
108+
{
109+
var csprojFile = FindCsprojFile(releaseDirectory);
110+
if (string.IsNullOrEmpty(csprojFile))
111+
return string.Empty;
112+
113+
var doc = XDocument.Load(csprojFile);
114+
var outputPath = GetElementValue(doc, "OutputPath");
115+
116+
return outputPath ?? string.Empty;
117+
}
118+
catch (Exception ex)
119+
{
120+
Trace.WriteLine($"Error reading OutputPath: {ex.Message}");
121+
return string.Empty;
122+
}
123+
}
124+
125+
/// <summary>
126+
/// Find .csproj file in the directory
127+
/// </summary>
128+
private static string FindCsprojFile(string directory)
129+
{
130+
if (!Directory.Exists(directory))
131+
return string.Empty;
132+
133+
var csprojFiles = Directory.GetFiles(directory, "*.csproj", SearchOption.TopDirectoryOnly);
134+
135+
if (csprojFiles.Length == 0)
136+
return string.Empty;
137+
138+
if (csprojFiles.Length > 1)
139+
{
140+
Trace.WriteLine($"Warning: Multiple .csproj files found in {directory}. Using the first one: {csprojFiles[0]}");
141+
}
142+
143+
return csprojFiles[0];
144+
}
145+
146+
/// <summary>
147+
/// Find .exe file with matching name recursively
148+
/// Note: Uses SearchOption.AllDirectories which may be slow for large directory trees.
149+
/// This is acceptable as release directories are typically small.
150+
/// </summary>
151+
private static string FindExeFile(string directory, string baseName)
152+
{
153+
if (!Directory.Exists(directory))
154+
return string.Empty;
155+
156+
try
157+
{
158+
// First try to find .exe file (Windows)
159+
var exeFiles = Directory.GetFiles(directory, $"{baseName}.exe", SearchOption.AllDirectories);
160+
if (exeFiles.Any())
161+
return exeFiles.First();
162+
163+
// Then try to find executable without extension (Linux/Mac)
164+
var allFiles = Directory.GetFiles(directory, baseName, SearchOption.AllDirectories);
165+
foreach (var file in allFiles)
166+
{
167+
var fileInfo = new FileInfo(file);
168+
// Check if file is executable (on Unix systems) or if it's an exact match
169+
if (fileInfo.Name == baseName)
170+
return file;
171+
}
172+
173+
return string.Empty;
174+
}
175+
catch (Exception ex)
176+
{
177+
Trace.WriteLine($"Error searching for exe file: {ex.Message}");
178+
return string.Empty;
179+
}
180+
}
181+
182+
/// <summary>
183+
/// Get element value from XDocument
184+
/// </summary>
185+
private static string GetElementValue(XDocument doc, string elementName)
186+
{
187+
try
188+
{
189+
// Search in all PropertyGroup elements
190+
var elements = doc.Descendants()
191+
.Where(e => e.Name.LocalName == elementName);
192+
193+
return elements.FirstOrDefault()?.Value?.Trim() ?? string.Empty;
194+
}
195+
catch
196+
{
197+
return string.Empty;
198+
}
199+
}
200+
}

src/Models/PacketConfigModel.cs

Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ namespace GeneralUpdate.Tool.Avalonia.Models;
55
public class PacketConfigModel : ObservableObject
66
{
77
private string _appDirectory, _releaseDirectory, _patchDirectory, _name, _path, _driverDirectory;
8+
private string _reportUrl, _updateUrl, _appName, _mainAppName, _clientVersion;
89
private PlatformModel _platform;
910
private FormatModel _format;
1011
private EncodingModel _encoding;
@@ -109,4 +110,69 @@ public string DriverDirectory
109110
OnPropertyChanged(nameof(DriverDirectory));
110111
}
111112
}
113+
114+
/// <summary>
115+
/// 报告地址
116+
/// </summary>
117+
public string ReportUrl
118+
{
119+
get => _reportUrl;
120+
set
121+
{
122+
_reportUrl = value;
123+
OnPropertyChanged(nameof(ReportUrl));
124+
}
125+
}
126+
127+
/// <summary>
128+
/// 更新地址
129+
/// </summary>
130+
public string UpdateUrl
131+
{
132+
get => _updateUrl;
133+
set
134+
{
135+
_updateUrl = value;
136+
OnPropertyChanged(nameof(UpdateUrl));
137+
}
138+
}
139+
140+
/// <summary>
141+
/// 应用程序名称
142+
/// </summary>
143+
public string AppName
144+
{
145+
get => _appName;
146+
set
147+
{
148+
_appName = value;
149+
OnPropertyChanged(nameof(AppName));
150+
}
151+
}
152+
153+
/// <summary>
154+
/// 主应用程序名称
155+
/// </summary>
156+
public string MainAppName
157+
{
158+
get => _mainAppName;
159+
set
160+
{
161+
_mainAppName = value;
162+
OnPropertyChanged(nameof(MainAppName));
163+
}
164+
}
165+
166+
/// <summary>
167+
/// 客户端版本
168+
/// </summary>
169+
public string ClientVersion
170+
{
171+
get => _clientVersion;
172+
set
173+
{
174+
_clientVersion = value;
175+
OnPropertyChanged(nameof(ClientVersion));
176+
}
177+
}
112178
}

0 commit comments

Comments
 (0)