Add settings, cloud places, and optional archive-content indexing.

Keep official clients in charge of sync while Explorer can group locations, persist UI prefs, and list zip/rar/7z members without extracting them.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
2026-08-23 12:16:09 +02:00
parent e9aba73552
commit 09a8cfafa3
57 changed files with 3130 additions and 119 deletions

View File

@@ -1,6 +1,8 @@
using Explorer.Application;
using Explorer.Domain;
using Explorer.Plugin.Abstractions;
using Explorer.Plugin.GoogleDrive;
using Explorer.Plugin.Nextcloud;
using Explorer.Plugin.OneDrive;
using Microsoft.Extensions.Logging.Abstractions;
@@ -104,6 +106,111 @@ public class CloudProviderTests
Assert.Equal("Dominique - Personal", merged[0].DisplayName);
Assert.Equal(@"C:\Users\x\OneDrive - Contoso", merged[1].Path);
}
[Fact]
public void Cloud_path_is_under_is_prefix_safe()
{
Assert.True(CloudPath.IsUnder(@"F:\OneDrive - wyniger", @"F:\OneDrive - wyniger\Obsidian\Notes"));
Assert.False(CloudPath.IsUnder(@"F:\OneDrive", @"F:\OneDrive - wyniger"));
Assert.True(CloudPath.IsUnder(@"J:\", @"J:\My Drive\Photos"));
Assert.Equal(@"J:\", CloudPath.NormalizePlace("j:"));
Assert.Equal(@"J:\", CloudPath.NormalizePlace(@"J:\"));
}
[Fact]
public void DriveFS_media_named_google_drive_becomes_a_place()
{
var places = DriveFsPreferenceReader.PlacesFromTables(
[
new("Games", @"D:\", 4),
new("Google Drive", @"J:\", 10),
new("MP3", @"G:\", 4)
],
[
new("My Drive", @"My Drive", @"J:\My Drive", true)
]);
Assert.Single(places);
Assert.Equal("googledrive", places[0].ProviderId);
Assert.Equal("Google Drive", places[0].DisplayName);
Assert.Equal(@"J:\", places[0].Path);
}
[Fact]
public void DriveFS_mirrored_folder_becomes_a_place_when_no_mount()
{
var places = DriveFsPreferenceReader.PlacesFromTables(
[new("Data", @"F:\", 4)],
[new("Photos", @"Photos", @"F:\Google Photos", false)]);
Assert.Single(places);
Assert.Equal("Google Drive - Photos", places[0].DisplayName);
Assert.Equal(@"F:\Google Photos", places[0].Path);
}
[Fact]
public void DriveFS_local_preference_db_exposes_configured_mount()
{
if (!File.Exists(DriveFsPreferenceReader.DefaultDatabasePath))
{
return;
}
var places = DriveFsPreferenceReader.ReadPlaces();
Assert.Contains(places, p => p.DisplayName == "Google Drive" && p.Path.Length >= 2);
}
[Theory]
[InlineData(@"C:\Users\x\My Drive", "Google Drive")]
[InlineData(@"G:\Shared drives", "Shared drives")]
[InlineData(@"D:\Work", "Google Drive - Work")]
public void Google_drive_place_label(string path, string expected)
=> Assert.Equal(expected, GoogleDrivePlaceDiscovery.BuildLabel(path, null));
[Fact]
public void Nextcloud_parses_account_folders_from_cfg()
{
const string cfg = """
[Accounts]
0\url=https://cloud.example.com
0\displayName=Dominique Wyniger
0\Folders\1\localPath=F:/OneDrive - wyniger/Obsidian/Notes/
0\Folders\1\targetPath=/Notizen/Notes
0\Folders\1\virtualFilesMode=off
""";
var places = NextcloudPlaceDiscovery.ParseCfg(cfg);
Assert.Single(places);
Assert.Equal("nextcloud", places[0].ProviderId);
Assert.Equal(@"F:\OneDrive - wyniger\Obsidian\Notes", places[0].Path);
Assert.Equal("Nextcloud - Notes", places[0].DisplayName);
}
[Fact]
public void OneDrive_does_not_claim_google_drive_paths()
{
Assert.False(OneDriveStorageProvider.LooksLikeOneDrive(@"G:\My Drive\Photos"));
Assert.True(GoogleDriveStorageProvider.LooksLikeGoogleDrive(@"G:\My Drive\Photos"));
}
[Fact]
public void Discover_skips_missing_default_profile_folders()
{
var profile = Environment.GetFolderPath(Environment.SpecialFolder.UserProfile);
var google = GoogleDrivePlaceDiscovery.Discover();
var nextcloud = NextcloudPlaceDiscovery.Discover();
AssertMissingGuess(google, Path.Combine(profile, "Google Drive"));
AssertMissingGuess(google, Path.Combine(profile, "My Drive"));
AssertMissingGuess(nextcloud, Path.Combine(profile, "Nextcloud"));
AssertMissingGuess(nextcloud, Path.Combine(profile, "ownCloud"));
}
private static void AssertMissingGuess(IReadOnlyList<ProviderPlace> places, string path)
{
if (Directory.Exists(path))
{
return;
}
Assert.DoesNotContain(places, p => p.Path.Equals(path.TrimEnd('\\'), StringComparison.OrdinalIgnoreCase));
}
}
file static class CloudFilesNativeStates