- Phase 2: MainWindow 3-section layout (sidebar/content/log bar), Dark/Light theme with ThemeHelper, MainViewModel with ObservableProperty/RelayCommand, tool card filtering by search + category - Phase 3: ToolEditWindow for add/edit tools, ProcessExecutionService (Process.Start + error handling), double-click + right-click context menu (run/edit), path browse dialog - Bugfix: ContextMenu commands now use PlacementTarget.Tag binding (ContextMenu in separate visual tree) - Bugfix: StaticResource converters moved to XAML before DataTemplate to fix XamlParseException on tool card render - Bugfix: Pure filenames (no path separators) treated as PATH commands, not marked invalid - Bugfix: RefreshData preserves SelectedCategory; Load() catches all exceptions; Save() wrapped in try-catch; auto-scroll log to newest entry - Tests: xUnit project with 55 tests covering models, services, converters, and view models
114 lines
3.4 KiB
C#
114 lines
3.4 KiB
C#
using System.IO;
|
|
using System.Text.Json;
|
|
using PersonalToolBox.Models;
|
|
|
|
namespace PersonalToolBox.Services;
|
|
|
|
/// <summary>
|
|
/// 基于 JSON 文件的数据持久化服务
|
|
/// 配置文件路径: 可执行程序目录/config.json
|
|
/// </summary>
|
|
public class JsonDataService : IDataService
|
|
{
|
|
private readonly ILogService _logService;
|
|
private readonly string _filePath;
|
|
|
|
private static readonly JsonSerializerOptions JsonOptions = new()
|
|
{
|
|
WriteIndented = true,
|
|
PropertyNameCaseInsensitive = true
|
|
};
|
|
|
|
public AppConfig Config { get; private set; } = new();
|
|
|
|
public JsonDataService(ILogService logService)
|
|
{
|
|
_logService = logService;
|
|
_filePath = Path.Combine(AppContext.BaseDirectory, "config.json");
|
|
}
|
|
|
|
public void Load()
|
|
{
|
|
if (!File.Exists(_filePath))
|
|
{
|
|
_logService.Info("配置文件不存在,使用默认配置");
|
|
Config = new AppConfig();
|
|
Save();
|
|
return;
|
|
}
|
|
|
|
try
|
|
{
|
|
var json = File.ReadAllText(_filePath);
|
|
var config = JsonSerializer.Deserialize<AppConfig>(json, JsonOptions);
|
|
|
|
if (config == null)
|
|
{
|
|
_logService.Warning("配置文件解析结果为空,使用默认配置");
|
|
Config = new AppConfig();
|
|
return;
|
|
}
|
|
|
|
Config = config;
|
|
|
|
// 核心容错逻辑:验证所有工具路径是否有效
|
|
foreach (var tool in Config.Tools)
|
|
{
|
|
if (string.IsNullOrWhiteSpace(tool.ExecutablePath))
|
|
continue;
|
|
|
|
// URL 格式的路径(如 https://...)不需要检查本地文件是否存在
|
|
if (Uri.TryCreate(tool.ExecutablePath, UriKind.Absolute, out var uri) &&
|
|
(uri.Scheme == Uri.UriSchemeHttp || uri.Scheme == Uri.UriSchemeHttps))
|
|
{
|
|
tool.IsValid = true;
|
|
continue;
|
|
}
|
|
|
|
// 纯文件名(无路径分隔符)可能位于系统 PATH 中,不标记为失效
|
|
if (!tool.ExecutablePath.Contains('\\') && !tool.ExecutablePath.Contains('/'))
|
|
{
|
|
tool.IsValid = true;
|
|
continue;
|
|
}
|
|
|
|
if (!File.Exists(tool.ExecutablePath))
|
|
{
|
|
tool.IsValid = false;
|
|
_logService.Warning($"工具 \"{tool.Name}\" 路径失效,找不到文件: {tool.ExecutablePath}");
|
|
}
|
|
else
|
|
{
|
|
tool.IsValid = true;
|
|
}
|
|
}
|
|
|
|
_logService.Info($"配置加载完成: {Config.Categories.Count} 个分类, {Config.Tools.Count} 个工具");
|
|
}
|
|
catch (JsonException ex)
|
|
{
|
|
_logService.Error($"配置文件 JSON 解析失败: {ex.Message}");
|
|
Config = new AppConfig();
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
_logService.Error($"配置文件加载失败: {ex.Message}");
|
|
Config = new AppConfig();
|
|
}
|
|
}
|
|
|
|
public void Save()
|
|
{
|
|
try
|
|
{
|
|
var json = JsonSerializer.Serialize(Config, JsonOptions);
|
|
File.WriteAllText(_filePath, json);
|
|
_logService.Info("配置已保存");
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
_logService.Error($"配置保存失败: {ex.Message}");
|
|
}
|
|
}
|
|
}
|