Keep collected media beside the executable, and let it be moved
Two things were wrong with where media lived. It defaulted to the user profile, which is the wrong home for the thing the application exists to accumulate: the collection grows without bound and belongs with the installation, so copying that folder takes the archive with it. And the setting for changing it existed but had no way to be set - the Settings page showed the path as read-only text. Media now defaults to a "media" folder next to the executable, and the Settings page has a box, a folder picker and a reset. Configuration stays in the profile, because that is genuinely per-user and the OS has an opinion about it. Writability is probed with a real file, not just a directory creation: creating a directory can succeed where writing into it does not, which is exactly what an install under Program Files looks like. On failure it falls back to the profile rather than refusing to start, and the effective path is shown in Settings so the fallback is visible instead of mysterious. A change applies on the next launch and says so. Paths are resolved before the container exists - the media root is read straight out of settings.json to build them - so applying it live would mean reconnecting the index, the blob store and the thumbnail cache underneath a possibly-running collection. Writing somewhere other than the box claims would be the worse failure. Existing files are not moved either; relocating an archive is its own operation with its own risks. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5
parent
ceacec79e2
commit
a4a0ea9a6b
@@ -0,0 +1,85 @@
|
||||
using AvParser.Infrastructure.Storage;
|
||||
|
||||
namespace AvParser.Infrastructure.Tests;
|
||||
|
||||
public sealed class AppPathsTests : IDisposable
|
||||
{
|
||||
private readonly string _root = Path.Combine(Path.GetTempPath(), "AvParserTests", Guid.NewGuid().ToString("N"));
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
if (Directory.Exists(_root))
|
||||
{
|
||||
Directory.Delete(_root, recursive: true);
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Media_defaults_to_a_folder_beside_the_application()
|
||||
{
|
||||
// The collection is the point of the app and grows without bound, so it belongs where the
|
||||
// app was put: copy that folder and the archive travels with it.
|
||||
var media = AppPaths.DefaultMediaDirectory();
|
||||
|
||||
Path.GetFileName(media).ShouldBe("media");
|
||||
Path.GetDirectoryName(media).ShouldBe(Path.TrimEndingDirectorySeparator(AppContext.BaseDirectory));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Settings_and_logs_stay_in_the_profile()
|
||||
{
|
||||
// Only media moves next to the executable. Configuration is per-user and belongs where the
|
||||
// operating system says it does.
|
||||
var paths = new AppPaths();
|
||||
|
||||
paths.SettingsFile.ShouldStartWith(AppPaths.ProfileDirectory());
|
||||
paths.LogDirectory.ShouldStartWith(AppPaths.ProfileDirectory());
|
||||
paths.MediaDirectory.ShouldNotStartWith(AppPaths.ProfileDirectory());
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void An_explicit_media_root_wins()
|
||||
{
|
||||
var elsewhere = Path.Combine(_root, "archive");
|
||||
|
||||
var paths = new AppPaths(_root, elsewhere);
|
||||
|
||||
paths.MediaDirectory.ShouldBe(elsewhere);
|
||||
paths.BlobDirectory.ShouldStartWith(elsewhere);
|
||||
paths.MediaIndexFile.ShouldStartWith(elsewhere);
|
||||
paths.MediaTempDirectory.ShouldStartWith(elsewhere);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void The_staging_area_is_a_sibling_of_the_blobs()
|
||||
{
|
||||
// Promotion has to be a rename, not a cross-volume copy of tens of megabytes.
|
||||
var paths = new AppPaths(_root, Path.Combine(_root, "archive"));
|
||||
|
||||
Path.GetDirectoryName(paths.MediaTempDirectory).ShouldBe(Path.GetDirectoryName(paths.BlobDirectory));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void An_omitted_media_root_falls_back_to_the_data_directory()
|
||||
{
|
||||
// The two-argument form is what tests and the override path use; without a media root it
|
||||
// must stay inside the directory it was given rather than escaping to the real profile.
|
||||
var paths = new AppPaths(_root);
|
||||
|
||||
paths.MediaDirectory.ShouldBe(Path.Combine(_root, "media"));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Creating_the_directories_is_idempotent()
|
||||
{
|
||||
var paths = new AppPaths(_root, Path.Combine(_root, "archive"));
|
||||
|
||||
paths.EnsureCreated();
|
||||
paths.EnsureCreated();
|
||||
|
||||
Directory.Exists(paths.BlobDirectory).ShouldBeTrue();
|
||||
Directory.Exists(paths.ShowcaseDirectory).ShouldBeTrue();
|
||||
Directory.Exists(paths.MediaTempDirectory).ShouldBeTrue();
|
||||
Directory.Exists(paths.LogDirectory).ShouldBeTrue();
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user