Unify path resolution server-wide

This commit is contained in:
Pat Hartl 2026-07-24 18:00:32 -05:00
parent 2ca1db535c
commit 41bcfa908d
11 changed files with 122 additions and 21 deletions

View file

@ -189,9 +189,6 @@ namespace LANCommander.Server.Services
{
var storageLocation = await storageLocationService.GetAsync(storageLocationId);
if (!Directory.Exists(storageLocation.Path))
Directory.CreateDirectory(storageLocation.Path);
var archive = new Archive
{
ObjectKey = Guid.NewGuid().ToString(),

View file

@ -67,6 +67,8 @@ namespace LANCommander.Server.Services
});
}
storagePath = AppPaths.ResolveStorageLocationPath(storagePath);
if (!Directory.Exists(storagePath))
Directory.CreateDirectory(storagePath);

View file

@ -136,6 +136,8 @@ namespace LANCommander.Server.Services
});
}
storagePath = AppPaths.ResolveStorageLocationPath(storagePath);
if (!Directory.Exists(storagePath))
Directory.CreateDirectory(storagePath);

View file

@ -140,8 +140,11 @@ namespace LANCommander.Server.Services
foreach (var storageLocation in storageLocations)
{
if (!Directory.Exists(storageLocation.Path))
Directory.CreateDirectory(storageLocation.Path);
// Store the configured (possibly relative) path, but create the resolved physical directory.
var resolvedPath = AppPaths.ResolveStorageLocationPath(storageLocation.Path);
if (!Directory.Exists(resolvedPath))
Directory.CreateDirectory(resolvedPath);
try
{

View file

@ -42,8 +42,9 @@ namespace LANCommander.Server.Services
public IEnumerable<LauncherArtifact> GetLauncherArtifactsFromLocalFiles()
{
var currentVersion = versionProvider.GetCurrentVersion();
var downloadedLaunchers = Directory.GetFiles(_settingsProvider.CurrentValue.Server.Launcher.StoragePath, $"LANCommander.Launcher*v{currentVersion.WithoutMetadata()}.*");
var downloadedInstallers = Directory.GetFiles(_settingsProvider.CurrentValue.Server.Launcher.StoragePath, $"LANCommander.Launcher-{currentVersion.WithoutMetadata()}*Setup*.*");
var launcherStoragePath = AppPaths.ResolveStorageLocationPath(_settingsProvider.CurrentValue.Server.Launcher.StoragePath);
var downloadedLaunchers = Directory.GetFiles(launcherStoragePath, $"LANCommander.Launcher*v{currentVersion.WithoutMetadata()}.*");
var downloadedInstallers = Directory.GetFiles(launcherStoragePath, $"LANCommander.Launcher-{currentVersion.WithoutMetadata()}*Setup*.*");
var downloads = downloadedLaunchers.Concat(downloadedInstallers).Distinct();
@ -149,7 +150,7 @@ namespace LANCommander.Server.Services
client.DownloadFileCompleted += ReleaseDownloaded;
client.QueryString.Add("Version", release.TagName);
await client.DownloadFileTaskAsync(new Uri(releaseFile), Path.Combine(_settingsProvider.CurrentValue.Server.Update.StoragePath, $"{release.TagName}.zip"));
await client.DownloadFileTaskAsync(new Uri(releaseFile), AppPaths.ResolveStorageLocationPath(_settingsProvider.CurrentValue.Server.Update.StoragePath, $"{release.TagName}.zip"));
}
}
@ -181,7 +182,7 @@ namespace LANCommander.Server.Services
var uri = new Uri(releaseFile);
client.QueryString.Add("Version", release.TagName);
await client.DownloadFileTaskAsync(uri, Path.Combine(_settingsProvider.CurrentValue.Server.Update.StoragePath, $"{release.TagName}.zip"));
await client.DownloadFileTaskAsync(uri, AppPaths.ResolveStorageLocationPath(_settingsProvider.CurrentValue.Server.Update.StoragePath, $"{release.TagName}.zip"));
}
}
}
@ -216,7 +217,7 @@ namespace LANCommander.Server.Services
private void ReleaseDownloaded(object? sender, System.ComponentModel.AsyncCompletedEventArgs e)
{
string version = ((WebClient)sender).QueryString["Version"];
string path = Path.Combine(_settingsProvider.CurrentValue.Server.Update.StoragePath, $"{version}.zip");
string path = AppPaths.ResolveStorageLocationPath(_settingsProvider.CurrentValue.Server.Update.StoragePath, $"{version}.zip");
_logger?.LogInformation("Update version {Version} has been downloaded", version);
@ -242,7 +243,7 @@ namespace LANCommander.Server.Services
var process = new ProcessStartInfo();
process.FileName = processExecutable;
process.Arguments = $"-Version {version} -Path \"{_settingsProvider.CurrentValue.Server.Update.StoragePath}\" -Executable {Process.GetCurrentProcess().MainModule.FileName}";
process.Arguments = $"-Version {version} -Path \"{AppPaths.ResolveStorageLocationPath(_settingsProvider.CurrentValue.Server.Update.StoragePath)}\" -Executable {Process.GetCurrentProcess().MainModule.FileName}";
process.UseShellExecute = true;
Process.Start(process);
@ -260,7 +261,7 @@ namespace LANCommander.Server.Services
public LauncherArtifact GetLauncherArtifact(string objectKey)
{
string name = Path.Combine(_settingsProvider.CurrentValue.Server.Launcher.StoragePath, objectKey);
string name = AppPaths.ResolveStorageLocationPath(_settingsProvider.CurrentValue.Server.Launcher.StoragePath, objectKey);
return GetArtifactFromName(name);
}
}

View file

@ -1,3 +1,4 @@
using LANCommander.SDK;
using LANCommander.SDK.Models;
using LANCommander.Server.Services;
using LANCommander.Server.Services.Abstractions;
@ -23,7 +24,7 @@ public static class LauncherEndpoints
{
var version = versionProvider.GetCurrentVersion();
var fileName = $"LANCommander.Launcher-Windows-x64-v{version.WithoutMetadata()}.zip";
var path = Path.Combine(settingsProvider.CurrentValue.Server.Launcher.StoragePath, fileName);
var path = AppPaths.ResolveStorageLocationPath(settingsProvider.CurrentValue.Server.Launcher.StoragePath, fileName);
if (!File.Exists(path) || !settingsProvider.CurrentValue.Server.Launcher.HostUpdates)
{

View file

@ -28,9 +28,6 @@ public static class UploadEndpoints
storageLocationId == null || storageLocationId == Guid.Empty ? null : storageLocationId,
SDK.Enums.StorageLocationType.Archive);
if (!Directory.Exists(storageLocation.Path))
Directory.CreateDirectory(storageLocation.Path);
var archive = new Archive
{
ObjectKey = Guid.NewGuid().ToString(),

View file

@ -1,4 +1,5 @@
using LANCommander.SDK.Extensions;
using LANCommander.SDK;
using LANCommander.SDK.Extensions;
using LANCommander.Server.Services;
using LANCommander.Server.Services.Models;
using Microsoft.Extensions.Options;
@ -28,7 +29,7 @@ namespace LANCommander.Server.Jobs.Background
if (!localArtifacts.Any(a => a.Name.EndsWith(artifact.Name)))
{
using (var downloadStream = await httpClient.GetStreamAsync(artifact.Url))
using (var fs = new FileStream(Path.Combine(settings.Value.Server.Launcher.StoragePath, artifact.Name), FileMode.Create))
using (var fs = new FileStream(AppPaths.ResolveStorageLocationPath(settings.Value.Server.Launcher.StoragePath, artifact.Name), FileMode.Create))
{
await downloadStream.CopyToAsync(fs);
op.Complete();

View file

@ -0,0 +1,89 @@
using LANCommander.SDK;
using LANCommander.SDK.Helpers;
using LANCommander.SDK.Migrations;
using Semver;
namespace LANCommander.Server.Migrations;
/// <summary>
/// Moves the settings-based storage directories (Update, Launcher, Backups, Snippets, Modules) from
/// their previously-written raw locations (resolved verbatim relative to the working directory) to the
/// unified location produced by <see cref="AppPaths.ResolveStorageLocationPath(string, string[])"/>.
/// This aligns runtime writes with the same resolution rule used everywhere else so relative paths land
/// under the config directory instead of next to the binary.
/// </summary>
public class AlignSettingsStoragePathsMigration(
SettingsProvider<Settings.Settings> settingsProvider,
ILogger<AlignSettingsStoragePathsMigration> logger) : FileSystemMigration(logger)
{
public override SemVersion Version => new(2, 1, 0);
private IEnumerable<string> GetConfiguredPaths()
{
var settings = settingsProvider.CurrentValue;
yield return settings.Server.Update.StoragePath;
yield return settings.Server.Launcher.StoragePath;
yield return settings.Server.Backups.StoragePath;
yield return settings.Server.Scripts.Snippets.StoragePath;
yield return settings.Server.Scripts.Modules.StoragePath;
}
private static (string Source, string Destination)? GetMove(string configuredPath)
{
if (string.IsNullOrWhiteSpace(configuredPath))
return null;
// Rooted paths already resolve verbatim, so there is nothing to move.
if (Path.IsPathRooted(configuredPath))
return null;
var source = Path.GetFullPath(Path.Combine(Directory.GetCurrentDirectory(), configuredPath));
var destination = AppPaths.ResolveStorageLocationPath(configuredPath);
if (string.Equals(source, destination, StringComparison.Ordinal))
return null;
return (source, destination);
}
public override async Task ExecuteAsync()
{
foreach (var configuredPath in GetConfiguredPaths())
{
var move = GetMove(configuredPath);
if (move == null)
continue;
var (source, destination) = move.Value;
try
{
if (!Directory.Exists(source))
continue;
Logger.LogInformation("Moving storage directory from \"{Source}\" to \"{Destination}\"", source, destination);
DirectoryHelper.MoveContents(source, destination);
}
catch (Exception ex)
{
Logger.LogError(ex, "Error while moving storage directory from \"{Source}\" to \"{Destination}\"", source, destination);
}
}
}
public override Task<bool> ShouldExecuteAsync()
{
foreach (var configuredPath in GetConfiguredPaths())
{
var move = GetMove(configuredPath);
if (move != null && Directory.Exists(move.Value.Source))
return Task.FromResult(true);
}
return Task.FromResult(false);
}
}

View file

@ -23,9 +23,14 @@ public static class Filesystem
foreach (var directory in directories)
{
logger.LogDebug("Ensuring directory {Directory} exists", directory);
if (!Directory.Exists(directory))
Directory.CreateDirectory(directory);
if (string.IsNullOrWhiteSpace(directory))
continue;
var resolved = AppPaths.ResolveStorageLocationPath(directory);
logger.LogDebug("Ensuring directory {Directory} exists", resolved);
if (!Directory.Exists(resolved))
Directory.CreateDirectory(resolved);
}
return app;

View file

@ -29,6 +29,9 @@ public static class Migrations
builder.Services.AddScoped<IMigration, MoveLauncherMigration>();
builder.Services.AddScoped<IMigration, MoveLogsMigration>();
// Align settings-based storage paths with the unified resolver
builder.Services.AddScoped<IMigration, AlignSettingsStoragePathsMigration>();
return builder;
}