Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
200 changes: 200 additions & 0 deletions src/Common/CsprojReader.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,200 @@
using System;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Xml.Linq;

namespace GeneralUpdate.Tool.Avalonia.Common;

/// <summary>
/// Utility class for reading .csproj files
/// </summary>
public static class CsprojReader
{
/// <summary>
/// Read MainAppName from .csproj file
/// </summary>
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;
}
}

/// <summary>
/// Read ClientVersion from .csproj file or .exe file version
/// </summary>
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;
}
}

/// <summary>
/// Read OutputPath from .csproj file
/// </summary>
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;
}
}

Comment on lines +102 to +124
Copy link

Copilot AI Feb 14, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The ReadOutputPath method is implemented but never called in the codebase. According to the issue requirements, OutputPath should be read and assigned to the release directory. However, this functionality appears incomplete - the method exists but is not integrated into the build pipeline in PacketViewModel. Consider either removing this unused method or integrating it into the ReadProjectConfiguration method if it's needed for future functionality.

Suggested change
/// <summary>
/// Read OutputPath from .csproj file
/// </summary>
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;
}
}

Copilot uses AI. Check for mistakes.
/// <summary>
/// Find .csproj file in the directory
/// </summary>
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];
}

/// <summary>
/// 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.
/// </summary>
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();
Comment on lines +146 to +161
Copy link

Copilot AI Feb 14, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The recursive file search using SearchOption.AllDirectories could be slow for large directory trees. While the comment acknowledges this is acceptable for "typically small" release directories, there's no safeguard against accidentally pointing to a large directory tree (e.g., an entire drive root). Consider adding a depth limit or file count threshold with a warning message to prevent performance issues when users accidentally select large directories.

Copilot uses AI. Check for mistakes.

// 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;
}
Comment on lines +163 to +171
Copy link

Copilot AI Feb 14, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The cross-platform executable detection for Unix systems doesn't verify actual file permissions. On Unix systems, just checking for a filename match doesn't guarantee the file is executable. Consider checking file permissions using UnixFileMode (available in .NET 7+) or by attempting to read file attributes to ensure the file is truly an executable. This could lead to incorrectly identifying non-executable files as the main application.

Copilot uses AI. Check for mistakes.

return string.Empty;
}
catch (Exception ex)
{
Trace.WriteLine($"Error searching for exe file: {ex.Message}");
return string.Empty;
}
}

/// <summary>
/// Get element value from XDocument
/// </summary>
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;
}
}
}
66 changes: 66 additions & 0 deletions src/Models/PacketConfigModel.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -109,4 +110,69 @@ public string DriverDirectory
OnPropertyChanged(nameof(DriverDirectory));
}
}

/// <summary>
/// 报告地址
/// </summary>
public string ReportUrl
{
get => _reportUrl;
set
{
_reportUrl = value;
OnPropertyChanged(nameof(ReportUrl));
}
}

/// <summary>
/// 更新地址
/// </summary>
public string UpdateUrl
{
get => _updateUrl;
set
{
_updateUrl = value;
OnPropertyChanged(nameof(UpdateUrl));
}
}

/// <summary>
/// 应用程序名称
/// </summary>
public string AppName
{
get => _appName;
set
{
_appName = value;
OnPropertyChanged(nameof(AppName));
}
}

/// <summary>
/// 主应用程序名称
/// </summary>
public string MainAppName
{
get => _mainAppName;
set
{
_mainAppName = value;
OnPropertyChanged(nameof(MainAppName));
}
}

/// <summary>
/// 客户端版本
/// </summary>
public string ClientVersion
{
get => _clientVersion;
set
{
_clientVersion = value;
OnPropertyChanged(nameof(ClientVersion));
}
}
}
Loading