mirror of
https://github.com/babalae/better-genshin-impact.git
synced 2026-05-21 09:45:48 +08:00
处理引用类型的任务,处理仓库变动
This commit is contained in:
@@ -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; }
|
||||
}
|
||||
|
||||
16
BetterGenshinImpact/Service/GearTask/ContainerGearTask.cs
Normal file
16
BetterGenshinImpact/Service/GearTask/ContainerGearTask.cs
Normal 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;
|
||||
}
|
||||
}
|
||||
24
BetterGenshinImpact/Service/GearTask/ErrorGearTask.cs
Normal file
24
BetterGenshinImpact/Service/GearTask/ErrorGearTask.cs
Normal 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}");
|
||||
}
|
||||
}
|
||||
@@ -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}");
|
||||
}
|
||||
}
|
||||
@@ -149,6 +149,8 @@ public partial class GearTaskExecutionManager : ObservableObject
|
||||
// 初始化执行信息
|
||||
InitializeExecutionInfo(rootTask);
|
||||
|
||||
await ScriptService.StartGameTask();
|
||||
|
||||
// 开始执行
|
||||
await ExecuteTaskWithTrackingAsync(rootTask, _cancellationTokenSource.Token);
|
||||
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user