using System.Net; using XFEToolBox.Core.Tools; using XFEToolBox.Server.Core.Models; using XFEToolBox.Server.Core.Services; using XFEToolBox.Server.Core.Utilities; using XFEExtension.NetCore.ServerInteractive.Attributes; using XFEExtension.NetCore.ServerInteractive.Implements.CoreService; namespace XFEToolBox.Server.Services; public partial class ToolCatalogService : ServerCoreStandardServiceBase { public IToolPackageRepository? ToolPackageRepository { get; set; } [EntryPoint("v1/tools/list")] public async Task GetPublishedToolsEntryPoint() { if (ToolPackageRepository is null) { await CloseWithError("工具包仓库未初始化。", HttpStatusCode.InternalServerError); return; } string? search; string? category; try { search = Json?["search"]?.GetValue(); category = Json?["category"]?.GetValue(); } catch (Exception exception) when (exception is InvalidOperationException or FormatException) { await CloseWithError("search 和 category 必须是字符串。", HttpStatusCode.BadRequest); return; } var packages = await ToolPackageRepository.ListAsync(publishedOnly: true); IEnumerable filtered = packages; if (!string.IsNullOrWhiteSpace(search)) { filtered = filtered.Where(package => package.Manifest.Name.Contains(search, StringComparison.OrdinalIgnoreCase) || package.Manifest.Description.Contains(search, StringComparison.OrdinalIgnoreCase) || package.Manifest.Tags.Any(tag => tag.Contains(search, StringComparison.OrdinalIgnoreCase))); } if (!string.IsNullOrWhiteSpace(category)) filtered = filtered.Where(package => string.Equals(package.Manifest.Category, category, StringComparison.OrdinalIgnoreCase)); var result = filtered .GroupBy(package => package.Manifest.Id, StringComparer.OrdinalIgnoreCase) .Select(group => group.OrderByDescending(package => package.Manifest.Version, SemanticVersionComparer.Instance).First()) .Select(ToolPackageContractMapper.ToSummary) .OrderBy(tool => tool.Name, StringComparer.CurrentCultureIgnoreCase) .ToArray(); await Close(result); } [EntryPoint("v1/tools/get")] public async Task GetPublishedToolEntryPoint() { if (ToolPackageRepository is null) { await CloseWithError("工具包仓库未初始化。", HttpStatusCode.InternalServerError); return; } var toolId = GetString("toolId"); if (toolId is null) { await CloseWithError("缺少 toolId。", HttpStatusCode.BadRequest); return; } var packages = (await ToolPackageRepository.ListAsync(publishedOnly: true)) .Where(package => string.Equals(package.Manifest.Id, toolId, StringComparison.OrdinalIgnoreCase)) .OrderByDescending(package => package.Manifest.Version, SemanticVersionComparer.Instance) .ToArray(); if (packages.Length == 0) { await CloseWithError("工具不存在。", HttpStatusCode.NotFound); return; } await Close(new ToolPackageDetails { Manifest = packages[0].Manifest, Versions = packages.Select(ToolPackageContractMapper.ToVersionInfo).ToArray() }); } [EntryPoint("v1/tools/download")] public async Task DownloadPublishedToolEntryPoint() { if (ToolPackageRepository is null) { await CloseWithError("工具包仓库未初始化。", HttpStatusCode.InternalServerError); return; } var toolId = GetString("toolId"); var version = GetString("version"); if (toolId is null || version is null) { await CloseWithError("缺少 toolId 或 version。", HttpStatusCode.BadRequest); return; } var package = await ToolPackageRepository.FindFileAsync(toolId, version, publishedOnly: true); if (package is null) { await CloseWithError("工具或版本不存在。", HttpStatusCode.NotFound); return; } #pragma warning disable CS0618 // Keep HttpListener compatibility while preferring the current socket response. ServerHttpResponseHelper.ConfigureDownload( Args.Context?.Response, Args.Response, "application/vnd.xfestudio.xfetool", package.Package.PackageSize, $"{package.Package.Manifest.Id}-{package.Package.Manifest.Version}.xfetool", package.Package.Sha256); #pragma warning restore CS0618 await using var stream = new FileStream( package.FullPath, FileMode.Open, FileAccess.Read, FileShare.Read, 81920, FileOptions.Asynchronous | FileOptions.SequentialScan); await Close(stream); } private string? GetString(string propertyName) { try { var value = Json?[propertyName]?.GetValue(); return string.IsNullOrWhiteSpace(value) ? null : value.Trim(); } catch (Exception exception) when (exception is InvalidOperationException or FormatException) { return null; } } }