From 39a89f6361a83fbd9fce14b3bc66d806745f0e75 Mon Sep 17 00:00:00 2001 From: uye <99072975+ABA2396@users.noreply.github.com> Date: Wed, 12 Nov 2025 14:15:15 +0800 Subject: [PATCH] =?UTF-8?q?rft:=20=E6=97=A5=E5=BF=97=E6=B1=87=E6=8A=A5?= =?UTF-8?q?=E6=89=93=E5=8C=85=20debug=20=E6=88=AA=E5=9B=BE=EF=BC=8C?= =?UTF-8?q?=E6=94=AF=E6=8C=81=E5=88=86=E5=8D=B7=EF=BC=8C=E4=BF=AE=E6=94=B9?= =?UTF-8?q?=E5=AD=98=E5=82=A8=E8=B7=AF=E5=BE=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/MaaWpfGui/Helper/PathsHelper.cs | 10 + .../Settings/IssueReportUserControlModel.cs | 199 ++++++++++++------ 2 files changed, 148 insertions(+), 61 deletions(-) diff --git a/src/MaaWpfGui/Helper/PathsHelper.cs b/src/MaaWpfGui/Helper/PathsHelper.cs index 6f700e80ac..68a98fc6cd 100644 --- a/src/MaaWpfGui/Helper/PathsHelper.cs +++ b/src/MaaWpfGui/Helper/PathsHelper.cs @@ -76,4 +76,14 @@ public static class PathsHelper /// named "data". This property returns the resolved path, creating the directory if it does not exist is the /// responsibility of the caller. public static string DataDir => _data ??= Path.Combine(BaseDir, "data"); + + private static string? _reports; + + /// + /// Gets the full path to the application's reports directory. + /// + /// The reports directory is located within the application's base directory under a folder + /// named "_reports". This property returns the resolved path, creating the directory if it does not exist is the + /// responsibility of the caller. + public static string ReportsDir => _reports ??= Path.Combine(BaseDir, "reports"); } diff --git a/src/MaaWpfGui/ViewModels/UserControl/Settings/IssueReportUserControlModel.cs b/src/MaaWpfGui/ViewModels/UserControl/Settings/IssueReportUserControlModel.cs index 9f9fc4f012..afd916d5b6 100644 --- a/src/MaaWpfGui/ViewModels/UserControl/Settings/IssueReportUserControlModel.cs +++ b/src/MaaWpfGui/ViewModels/UserControl/Settings/IssueReportUserControlModel.cs @@ -13,16 +13,16 @@ #nullable enable using System; +using System.Collections.Generic; using System.Diagnostics; using System.IO; using System.IO.Compression; +using System.Linq; using HandyControl.Controls; using HandyControl.Data; using JetBrains.Annotations; -using MaaWpfGui.Configuration.Factory; using MaaWpfGui.Constants; using MaaWpfGui.Helper; -using MaaWpfGui.Main; using Serilog; using Stylet; @@ -38,110 +38,187 @@ public class IssueReportUserControlModel : PropertyChangedBase Instance = new(); } - private static readonly string[] _payloadFileNames = [ - Bootstrapper.UiLogFile, - Bootstrapper.UiLogBakFile, - Bootstrapper.CoreLogFile, - Bootstrapper.CoreLogBakFile, - ConfigurationHelper.ConfigFile, - ConfigFactory.ConfigFile]; - public static IssueReportUserControlModel Instance { get; } public void OpenDebugFolder() { try { - if (!Directory.Exists(PathsHelper.DebugDir)) + if (!Directory.Exists(PathsHelper.ReportsDir)) { - Directory.CreateDirectory(PathsHelper.DebugDir); + Directory.CreateDirectory(PathsHelper.ReportsDir); } - Process.Start("explorer.exe", PathsHelper.DebugDir); + Process.Start("explorer.exe", PathsHelper.ReportsDir); } catch (Exception ex) { - ToastNotification.ShowDirect($"Failed to open debug folder\n{ex.Message}"); - Log.Error(ex, "Failed to open debug folder"); + ToastNotification.ShowDirect($"Failed to open reports folder\n{ex.Message}"); + Log.Error(ex, "Failed to open reports folder"); } } + /// + /// 生成日志压缩包 + /// public void GenerateSupportPayload() { try { - var reportFileName = $"report_{DateTimeOffset.Now:MM-dd_HH-mm-ss}.zip"; - string zipPath = Path.Combine(PathsHelper.DebugDir, reportFileName); - string tempPath = Path.Combine(Path.GetTempPath(), Guid.NewGuid().ToString()); - string debugTempPath = Path.Combine(tempPath, "debug"); - string resourceTempPath = Path.Combine(tempPath, "resource"); - if (File.Exists(zipPath)) - { - File.Delete(zipPath); - } - + const int PartSize = 10 * 1024 * 1024; // 10 MB + string reportNameBase = $"report_{DateTimeOffset.Now:MM-dd_HH-mm-ss}"; + string tempPath = Path.Combine(Path.GetTempPath(), $"maa-report-{Guid.NewGuid()}"); Directory.CreateDirectory(tempPath); - Directory.CreateDirectory(debugTempPath); - Directory.CreateDirectory(resourceTempPath); - // 复制 _payloadFileNames 中的文件到 tempPath/debug - foreach (var file in _payloadFileNames) + if (!Directory.Exists(PathsHelper.ReportsDir)) { - if (!File.Exists(file)) - { - continue; - } - - string relativePath = Path.GetRelativePath(PathsHelper.BaseDir, file); - string dest = Path.Combine(tempPath, relativePath); - Directory.CreateDirectory(Path.GetDirectoryName(dest)!); - File.Copy(file, dest, overwrite: true); + Directory.CreateDirectory(PathsHelper.ReportsDir); } - // 遍历 resource 文件夹下带 _custom 后缀的文件,复制到 tempPath/resource - const string ResourceDir = "resource"; - if (Directory.Exists(ResourceDir)) + // 复制文件 + CopyDirectoryIfExists(PathsHelper.DebugDir, Path.Combine(tempPath, "debug"), + f => { return !Path.GetFileName(f).StartsWith("report", StringComparison.OrdinalIgnoreCase); }); + CopyDirectoryIfExists(PathsHelper.ResourceDir, Path.Combine(tempPath, "resource"), + f => { return Path.GetFileName(f).Contains("_custom.", StringComparison.OrdinalIgnoreCase); }); + CopyDirectoryIfExists(PathsHelper.ConfigDir, Path.Combine(tempPath, "config")); + CopyDirectoryIfExists(PathsHelper.CacheDir, Path.Combine(tempPath, "cache")); + + string partsFolder = Path.Combine(PathsHelper.ReportsDir, $"{reportNameBase}_parts"); + if (!Directory.Exists(partsFolder)) { - foreach (var file in Directory.EnumerateFiles(ResourceDir, "*_custom.*", SearchOption.AllDirectories)) + Directory.CreateDirectory(partsFolder); + } + + // ====== part01:config + resource + cache + debug 根目录文件 ====== + List part01Files = []; + + string[] categories = ["config", "resource", "cache"]; + foreach (string category in categories) + { + string categoryPath = Path.Combine(tempPath, category); + if (Directory.Exists(categoryPath)) { - string relativePath = Path.GetRelativePath(ResourceDir, file); - string dest = Path.Combine(resourceTempPath, relativePath); - Directory.CreateDirectory(Path.GetDirectoryName(dest)!); - File.Copy(file, dest, overwrite: true); + part01Files.AddRange(Directory.EnumerateFiles(categoryPath, "*", SearchOption.AllDirectories)); } } - // 遍历 cache 文件夹下的文件,复制到 tempPath/cache - string cacheResourceDir = PathsHelper.CacheDir; - if (Directory.Exists(cacheResourceDir)) + string debugPath = Path.Combine(tempPath, "debug"); + if (Directory.Exists(debugPath)) { - foreach (var file in Directory.EnumerateFiles(cacheResourceDir, "*", SearchOption.AllDirectories)) + // 只取 debug 根目录文件 + var debugRootFiles = Directory.EnumerateFiles(debugPath, "*", SearchOption.TopDirectoryOnly).ToList(); + part01Files.AddRange(debugRootFiles); + } + + if (part01Files.Count > 0) + { + string part01Path = Path.Combine(partsFolder, "part01.zip"); + using var fs = new FileStream(part01Path, FileMode.Create); + using var archive = new ZipArchive(fs, ZipArchiveMode.Create); + foreach (var file in part01Files) { - string relativePath = Path.GetRelativePath(cacheResourceDir, file); - string dest = Path.Combine(tempPath, "cache", relativePath); - Directory.CreateDirectory(Path.GetDirectoryName(dest)!); - File.Copy(file, dest, overwrite: true); + string entryName = Path.GetRelativePath(tempPath, file).Replace("\\", "/"); + var entry = archive.CreateEntry(entryName, CompressionLevel.SmallestSize); + + using var entryStream = entry.Open(); + using var fileStream = File.OpenRead(file); + fileStream.CopyTo(entryStream); } } - using (FileStream zipToOpen = new FileStream(zipPath, FileMode.Create)) + // ====== part02+:debug 子目录文件按 PartSize 分卷 ====== + var debugSubFiles = Directory.EnumerateFiles(debugPath, "*", SearchOption.AllDirectories) + .Where(f => Path.GetDirectoryName(f) != debugPath).ToList(); + + int partNumber = 2; + while (debugSubFiles.Count > 0) { - using var archive = new ZipArchive(zipToOpen, ZipArchiveMode.Create); - foreach (var file in Directory.EnumerateFiles(tempPath, "*", SearchOption.AllDirectories)) + string partFileName = $"part{partNumber:D2}.zip"; + string partPath = Path.Combine(partsFolder, partFileName); + + using (var fs = new FileStream(partPath, FileMode.Create)) { - string entryName = Path.GetRelativePath(tempPath, file); - archive.CreateEntryFromFile(file, entryName, CompressionLevel.SmallestSize); + using var archive = new ZipArchive(fs, ZipArchiveMode.Create); + long currentSize = 0; + List processedFiles = []; + + foreach (var file in debugSubFiles.ToList()) + { + var fileInfo = new FileInfo(file); + + if (currentSize + fileInfo.Length > PartSize && currentSize > 0) + { + break; + } + + string entryName = Path.GetRelativePath(tempPath, file).Replace("\\", "/"); + var entry = archive.CreateEntry(entryName, CompressionLevel.SmallestSize); + + using (var entryStream = entry.Open()) + { + using var fileStream = File.OpenRead(file); + fileStream.CopyTo(entryStream); + } + + currentSize += fileInfo.Length; + processedFiles.Add(file); + } + + debugSubFiles.RemoveAll(f => processedFiles.Contains(f)); } + + partNumber++; } + // ====== 生成完整压缩包 ====== + string fullZipPath = Path.Combine(PathsHelper.ReportsDir, $"{reportNameBase}.zip"); + ZipFile.CreateFromDirectory(tempPath, fullZipPath, CompressionLevel.SmallestSize, includeBaseDirectory: false); + + // 清理临时目录 Directory.Delete(tempPath, recursive: true); - ShowGrowl($"{LocalizationHelper.GetString("GenerateSupportPayloadSuccessful")}\n{reportFileName}"); + + ShowGrowl($"{LocalizationHelper.GetString("GenerateSupportPayloadSuccessful")}\n{fullZipPath}"); OpenDebugFolder(); } catch (Exception ex) { ShowGrowl($"{LocalizationHelper.GetString("GenerateSupportPayloadException")}\n{ex.Message}"); - Log.Error(ex, "Failed to create log zip"); + Log.Error(ex, "Failed to create support payload"); + } + } + + /// + /// 从 sourceDir 复制文件到 targetDir,支持过滤。 + /// + private static void CopyDirectoryIfExists(string? sourceDir, string targetDir, Func? filter = null) + { + if (string.IsNullOrWhiteSpace(sourceDir) || !Directory.Exists(sourceDir)) + { + return; + } + + foreach (var file in Directory.EnumerateFiles(sourceDir, "*", SearchOption.AllDirectories)) + { + if (filter != null && !filter(file)) + { + continue; + } + + string relative = Path.GetRelativePath(sourceDir, file); + string dest = Path.Combine(targetDir, relative); + Directory.CreateDirectory(Path.GetDirectoryName(dest)!); + try + { + File.Copy(file, dest, true); + } + catch (IOException) + { + // 某些文件可能被占用,忽略复制失败 + } + catch (UnauthorizedAccessException) + { + // 也忽略权限问题 + } } }