处理引用类型的任务,处理仓库变动

This commit is contained in:
辉鸭蛋
2026-05-05 03:05:13 +08:00
parent 977b12f9dc
commit 786f2715a7
7 changed files with 604 additions and 99 deletions

View File

@@ -1376,6 +1376,229 @@ public class ScriptRepoUpdater : Singleton<ScriptRepoUpdater>
return (Tree)repoEntry.Target;
}
/// <summary>
/// 获取仓库内指定相对目录下的直接子项(目录和文件)。
/// relDir 为相对于 repo/ 的路径,例如 "pathing"、"pathing/璃月"。
/// </summary>
public List<RepoTreeEntryInfo> GetChildrenFromCenterRepo(string relDir)
{
var result = new List<RepoTreeEntryInfo>();
try
{
var normalizedRelDir = NormalizeRepoRelativePath(relDir);
var repoPath = CenterRepoPath;
if (IsGitRepository(repoPath))
{
using var repo = new Repository(repoPath);
var rootTree = GetRepoSubdirectoryTree(repo);
if (!TryGetTreeByRelativePath(rootTree, normalizedRelDir, out var targetTree))
{
return result;
}
foreach (var entry in targetTree)
{
if (entry.TargetType != TreeEntryTargetType.Tree && entry.TargetType != TreeEntryTargetType.Blob)
{
continue;
}
var childRelPath = string.IsNullOrEmpty(normalizedRelDir)
? entry.Name
: $"{normalizedRelDir}/{entry.Name}";
result.Add(new RepoTreeEntryInfo
{
Name = entry.Name,
RelativePath = childRelPath,
IsDirectory = entry.TargetType == TreeEntryTargetType.Tree
});
}
}
else
{
var dirPath = string.IsNullOrEmpty(normalizedRelDir)
? Path.Combine(repoPath, "repo")
: Path.Combine(repoPath, "repo", normalizedRelDir);
if (!Directory.Exists(dirPath))
{
return result;
}
foreach (var dir in Directory.GetDirectories(dirPath))
{
var name = Path.GetFileName(dir);
var childRelPath = string.IsNullOrEmpty(normalizedRelDir)
? name
: $"{normalizedRelDir}/{name}";
result.Add(new RepoTreeEntryInfo
{
Name = name,
RelativePath = childRelPath,
IsDirectory = true
});
}
foreach (var file in Directory.GetFiles(dirPath))
{
var name = Path.GetFileName(file);
var childRelPath = string.IsNullOrEmpty(normalizedRelDir)
? name
: $"{normalizedRelDir}/{name}";
result.Add(new RepoTreeEntryInfo
{
Name = name,
RelativePath = childRelPath,
IsDirectory = false
});
}
}
return result
.OrderByDescending(x => x.IsDirectory)
.ThenBy(x => x.Name, StringComparer.OrdinalIgnoreCase)
.ToList();
}
catch (Exception ex)
{
_logger.LogError(ex, "获取仓库子项失败: {RelDir}", relDir);
return result;
}
}
/// <summary>
/// 判断仓库内相对目录是否存在。
/// relDir 为相对于 repo/ 的路径。
/// </summary>
public bool DirectoryExistsInCenterRepo(string relDir)
{
try
{
var normalizedRelDir = NormalizeRepoRelativePath(relDir);
var repoPath = CenterRepoPath;
if (IsGitRepository(repoPath))
{
using var repo = new Repository(repoPath);
var rootTree = GetRepoSubdirectoryTree(repo);
return TryGetTreeByRelativePath(rootTree, normalizedRelDir, out _);
}
var dirPath = string.IsNullOrEmpty(normalizedRelDir)
? Path.Combine(repoPath, "repo")
: Path.Combine(repoPath, "repo", normalizedRelDir);
return Directory.Exists(dirPath);
}
catch (Exception ex)
{
_logger.LogError(ex, "判断仓库目录是否存在失败: {RelDir}", relDir);
return false;
}
}
/// <summary>
/// 统计仓库内指定目录下匹配扩展名的文件数量。
/// relDir 为相对于 repo/ 的路径,支持递归统计。
/// </summary>
public int CountFilesInCenterRepo(string relDir, string extensionWithDot, bool recursive)
{
try
{
var normalizedRelDir = NormalizeRepoRelativePath(relDir);
var extension = extensionWithDot.StartsWith(".")
? extensionWithDot
: "." + extensionWithDot;
var repoPath = CenterRepoPath;
if (IsGitRepository(repoPath))
{
using var repo = new Repository(repoPath);
var rootTree = GetRepoSubdirectoryTree(repo);
if (!TryGetTreeByRelativePath(rootTree, normalizedRelDir, out var targetTree))
{
return 0;
}
return recursive
? CountFilesByExtensionInTreeRecursive(targetTree, extension)
: targetTree.Count(e => e.TargetType == TreeEntryTargetType.Blob
&& e.Name.EndsWith(extension, StringComparison.OrdinalIgnoreCase));
}
var dirPath = string.IsNullOrEmpty(normalizedRelDir)
? Path.Combine(repoPath, "repo")
: Path.Combine(repoPath, "repo", normalizedRelDir);
if (!Directory.Exists(dirPath))
{
return 0;
}
var option = recursive ? SearchOption.AllDirectories : SearchOption.TopDirectoryOnly;
return Directory.GetFiles(dirPath, "*" + extension, option).Length;
}
catch (Exception ex)
{
_logger.LogError(ex, "统计仓库文件失败: {RelDir}", relDir);
return 0;
}
}
private static string NormalizeRepoRelativePath(string relPath)
{
if (string.IsNullOrWhiteSpace(relPath))
{
return string.Empty;
}
var normalized = relPath.Replace('\\', '/').Trim('/');
return normalized;
}
private static bool TryGetTreeByRelativePath(Tree rootTree, string relDir, out Tree tree)
{
tree = rootTree;
if (string.IsNullOrEmpty(relDir))
{
return true;
}
var parts = relDir.Split(new[] { '/' }, StringSplitOptions.RemoveEmptyEntries);
foreach (var part in parts)
{
var entry = tree[part];
if (entry == null || entry.TargetType != TreeEntryTargetType.Tree)
{
return false;
}
tree = (Tree)entry.Target;
}
return true;
}
private static int CountFilesByExtensionInTreeRecursive(Tree tree, string extensionWithDot)
{
int count = 0;
foreach (var entry in tree)
{
if (entry.TargetType == TreeEntryTargetType.Blob)
{
if (entry.Name.EndsWith(extensionWithDot, StringComparison.OrdinalIgnoreCase))
{
count++;
}
}
else if (entry.TargetType == TreeEntryTargetType.Tree)
{
count += CountFilesByExtensionInTreeRecursive((Tree)entry.Target, extensionWithDot);
}
}
return count;
}
/// <summary>
/// 从中央仓库读取文件内容
/// </summary>
@@ -3339,3 +3562,12 @@ public class ScriptRepoUpdater : Singleton<ScriptRepoUpdater>
}
}
}
public sealed class RepoTreeEntryInfo
{
public string Name { get; init; } = string.Empty;
public string RelativePath { get; init; } = string.Empty;
public bool IsDirectory { get; init; }
}

View File

@@ -0,0 +1,16 @@
using System.Threading;
using System.Threading.Tasks;
using BetterGenshinImpact.Model.Gear.Tasks;
namespace BetterGenshinImpact.Service;
/// <summary>
/// 容器任务,用于目录类型或禁用任务的结构承载,不执行实际逻辑。
/// </summary>
internal class ContainerGearTask : BaseGearTask
{
public override Task Run(CancellationToken ct)
{
return Task.CompletedTask;
}
}

View File

@@ -0,0 +1,24 @@
using System;
using System.Threading;
using System.Threading.Tasks;
using BetterGenshinImpact.Model.Gear.Tasks;
namespace BetterGenshinImpact.Service;
/// <summary>
/// 错误占位任务,执行时抛出创建阶段捕获到的异常信息。
/// </summary>
internal class ErrorGearTask : BaseGearTask
{
private readonly string _errorMessage;
public ErrorGearTask(string errorMessage)
{
_errorMessage = errorMessage;
}
public override Task Run(CancellationToken ct)
{
throw new InvalidOperationException($"任务转换失败: {_errorMessage}");
}
}

View File

@@ -3,9 +3,12 @@ using System.Collections.Generic;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using System.IO;
using BetterGenshinImpact.Model.Gear;
using BetterGenshinImpact.Model.Gear.Tasks;
using BetterGenshinImpact.Model.Gear.Parameter;
using BetterGenshinImpact.Core.Script;
using BetterGenshinImpact.Core.Config;
using Microsoft.Extensions.Logging;
using Newtonsoft.Json;
@@ -16,8 +19,12 @@ namespace BetterGenshinImpact.Service;
/// </summary>
public class GearTaskConverter
{
private const string PathingRepoFolderPlaceholder = "{pathingRepoFolder}";
private readonly ILogger<GearTaskConverter> _logger;
private readonly GearTaskFactory _taskFactory;
private readonly object _mirrorLock = new();
private bool _pathingRepoMirrorInitialized;
public GearTaskConverter(ILogger<GearTaskConverter> logger, GearTaskFactory taskFactory)
{
@@ -78,6 +85,11 @@ public class GearTaskConverter
// 如果是目录类型或者任务被禁用,创建容器任务
if (taskData.IsDirectory || !taskData.IsEnabled)
{
if (taskData.IsDirectory && taskData.IsEnabled)
{
MaterializePathingReferenceIfNeeded(taskData);
}
task = new ContainerGearTask
{
Name = taskData.Name,
@@ -92,7 +104,8 @@ public class GearTaskConverter
else
{
// 使用工厂创建具体的任务实例
task = await _taskFactory.CreateTaskAsync(taskData);
var preparedTaskData = PrepareTaskDataForExecution(taskData);
task = await _taskFactory.CreateTaskAsync(preparedTaskData);
task.Father = parent;
_logger.LogDebug("创建具体任务: {TaskName} ({TaskType})", taskData.Name, taskData.TaskType);
@@ -286,6 +299,268 @@ public class GearTaskConverter
}
return count;
}
private void MaterializePathingReferenceIfNeeded(GearTaskData taskData)
{
if (taskData.Children.Count > 0 || string.IsNullOrWhiteSpace(taskData.Path))
{
return;
}
if (!TryExtractPathingRepoRelativePath(taskData.Path, out var repoRelativePath))
{
return;
}
var children = BuildPathingReferenceChildren(repoRelativePath);
if (children.Count == 0)
{
_logger.LogWarning("引用目录为空或不存在: {Path}", taskData.Path);
return;
}
taskData.Children = children;
_logger.LogDebug("已展开地图追踪引用节点: {NodeName}, 子节点数量: {ChildCount}", taskData.Name, children.Count);
}
private List<GearTaskData> BuildPathingReferenceChildren(string repoRelativePath)
{
var result = new List<GearTaskData>();
var children = ScriptRepoUpdater.Instance.GetChildrenFromCenterRepo(repoRelativePath);
foreach (var entry in children)
{
if (entry.IsDirectory)
{
result.Add(new GearTaskData
{
Name = entry.Name,
TaskType = string.Empty,
IsEnabled = true,
IsDirectory = true,
Path = BuildPathingPlaceholderPath(entry.RelativePath, true),
Parameters = "{}",
});
continue;
}
if (!entry.Name.EndsWith(".json", StringComparison.OrdinalIgnoreCase))
{
continue;
}
var executionPath = GetPathingExecutionFilePath(entry.RelativePath);
var parameters = new PathingGearTaskParams { Path = executionPath };
result.Add(new GearTaskData
{
Name = Path.GetFileNameWithoutExtension(entry.Name),
TaskType = "Pathing",
IsEnabled = true,
IsDirectory = false,
Path = BuildPathingPlaceholderPath(entry.RelativePath, false),
Parameters = JsonConvert.SerializeObject(parameters),
});
}
return result;
}
private GearTaskData PrepareTaskDataForExecution(GearTaskData taskData)
{
if (!string.Equals(taskData.TaskType, "Pathing", StringComparison.OrdinalIgnoreCase))
{
return taskData;
}
var parameters = DeserializePathingParams(taskData.Parameters);
if (!string.IsNullOrWhiteSpace(parameters.Path))
{
return taskData;
}
if (string.IsNullOrWhiteSpace(taskData.Path))
{
return taskData;
}
if (TryExtractPathingRepoRelativePath(taskData.Path, out var repoRelativePath)
&& repoRelativePath.EndsWith(".json", StringComparison.OrdinalIgnoreCase))
{
parameters.Path = GetPathingExecutionFilePath(repoRelativePath);
}
else
{
parameters.Path = taskData.Path.Trim().TrimEnd('\\', '/');
}
return new GearTaskData
{
Name = taskData.Name,
TaskType = taskData.TaskType,
Path = taskData.Path,
IsEnabled = taskData.IsEnabled,
IsDirectory = taskData.IsDirectory,
IsExpanded = taskData.IsExpanded,
Parameters = JsonConvert.SerializeObject(parameters),
CreatedTime = taskData.CreatedTime,
ModifiedTime = taskData.ModifiedTime,
Priority = taskData.Priority,
Children = taskData.Children
};
}
private PathingGearTaskParams DeserializePathingParams(string? parametersJson)
{
if (string.IsNullOrWhiteSpace(parametersJson))
{
return new PathingGearTaskParams();
}
try
{
return JsonConvert.DeserializeObject<PathingGearTaskParams>(parametersJson) ?? new PathingGearTaskParams();
}
catch
{
return new PathingGearTaskParams();
}
}
private static string BuildPathingPlaceholderPath(string repoRelativePath, bool isDirectory)
{
var normalized = repoRelativePath.Replace('/', Path.DirectorySeparatorChar);
if (normalized.StartsWith("pathing\\", StringComparison.OrdinalIgnoreCase))
{
normalized = normalized["pathing\\".Length..];
}
else if (string.Equals(normalized, "pathing", StringComparison.OrdinalIgnoreCase))
{
normalized = string.Empty;
}
var path = string.IsNullOrEmpty(normalized)
? PathingRepoFolderPlaceholder
: $@"{PathingRepoFolderPlaceholder}\{normalized}";
if (isDirectory && !path.EndsWith('\\'))
{
path += "\\";
}
return path;
}
private bool TryExtractPathingRepoRelativePath(string sourcePath, out string repoRelativePath)
{
repoRelativePath = string.Empty;
if (string.IsNullOrWhiteSpace(sourcePath))
{
return false;
}
var normalized = sourcePath.Replace('\\', '/').Trim();
if (!normalized.StartsWith(PathingRepoFolderPlaceholder, StringComparison.OrdinalIgnoreCase))
{
return false;
}
var relative = normalized[PathingRepoFolderPlaceholder.Length..].Trim('/');
repoRelativePath = string.IsNullOrEmpty(relative)
? "pathing"
: $"pathing/{relative}";
return true;
}
private string GetPathingExecutionFilePath(string repoRelativeJsonPath)
{
EnsurePathingRepoMirrorInitialized();
var normalized = repoRelativeJsonPath.Replace('\\', '/').Trim('/');
var relativeUnderPathing = normalized.StartsWith("pathing/", StringComparison.OrdinalIgnoreCase)
? normalized["pathing/".Length..]
: normalized;
var mirrorRoot = GetPathingRepoMirrorRoot();
var target = Path.Combine(mirrorRoot, relativeUnderPathing.Replace('/', Path.DirectorySeparatorChar));
if (File.Exists(target))
{
return target;
}
// 兜底:镜像中不存在时按需写入
var content = ScriptRepoUpdater.Instance.ReadFileFromCenterRepo(normalized);
if (string.IsNullOrWhiteSpace(content))
{
throw new FileNotFoundException($"仓库中不存在地图追踪文件: {normalized}");
}
var dir = Path.GetDirectoryName(target);
if (!string.IsNullOrEmpty(dir))
{
Directory.CreateDirectory(dir);
}
File.WriteAllText(target, content);
return target;
}
private void EnsurePathingRepoMirrorInitialized()
{
if (_pathingRepoMirrorInitialized)
{
return;
}
lock (_mirrorLock)
{
if (_pathingRepoMirrorInitialized)
{
return;
}
var mirrorRoot = GetPathingRepoMirrorRoot();
if (Directory.Exists(mirrorRoot))
{
Directory.Delete(mirrorRoot, true);
}
Directory.CreateDirectory(mirrorRoot);
MirrorPathingJsonRecursively("pathing", mirrorRoot);
_pathingRepoMirrorInitialized = true;
}
}
private void MirrorPathingJsonRecursively(string repoRelativePath, string localPath)
{
var entries = ScriptRepoUpdater.Instance.GetChildrenFromCenterRepo(repoRelativePath);
foreach (var entry in entries)
{
if (entry.IsDirectory)
{
var dirPath = Path.Combine(localPath, entry.Name);
Directory.CreateDirectory(dirPath);
MirrorPathingJsonRecursively(entry.RelativePath, dirPath);
continue;
}
if (!entry.Name.EndsWith(".json", StringComparison.OrdinalIgnoreCase))
{
continue;
}
var content = ScriptRepoUpdater.Instance.ReadFileFromCenterRepo(entry.RelativePath);
if (string.IsNullOrWhiteSpace(content))
{
continue;
}
var filePath = Path.Combine(localPath, entry.Name);
File.WriteAllText(filePath, content);
}
}
private static string GetPathingRepoMirrorRoot()
{
return Global.Absolute(@"User\Temp\GearTask\PathingRepoMirror");
}
}
/// <summary>
@@ -297,33 +572,3 @@ public class TaskValidationResult
public List<string> Errors { get; set; } = new();
public List<string> Warnings { get; set; } = new();
}
/// <summary>
/// 容器任务,用于目录类型或禁用的任务
/// </summary>
internal class ContainerGearTask : BaseGearTask
{
public override async Task Run(CancellationToken ct)
{
// 容器任务本身不执行任何操作,只是作为子任务的容器
await Task.CompletedTask;
}
}
/// <summary>
/// 错误任务,用于转换失败的任务占位符
/// </summary>
internal class ErrorGearTask : BaseGearTask
{
private readonly string _errorMessage;
public ErrorGearTask(string errorMessage)
{
_errorMessage = errorMessage;
}
public override async Task Run(CancellationToken ct)
{
throw new InvalidOperationException($"任务转换失败: {_errorMessage}");
}
}

View File

@@ -149,6 +149,8 @@ public partial class GearTaskExecutionManager : ObservableObject
// 初始化执行信息
InitializeExecutionInfo(rootTask);
await ScriptService.StartGameTask();
// 开始执行
await ExecuteTaskWithTrackingAsync(rootTask, _cancellationTokenSource.Token);

View File

@@ -253,6 +253,7 @@ public partial class GearTaskExecutor : ObservableObject
{
Name = viewModel.Name,
TaskType = viewModel.TaskType,
Path = viewModel.Path,
IsEnabled = viewModel.IsEnabled,
IsDirectory = viewModel.IsDirectory,
Parameters = viewModel.Parameters,

View File

@@ -114,15 +114,15 @@ public partial class PathingTaskSelectionViewModel : ViewModel
{
PathingTasks.Clear();
var pathingPath = Path.Combine(ScriptRepoUpdater.CenterRepoPath, "repo", "pathing");
if (!Directory.Exists(pathingPath))
const string pathingRoot = "pathing";
if (!ScriptRepoUpdater.Instance.DirectoryExistsInCenterRepo(pathingRoot))
{
_logger.LogWarning($"地图追踪任务目录不存在: {pathingPath}");
_logger.LogWarning("地图追踪任务目录不存在: {PathingRoot}", pathingRoot);
return;
}
// 加载根目录下的直接子项
LoadDirectChildrenFromDirectory(pathingPath, pathingPath, PathingTasks);
// 加载 pathing 根目录下的直接子项
LoadDirectChildrenFromRepo(pathingRoot, PathingTasks);
FilterTasks();
}
catch (Exception ex)
@@ -132,46 +132,37 @@ public partial class PathingTaskSelectionViewModel : ViewModel
}
/// <summary>
/// 从目录加载直接子项(用于构建层级结构)
/// 从仓库加载直接子项(用于构建层级结构)
/// </summary>
private void LoadDirectChildrenFromDirectory(string directoryPath, string rootPath, ObservableCollection<PathingTaskInfo> parentCollection)
private void LoadDirectChildrenFromRepo(string repoRelativePath, ObservableCollection<PathingTaskInfo> parentCollection)
{
try
{
// 加载文件夹
foreach (var dir in Directory.GetDirectories(directoryPath))
var children = ScriptRepoUpdater.Instance.GetChildrenFromCenterRepo(repoRelativePath);
foreach (var child in children)
{
var taskInfo = new PathingTaskInfo(dir, rootPath)
var taskInfo = new PathingTaskInfo
{
IsDirectory = true
Name = child.Name,
FolderName = Path.GetFileNameWithoutExtension(child.Name),
FullPath = child.RelativePath,
IsDirectory = child.IsDirectory,
ParentPath = GetParentPath(child.RelativePath),
RelativePath = TrimPathingRoot(child.RelativePath).Replace('/', Path.DirectorySeparatorChar)
};
// 设置图标
SetTaskIcon(taskInfo);
// 递归加载子目录到当前任务的Children集合中
LoadDirectChildrenFromDirectory(dir, rootPath, taskInfo.Children);
parentCollection.Add(taskInfo);
}
// 加载JSON文件默认展示到文件级别
foreach (var file in Directory.GetFiles(directoryPath, "*.json"))
{
var taskInfo = new PathingTaskInfo(file, rootPath)
if (taskInfo.IsDirectory)
{
IsDirectory = false
};
// 设置图标
SetTaskIcon(taskInfo);
LoadDirectChildrenFromRepo(child.RelativePath, taskInfo.Children);
}
parentCollection.Add(taskInfo);
}
}
catch (Exception ex)
{
_logger.LogError(ex, $"加载目录任务失败: {directoryPath}");
_logger.LogError(ex, "加载目录任务失败: {RepoRelativePath}", repoRelativePath);
}
}
@@ -203,16 +194,13 @@ public partial class PathingTaskSelectionViewModel : ViewModel
{
if (taskInfo.IsDirectory && string.IsNullOrEmpty(taskInfo.ReadmeContent))
{
var readmePath = Path.Combine(taskInfo.FullPath, "README.md");
if (File.Exists(readmePath))
{
taskInfo.ReadmeContent = File.ReadAllText(readmePath);
}
var readmePath = $"{NormalizeRepoPath(taskInfo.FullPath)}/README.md";
taskInfo.ReadmeContent = ScriptRepoUpdater.Instance.ReadFileFromCenterRepo(readmePath) ?? string.Empty;
}
}
catch (Exception ex)
{
_logger.LogError(ex, $"加载README内容失败: {taskInfo.FullPath}");
_logger.LogError(ex, "加载README内容失败: {FullPath}", taskInfo.FullPath);
taskInfo.ReadmeContent = "README加载失败";
}
}
@@ -226,7 +214,13 @@ public partial class PathingTaskSelectionViewModel : ViewModel
{
if (!taskInfo.IsDirectory && taskInfo.FullPath.EndsWith(".json") && string.IsNullOrEmpty(taskInfo.JsonContent))
{
var jsonContent = File.ReadAllText(taskInfo.FullPath);
var jsonContent = ScriptRepoUpdater.Instance.ReadFileFromCenterRepo(taskInfo.FullPath);
if (string.IsNullOrWhiteSpace(jsonContent))
{
taskInfo.JsonContent = "JSON文件不存在";
return;
}
// 格式化JSON
var jsonObject = JsonConvert.DeserializeObject(jsonContent);
taskInfo.JsonContent = JsonConvert.SerializeObject(jsonObject, Formatting.Indented);
@@ -234,7 +228,7 @@ public partial class PathingTaskSelectionViewModel : ViewModel
}
catch (Exception ex)
{
_logger.LogError(ex, $"加载JSON内容失败: {taskInfo.FullPath}");
_logger.LogError(ex, "加载JSON内容失败: {FullPath}", taskInfo.FullPath);
taskInfo.JsonContent = "JSON格式错误";
}
}
@@ -353,49 +347,40 @@ public partial class PathingTaskSelectionViewModel : ViewModel
if (!directory.IsDirectory)
return 0;
int count = 0;
// 计算当前目录下的JSON文件数量
try
{
count += Directory.GetFiles(directory.FullPath, "*.json").Length;
// 递归计算子目录
foreach (var subDir in Directory.GetDirectories(directory.FullPath))
{
count += CountTasksInDirectoryPath(subDir);
}
return ScriptRepoUpdater.Instance.CountFilesInCenterRepo(directory.FullPath, ".json", recursive: true);
}
catch (Exception ex)
{
_logger.LogError(ex, $"计算目录任务数量失败: {directory.FullPath}");
_logger.LogError(ex, "计算目录任务数量失败: {FullPath}", directory.FullPath);
return 0;
}
return count;
}
/// <summary>
/// 计算指定路径目录下的任务数量(递归)
/// </summary>
private int CountTasksInDirectoryPath(string directoryPath)
private static string NormalizeRepoPath(string path)
{
int count = 0;
return path.Replace('\\', '/').Trim('/');
}
try
private static string TrimPathingRoot(string repoRelativePath)
{
var normalized = NormalizeRepoPath(repoRelativePath);
if (normalized.StartsWith("pathing/", StringComparison.OrdinalIgnoreCase))
{
count += Directory.GetFiles(directoryPath, "*.json").Length;
foreach (var subDir in Directory.GetDirectories(directoryPath))
{
count += CountTasksInDirectoryPath(subDir);
}
}
catch (Exception ex)
{
_logger.LogError(ex, $"计算目录任务数量失败: {directoryPath}");
return normalized["pathing/".Length..];
}
return count;
return string.Equals(normalized, "pathing", StringComparison.OrdinalIgnoreCase)
? string.Empty
: normalized;
}
private static string GetParentPath(string repoRelativePath)
{
var normalized = NormalizeRepoPath(repoRelativePath);
var idx = normalized.LastIndexOf('/');
return idx > 0 ? normalized[..idx] : string.Empty;
}
/// <summary>
@@ -594,4 +579,4 @@ public partial class PathingTaskSelectionViewModel : ViewModel
return null;
}
}
}
}