✨ feat: 初始化飞拍替换方案仓库骨架
* 建立 .NET 8 解决方案及分层项目结构 * 添加 Flyshot.Core.Domain 领域模型(机器人、轨迹、运动学) * 添加 Flyshot.Core.Planning 规划层(ICSP、CubicSpline、采样器) * 添加 Flyshot.Core.Triggering 触发时序与 IO 时间轴 * 添加 Flyshot.Core.Config 配置兼容与 .robot 解析 * 添加 Flyshot.Server.Host 最小宿主及 /healthz 端点 * 补充单元测试与集成测试项目 * 添加 CLAUDE.md、AGENTS.md、README.md 项目规范
This commit is contained in:
137
src/Flyshot.Core.Config/PathCompatibility.cs
Normal file
137
src/Flyshot.Core.Config/PathCompatibility.cs
Normal file
@@ -0,0 +1,137 @@
|
||||
namespace Flyshot.Core.Config;
|
||||
|
||||
/// <summary>
|
||||
/// 标识需要生成哪一种平台风格的兼容路径。
|
||||
/// </summary>
|
||||
public enum CompatibilityPathStyle
|
||||
{
|
||||
/// <summary>
|
||||
/// 使用 Linux/Unix 风格路径。
|
||||
/// </summary>
|
||||
Posix,
|
||||
|
||||
/// <summary>
|
||||
/// 使用 Windows 风格路径。
|
||||
/// </summary>
|
||||
Windows
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 提供旧配置与新服务端之间的路径兼容策略。
|
||||
/// </summary>
|
||||
public static class PathCompatibility
|
||||
{
|
||||
/// <summary>
|
||||
/// 按旧系统常见目录约定解析配置文件路径。
|
||||
/// </summary>
|
||||
/// <param name="configPath">调用方传入的原始配置路径。</param>
|
||||
/// <param name="repoRoot">当前兼容搜索的仓库根目录。</param>
|
||||
/// <returns>命中的绝对配置路径。</returns>
|
||||
public static string ResolveConfigPath(string configPath, string repoRoot)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(configPath))
|
||||
{
|
||||
throw new ArgumentException("配置路径不能为空。", nameof(configPath));
|
||||
}
|
||||
|
||||
if (string.IsNullOrWhiteSpace(repoRoot))
|
||||
{
|
||||
throw new ArgumentException("仓库根目录不能为空。", nameof(repoRoot));
|
||||
}
|
||||
|
||||
var rawPath = configPath.Trim();
|
||||
if (Path.IsPathRooted(rawPath))
|
||||
{
|
||||
return File.Exists(rawPath)
|
||||
? Path.GetFullPath(rawPath)
|
||||
: throw new FileNotFoundException($"未找到配置文件: {rawPath}", rawPath);
|
||||
}
|
||||
|
||||
var normalizedRepoRoot = Path.GetFullPath(repoRoot);
|
||||
var fileName = Path.GetFileName(rawPath);
|
||||
var checkedPaths = new List<string>();
|
||||
|
||||
// 先按最常见的候选路径顺序尝试,保持与旧工具链相近的定位逻辑。
|
||||
foreach (var candidate in BuildConfigCandidates(normalizedRepoRoot, rawPath, fileName))
|
||||
{
|
||||
var fullCandidate = Path.GetFullPath(candidate);
|
||||
if (checkedPaths.Contains(fullCandidate, StringComparer.OrdinalIgnoreCase))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
checkedPaths.Add(fullCandidate);
|
||||
if (File.Exists(fullCandidate))
|
||||
{
|
||||
return fullCandidate;
|
||||
}
|
||||
}
|
||||
|
||||
// 最后一层兜底按文件名全仓库搜索,但只接受唯一命中,避免同名配置误判。
|
||||
var matches = Directory
|
||||
.EnumerateFiles(normalizedRepoRoot, fileName, SearchOption.AllDirectories)
|
||||
.Select(Path.GetFullPath)
|
||||
.Distinct(StringComparer.OrdinalIgnoreCase)
|
||||
.ToArray();
|
||||
|
||||
if (matches.Length == 1)
|
||||
{
|
||||
return matches[0];
|
||||
}
|
||||
|
||||
throw new FileNotFoundException(
|
||||
$"未找到配置文件 '{configPath}'。已检查: {string.Join(", ", checkedPaths)}",
|
||||
configPath);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 构造当前平台约定的用户数据根目录。
|
||||
/// </summary>
|
||||
/// <param name="homeDirectory">用户主目录。</param>
|
||||
/// <param name="pathStyle">目标平台风格。</param>
|
||||
/// <returns>兼容旧系统的用户数据目录。</returns>
|
||||
public static string BuildUserDataRoot(string homeDirectory, CompatibilityPathStyle pathStyle)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(homeDirectory))
|
||||
{
|
||||
throw new ArgumentException("用户目录不能为空。", nameof(homeDirectory));
|
||||
}
|
||||
|
||||
return pathStyle switch
|
||||
{
|
||||
CompatibilityPathStyle.Posix => JoinPosix(homeDirectory, ".Rvbust", "Data"),
|
||||
CompatibilityPathStyle.Windows => JoinWindows(homeDirectory, ".Rvbust", "Data"),
|
||||
_ => throw new ArgumentOutOfRangeException(nameof(pathStyle), pathStyle, "不支持的路径风格。")
|
||||
};
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 枚举旧系统中最常见的配置候选路径。
|
||||
/// </summary>
|
||||
private static IEnumerable<string> BuildConfigCandidates(string repoRoot, string rawPath, string fileName)
|
||||
{
|
||||
yield return Path.Combine(repoRoot, rawPath);
|
||||
yield return Path.Combine(repoRoot, "Rvbust", "Data", fileName);
|
||||
yield return Path.Combine(repoRoot, "Rvbust", "Install", "FlyingShot", "Config", fileName);
|
||||
yield return Path.Combine(repoRoot, "Rvbust", fileName);
|
||||
yield return Path.Combine(repoRoot, fileName);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 使用 Posix 风格拼接路径,便于在 Linux 下验证固定输出。
|
||||
/// </summary>
|
||||
private static string JoinPosix(string root, params string[] segments)
|
||||
{
|
||||
var trimmedRoot = root.TrimEnd('/');
|
||||
return string.Join("/", new[] { trimmedRoot }.Concat(segments));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 使用 Windows 风格拼接路径,避免在 Linux 上测试时被当前平台分隔符污染。
|
||||
/// </summary>
|
||||
private static string JoinWindows(string root, params string[] segments)
|
||||
{
|
||||
var normalizedRoot = root.TrimEnd('\\', '/').Replace('/', '\\');
|
||||
return string.Join("\\", new[] { normalizedRoot }.Concat(segments));
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user