Files
av-parser/src/AvParser.UI/Services/FolderPicker.cs
T
Leonid PershinandClaude Opus 5 a4a0ea9a6b 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>
2026-08-14 07:53:42 +03:00

49 lines
1.6 KiB
C#

using Avalonia;
using Avalonia.Controls.ApplicationLifetimes;
using Avalonia.Platform.Storage;
namespace AvParser.UI.Services;
/// <summary>Asks the user for a directory.</summary>
/// <remarks>
/// An interface because the picker lives on the window, not on the view model: reaching for a
/// <c>TopLevel</c> from a view model would make it untestable and would tie it to there being a
/// window at all. Tests substitute a canned answer.
/// </remarks>
public interface IFolderPicker
{
/// <summary>Returns the chosen directory, or null when the user cancelled.</summary>
Task<string?> PickAsync(string title, string? startAt = null);
}
/// <inheritdoc />
public sealed class FolderPicker : IFolderPicker
{
/// <inheritdoc />
public async Task<string?> PickAsync(string title, string? startAt = null)
{
if (Application.Current?.ApplicationLifetime is not IClassicDesktopStyleApplicationLifetime desktop)
{
return null;
}
var window = desktop.MainWindow;
if (window?.StorageProvider is not { CanPickFolder: true } storage)
{
return null;
}
var options = new FolderPickerOpenOptions { Title = title, AllowMultiple = false };
if (!string.IsNullOrWhiteSpace(startAt) && Directory.Exists(startAt))
{
options.SuggestedStartLocation = await storage.TryGetFolderFromPathAsync(startAt).ConfigureAwait(true);
}
var chosen = await storage.OpenFolderPickerAsync(options).ConfigureAwait(true);
return chosen.Count == 0 ? null : chosen[0].TryGetLocalPath();
}
}