mirror of
https://github.com/babalae/better-genshin-impact.git
synced 2026-05-21 09:45:48 +08:00
702 lines
23 KiB
C#
702 lines
23 KiB
C#
using System;
|
||
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 BetterGenshinImpact.Core.Script.Group;
|
||
using BetterGenshinImpact.Service.GearTask;
|
||
using Microsoft.Extensions.Logging;
|
||
using Newtonsoft.Json;
|
||
|
||
namespace BetterGenshinImpact.Service;
|
||
|
||
/// <summary>
|
||
/// 齿轮任务转换器,负责将 GearTaskData 转换为可执行的 BaseGearTask
|
||
/// </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;
|
||
private readonly HashSet<string> _exportedPathingRepoDirectories = new(StringComparer.OrdinalIgnoreCase);
|
||
|
||
public GearTaskConverter(ILogger<GearTaskConverter> logger, GearTaskFactory taskFactory)
|
||
{
|
||
_logger = logger;
|
||
_taskFactory = taskFactory;
|
||
}
|
||
|
||
/// <summary>
|
||
/// 将 GearTaskDefinitionData 转换为可执行的任务列表
|
||
/// </summary>
|
||
/// <param name="taskDefinition">任务定义数据</param>
|
||
/// <returns>可执行的任务列表</returns>
|
||
public async Task<List<BaseGearTask>> ConvertTaskDefinitionAsync(GearTaskDefinitionData taskDefinition)
|
||
{
|
||
if (taskDefinition?.RootTask == null)
|
||
{
|
||
throw new ArgumentException("任务定义或根任务不能为空");
|
||
}
|
||
|
||
var tasks = new List<BaseGearTask>();
|
||
|
||
try
|
||
{
|
||
_logger.LogInformation("开始转换任务定义: {TaskDefinitionName}", taskDefinition.Name);
|
||
|
||
var rootTask = await ConvertTaskDataAsync(taskDefinition.RootTask, null, taskDefinition.GroupConfig);
|
||
tasks.Add(rootTask);
|
||
|
||
_logger.LogInformation("任务定义转换完成: {TaskDefinitionName}, 共 {TaskCount} 个任务",
|
||
taskDefinition.Name, CountTotalTasks(rootTask));
|
||
|
||
return tasks;
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
_logger.LogError(ex, "转换任务定义失败: {TaskDefinitionName}", taskDefinition.Name);
|
||
throw;
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// 将单个 GearTaskData 转换为 BaseGearTask(包括子任务)
|
||
/// </summary>
|
||
/// <param name="taskData">任务数据</param>
|
||
/// <param name="parent">父任务</param>
|
||
/// <returns>转换后的任务</returns>
|
||
public async Task<BaseGearTask> ConvertTaskDataAsync(GearTaskData taskData, BaseGearTask? parent = null, ScriptGroupConfig? groupConfig = null)
|
||
{
|
||
if (taskData == null)
|
||
{
|
||
throw new ArgumentNullException(nameof(taskData));
|
||
}
|
||
|
||
try
|
||
{
|
||
BaseGearTask task;
|
||
|
||
// 如果是目录类型或者任务被禁用,创建容器任务
|
||
if (taskData.IsDirectory || !taskData.IsEnabled)
|
||
{
|
||
if (taskData.IsDirectory && taskData.IsEnabled)
|
||
{
|
||
MaterializePathingReferenceIfNeeded(taskData);
|
||
}
|
||
|
||
task = new ContainerGearTask
|
||
{
|
||
Name = taskData.Name,
|
||
Type = taskData.TaskType ?? "container",
|
||
Enabled = taskData.IsEnabled,
|
||
Father = parent
|
||
};
|
||
|
||
_logger.LogDebug("创建容器任务: {TaskName} (IsDirectory: {IsDirectory}, IsEnabled: {IsEnabled})",
|
||
taskData.Name, taskData.IsDirectory, taskData.IsEnabled);
|
||
}
|
||
else
|
||
{
|
||
// 使用工厂创建具体的任务实例
|
||
var preparedTaskData = PrepareTaskDataForExecution(taskData, groupConfig);
|
||
task = await _taskFactory.CreateTaskAsync(preparedTaskData);
|
||
task.Father = parent;
|
||
|
||
_logger.LogDebug("创建具体任务: {TaskName} ({TaskType})", taskData.Name, taskData.TaskType);
|
||
}
|
||
|
||
// 递归处理子任务
|
||
if (taskData.Children?.Count > 0)
|
||
{
|
||
_logger.LogDebug("处理子任务: {TaskName}, 子任务数量: {ChildCount}",
|
||
taskData.Name, taskData.Children.Count);
|
||
|
||
foreach (var childData in taskData.Children)
|
||
{
|
||
try
|
||
{
|
||
var childTask = await ConvertTaskDataAsync(childData, task, groupConfig);
|
||
task.Children.Add(childTask);
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
_logger.LogError(ex, "转换子任务失败: {ChildTaskName}, 父任务: {ParentTaskName}",
|
||
childData.Name, taskData.Name);
|
||
|
||
// 创建错误任务占位符
|
||
var errorTask = new ErrorGearTask(ex.Message)
|
||
{
|
||
Name = childData.Name,
|
||
Type = "error",
|
||
Enabled = false,
|
||
Father = task
|
||
};
|
||
task.Children.Add(errorTask);
|
||
}
|
||
}
|
||
}
|
||
|
||
return task;
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
_logger.LogError(ex, "转换任务数据失败: {TaskName}", taskData.Name);
|
||
|
||
// 返回错误任务
|
||
return new ErrorGearTask(ex.Message)
|
||
{
|
||
Name = taskData.Name,
|
||
Type = "error",
|
||
Enabled = false,
|
||
Father = parent
|
||
};
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// 验证任务数据的完整性
|
||
/// </summary>
|
||
/// <param name="taskData">任务数据</param>
|
||
/// <returns>验证结果</returns>
|
||
public TaskValidationResult ValidateTaskData(GearTaskData taskData)
|
||
{
|
||
var result = new TaskValidationResult { IsValid = true };
|
||
var errors = new List<string>();
|
||
var warnings = new List<string>();
|
||
|
||
if (taskData == null)
|
||
{
|
||
errors.Add("任务数据不能为空");
|
||
result.IsValid = false;
|
||
}
|
||
else
|
||
{
|
||
// 验证基本属性
|
||
if (string.IsNullOrWhiteSpace(taskData.Name))
|
||
{
|
||
errors.Add("任务名称不能为空");
|
||
result.IsValid = false;
|
||
}
|
||
|
||
if (!taskData.IsDirectory && string.IsNullOrWhiteSpace(taskData.TaskType))
|
||
{
|
||
errors.Add("非目录任务必须指定类型");
|
||
result.IsValid = false;
|
||
}
|
||
|
||
// 验证任务类型
|
||
if (!string.IsNullOrWhiteSpace(taskData.TaskType) &&
|
||
!GearTaskFactory.IsTaskTypeSupported(taskData.TaskType))
|
||
{
|
||
errors.Add($"不支持的任务类型: {taskData.TaskType}");
|
||
result.IsValid = false;
|
||
}
|
||
|
||
// 验证参数
|
||
if (!taskData.IsDirectory && taskData.IsEnabled)
|
||
{
|
||
try
|
||
{
|
||
ValidateTaskParameters(taskData);
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
warnings.Add($"参数验证警告: {ex.Message}");
|
||
}
|
||
}
|
||
|
||
// 递归验证子任务
|
||
if (taskData.Children?.Count > 0)
|
||
{
|
||
foreach (var child in taskData.Children)
|
||
{
|
||
var childResult = ValidateTaskData(child);
|
||
if (!childResult.IsValid)
|
||
{
|
||
errors.AddRange(childResult.Errors.Select(e => $"子任务 '{child.Name}': {e}"));
|
||
result.IsValid = false;
|
||
}
|
||
warnings.AddRange(childResult.Warnings.Select(w => $"子任务 '{child.Name}': {w}"));
|
||
}
|
||
}
|
||
}
|
||
|
||
result.Errors = errors;
|
||
result.Warnings = warnings;
|
||
return result;
|
||
}
|
||
|
||
/// <summary>
|
||
/// 验证任务参数
|
||
/// </summary>
|
||
/// <param name="taskData">任务数据</param>
|
||
private void ValidateTaskParameters(GearTaskData taskData)
|
||
{
|
||
if (taskData.Parameters == null)
|
||
{
|
||
return;
|
||
}
|
||
|
||
// 根据任务类型验证参数
|
||
switch (taskData.TaskType?.ToLowerInvariant())
|
||
{
|
||
case "javascript":
|
||
ValidateJsonParameter<JavascriptGearTaskParams>(taskData.Parameters, "FolderName");
|
||
break;
|
||
case "pathing":
|
||
ValidateJsonParameter<PathingGearTaskParams>(taskData.Parameters, "Path");
|
||
break;
|
||
case "csharp":
|
||
case "csharpreflection":
|
||
ValidateJsonParameter<CSharpReflectionGearTaskParams>(taskData.Parameters, "MethodPath");
|
||
break;
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// 验证 JSON 参数中的必需字段
|
||
/// </summary>
|
||
private void ValidateJsonParameter<T>(object parameters, string requiredField)
|
||
{
|
||
try
|
||
{
|
||
string json = parameters is string str ? str : JsonConvert.SerializeObject(parameters);
|
||
var obj = JsonConvert.DeserializeObject<T>(json);
|
||
|
||
var property = typeof(T).GetProperty(requiredField);
|
||
if (property != null)
|
||
{
|
||
var value = property.GetValue(obj);
|
||
if (value == null || (value is string strValue && string.IsNullOrWhiteSpace(strValue)))
|
||
{
|
||
throw new ArgumentException($"缺少必需参数: {requiredField}");
|
||
}
|
||
}
|
||
}
|
||
catch (JsonException ex)
|
||
{
|
||
throw new ArgumentException($"参数 JSON 格式错误: {ex.Message}");
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// 统计任务总数(包括子任务)
|
||
/// </summary>
|
||
/// <param name="task">根任务</param>
|
||
/// <returns>任务总数</returns>
|
||
private int CountTotalTasks(BaseGearTask task)
|
||
{
|
||
int count = 1;
|
||
if (task.Children?.Count > 0)
|
||
{
|
||
count += task.Children.Sum(CountTotalTasks);
|
||
}
|
||
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>();
|
||
EnsurePathingRepoDirectoryExported(repoRelativePath);
|
||
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 parameters = new PathingGearTaskParams
|
||
{
|
||
Path = BuildPathingPlaceholderPath(entry.RelativePath, false),
|
||
};
|
||
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, ScriptGroupConfig? groupConfig)
|
||
{
|
||
if (string.Equals(taskData.TaskType, "Javascript", StringComparison.OrdinalIgnoreCase))
|
||
{
|
||
var javascriptParameters = DeserializeJavascriptParams(taskData.Parameters);
|
||
javascriptParameters.PathingPartyConfig = groupConfig?.PathingConfig;
|
||
return BuildPreparedTaskData(taskData, JsonConvert.SerializeObject(javascriptParameters));
|
||
}
|
||
|
||
if (string.Equals(taskData.TaskType, "Shell", StringComparison.OrdinalIgnoreCase))
|
||
{
|
||
if (groupConfig?.EnableShellConfig == true)
|
||
{
|
||
return BuildPreparedTaskData(taskData, JsonConvert.SerializeObject(groupConfig.ShellConfig));
|
||
}
|
||
|
||
return taskData;
|
||
}
|
||
|
||
if (!string.Equals(taskData.TaskType, "Pathing", StringComparison.OrdinalIgnoreCase))
|
||
{
|
||
return taskData;
|
||
}
|
||
|
||
var pathingParameters = DeserializePathingParams(taskData.Parameters);
|
||
pathingParameters.PathingPartyConfig = groupConfig?.PathingConfig;
|
||
if (!string.IsNullOrWhiteSpace(pathingParameters.Path)
|
||
&& !TryExtractPathingRepoRelativePath(pathingParameters.Path, out _))
|
||
{
|
||
return BuildPreparedTaskData(taskData, JsonConvert.SerializeObject(pathingParameters));
|
||
}
|
||
|
||
if (string.IsNullOrWhiteSpace(taskData.Path))
|
||
{
|
||
return BuildPreparedTaskData(taskData, JsonConvert.SerializeObject(pathingParameters));
|
||
}
|
||
|
||
if (TryExtractPathingRepoRelativePath(taskData.Path, out var repoRelativePath)
|
||
&& repoRelativePath.EndsWith(".json", StringComparison.OrdinalIgnoreCase))
|
||
{
|
||
pathingParameters.Path = GetPathingExecutionFilePath(repoRelativePath);
|
||
}
|
||
else
|
||
{
|
||
pathingParameters.Path = taskData.Path.Trim().TrimEnd('\\', '/');
|
||
}
|
||
|
||
return BuildPreparedTaskData(taskData, JsonConvert.SerializeObject(pathingParameters));
|
||
}
|
||
|
||
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 JavascriptGearTaskParams DeserializeJavascriptParams(string? parametersJson)
|
||
{
|
||
if (string.IsNullOrWhiteSpace(parametersJson))
|
||
{
|
||
return new JavascriptGearTaskParams();
|
||
}
|
||
|
||
try
|
||
{
|
||
return JsonConvert.DeserializeObject<JavascriptGearTaskParams>(parametersJson) ?? new JavascriptGearTaskParams();
|
||
}
|
||
catch
|
||
{
|
||
return new JavascriptGearTaskParams();
|
||
}
|
||
}
|
||
|
||
private static GearTaskData BuildPreparedTaskData(GearTaskData taskData, string parametersJson)
|
||
{
|
||
return new GearTaskData
|
||
{
|
||
Name = taskData.Name,
|
||
TaskType = taskData.TaskType,
|
||
Path = taskData.Path,
|
||
IsEnabled = taskData.IsEnabled,
|
||
IsDirectory = taskData.IsDirectory,
|
||
IsExpanded = taskData.IsExpanded,
|
||
Parameters = parametersJson,
|
||
CreatedTime = taskData.CreatedTime,
|
||
ModifiedTime = taskData.ModifiedTime,
|
||
Priority = taskData.Priority,
|
||
Children = taskData.Children
|
||
};
|
||
}
|
||
|
||
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)
|
||
{
|
||
// Pathing 文件改为按需导出,避免首次转换时全量镜像仓库
|
||
|
||
var normalized = repoRelativeJsonPath.Replace('\\', '/').Trim('/');
|
||
var target = GetPathingMirrorPath(normalized);
|
||
if (File.Exists(target))
|
||
{
|
||
return target;
|
||
}
|
||
|
||
lock (_mirrorLock)
|
||
{
|
||
if (File.Exists(target))
|
||
{
|
||
return target;
|
||
}
|
||
|
||
var exportDirectory = Path.GetDirectoryName(target);
|
||
if (!string.IsNullOrEmpty(exportDirectory))
|
||
{
|
||
Directory.CreateDirectory(exportDirectory);
|
||
}
|
||
|
||
if (!ScriptRepoUpdater.Instance.ExportFileFromCenterRepo(normalized, target))
|
||
{
|
||
throw new FileNotFoundException($"仓库中不存在地图追踪文件: {normalized}");
|
||
}
|
||
|
||
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 EnsurePathingRepoDirectoryExported(string repoRelativePath)
|
||
{
|
||
var normalized = repoRelativePath.Replace('\\', '/').Trim('/');
|
||
if (IsPathingRepoDirectoryExported(normalized))
|
||
{
|
||
return;
|
||
}
|
||
|
||
lock (_mirrorLock)
|
||
{
|
||
if (IsPathingRepoDirectoryExported(normalized))
|
||
{
|
||
return;
|
||
}
|
||
|
||
var targetDirectory = GetPathingMirrorPath(normalized);
|
||
if (string.Equals(normalized, "pathing", StringComparison.OrdinalIgnoreCase))
|
||
{
|
||
var mirrorRoot = GetPathingRepoMirrorRoot();
|
||
if (Directory.Exists(mirrorRoot))
|
||
{
|
||
Directory.Delete(mirrorRoot, true);
|
||
}
|
||
}
|
||
else if (Directory.Exists(targetDirectory))
|
||
{
|
||
Directory.Delete(targetDirectory, true);
|
||
}
|
||
|
||
Directory.CreateDirectory(targetDirectory);
|
||
ScriptRepoUpdater.Instance.ExportFilesFromCenterRepo(normalized, targetDirectory, ".json");
|
||
_exportedPathingRepoDirectories.Add(normalized);
|
||
}
|
||
}
|
||
|
||
private bool IsPathingRepoDirectoryExported(string repoRelativePath)
|
||
{
|
||
foreach (var exportedDirectory in _exportedPathingRepoDirectories)
|
||
{
|
||
if (string.Equals(exportedDirectory, repoRelativePath, StringComparison.OrdinalIgnoreCase)
|
||
|| repoRelativePath.StartsWith(exportedDirectory + "/", StringComparison.OrdinalIgnoreCase))
|
||
{
|
||
return true;
|
||
}
|
||
}
|
||
|
||
return false;
|
||
}
|
||
|
||
private static string GetPathingMirrorPath(string repoRelativePath)
|
||
{
|
||
var normalized = repoRelativePath.Replace('\\', '/').Trim('/');
|
||
var relativeUnderPathing = normalized.StartsWith("pathing/", StringComparison.OrdinalIgnoreCase)
|
||
? normalized["pathing/".Length..]
|
||
: normalized == "pathing"
|
||
? string.Empty
|
||
: normalized;
|
||
|
||
var mirrorRoot = GetPathingRepoMirrorRoot();
|
||
return string.IsNullOrEmpty(relativeUnderPathing)
|
||
? mirrorRoot
|
||
: Path.Combine(mirrorRoot, relativeUnderPathing.Replace('/', Path.DirectorySeparatorChar));
|
||
}
|
||
|
||
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>
|
||
/// 任务验证结果
|
||
/// </summary>
|
||
public class TaskValidationResult
|
||
{
|
||
public bool IsValid { get; set; }
|
||
public List<string> Errors { get; set; } = new();
|
||
public List<string> Warnings { get; set; } = new();
|
||
}
|