diff --git a/src/GuiApp/Avalonia/FileTime.GuiApp.Abstractions/IconProviders/SpecialPathType.cs b/src/GuiApp/Avalonia/FileTime.GuiApp.Abstractions/IconProviders/SpecialPathType.cs new file mode 100644 index 0000000..bb15fa5 --- /dev/null +++ b/src/GuiApp/Avalonia/FileTime.GuiApp.Abstractions/IconProviders/SpecialPathType.cs @@ -0,0 +1,14 @@ +namespace FileTime.GuiApp.IconProviders; + +public enum SpecialPathType +{ + Home, + Downloads, + Music, + Videos, + Desktop, + Documents, + Images, + Templates, + PublicShare +} \ No newline at end of file diff --git a/src/GuiApp/Avalonia/FileTime.GuiApp.Abstractions/Services/IPlacesService.cs b/src/GuiApp/Avalonia/FileTime.GuiApp.Abstractions/Services/IPlacesService.cs new file mode 100644 index 0000000..7aa3999 --- /dev/null +++ b/src/GuiApp/Avalonia/FileTime.GuiApp.Abstractions/Services/IPlacesService.cs @@ -0,0 +1,9 @@ +using FileTime.App.Core.Services; +using FileTime.GuiApp.IconProviders; + +namespace FileTime.GuiApp.Services; + +public interface IPlacesService : IStartupHandler +{ + Dictionary GetSpecialPaths(); +} \ No newline at end of file diff --git a/src/GuiApp/Avalonia/FileTime.GuiApp.App/Startup.cs b/src/GuiApp/Avalonia/FileTime.GuiApp.App/Startup.cs index 2b9201d..6156d20 100644 --- a/src/GuiApp/Avalonia/FileTime.GuiApp.App/Startup.cs +++ b/src/GuiApp/Avalonia/FileTime.GuiApp.App/Startup.cs @@ -45,16 +45,20 @@ public static class Startup if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) { - serviceCollection.AddSingleton(); + serviceCollection + .AddSingleton() + .AddSingleton(); } else { - serviceCollection.AddSingleton(); + serviceCollection + .AddSingleton() + .AddSingleton(); } return serviceCollection .AddSingleton() - .AddSingleton(); + .AddSingleton(sp => sp.GetRequiredService()); } internal static IServiceCollection RegisterLogging(this IServiceCollection serviceCollection) diff --git a/src/GuiApp/Avalonia/FileTime.GuiApp/IconProviders/MaterialIconProvider.cs b/src/GuiApp/Avalonia/FileTime.GuiApp/IconProviders/MaterialIconProvider.cs index 6708b13..00782cc 100644 --- a/src/GuiApp/Avalonia/FileTime.GuiApp/IconProviders/MaterialIconProvider.cs +++ b/src/GuiApp/Avalonia/FileTime.GuiApp/IconProviders/MaterialIconProvider.cs @@ -1,8 +1,8 @@ using System.Runtime.InteropServices; using FileTime.Core.Models; using FileTime.GuiApp.Models; +using FileTime.GuiApp.Services; using FileTime.Providers.Local; -using Syroot.Windows.IO; namespace FileTime.GuiApp.IconProviders; @@ -11,7 +11,7 @@ public class MaterialIconProvider : IIconProvider private static readonly Dictionary _iconsByExtension = new(); private static readonly Dictionary _iconsByFileName = new(); - private readonly List _specialPaths = new(); + private readonly Lazy> _specialPaths; public bool EnableAdvancedIcons { get; set; } = true; static MaterialIconProvider() @@ -19,17 +19,18 @@ public class MaterialIconProvider : IIconProvider InitMatches(); } - public MaterialIconProvider() + public MaterialIconProvider(IPlacesService placesService) { - if (!RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) return; + _specialPaths = new Lazy>(() => + { + var result = new List(); + foreach (var kvp in placesService.GetSpecialPaths()) + { + result.Add(new SpecialPathWithIcon(kvp.Key, GetAssetPath(GetSpecialPathIcon(kvp.Value)))); + } - _specialPaths.Add(new SpecialPathWithIcon(KnownFolders.Desktop.Path, GetAssetPath("desktop.svg"))); - _specialPaths.Add(new SpecialPathWithIcon(KnownFolders.Documents.Path, GetAssetPath("folder-resource.svg"))); - _specialPaths.Add(new SpecialPathWithIcon(KnownFolders.DownloadsLocalized.Path, GetAssetPath("folder-download.svg"))); - _specialPaths.Add(new SpecialPathWithIcon(KnownFolders.MusicLocalized.Path, GetAssetPath("folder-music.svg"))); - _specialPaths.Add(new SpecialPathWithIcon(KnownFolders.Pictures.Path, GetAssetPath("folder-images.svg"))); - _specialPaths.Add(new SpecialPathWithIcon(KnownFolders.Profile.Path, GetAssetPath("folder-home.svg"))); - _specialPaths.Add(new SpecialPathWithIcon(KnownFolders.Videos.Path, GetAssetPath("folder-video.svg"))); + return result; + }); } public ImagePath GetImage(IItem item) @@ -40,13 +41,13 @@ public class MaterialIconProvider : IIconProvider if (!EnableAdvancedIcons) return GetAssetPath(icon); - if (localPath != null && _specialPaths.Find(p => p.Path == localPath) is SpecialPathWithIcon specialPath) + if (localPath != null && _specialPaths.Value.Find(p => p.Path == localPath) is SpecialPathWithIcon specialPath) { return specialPath.IconPath; } if (item is not IElement element) return GetAssetPath(icon); - + if (element.Provider is ILocalContentProvider && (localPath?.EndsWith(".svg") ?? false)) { return new ImagePath(ImagePathType.Absolute, localPath); @@ -67,6 +68,21 @@ public class MaterialIconProvider : IIconProvider return GetAssetPath(icon); } + private string GetSpecialPathIcon(SpecialPathType pathType) + => pathType switch + { + SpecialPathType.Desktop => "desktop.svg", + SpecialPathType.Documents => "folder-resource.svg", + SpecialPathType.Downloads => "folder-download.svg", + SpecialPathType.Music => "folder-music.svg", + SpecialPathType.Images => "folder-images.svg", + SpecialPathType.Home => "folder-home.svg", + SpecialPathType.Videos => "folder-video.svg", + SpecialPathType.Templates => "folder-resource.svg", + SpecialPathType.PublicShare => "folder-resource.svg", + _ => throw new NotImplementedException() + }; + private static ImagePath GetAssetPath(string iconName) { return new ImagePath(ImagePathType.Asset, "/Assets/material/" + iconName); diff --git a/src/GuiApp/Avalonia/FileTime.GuiApp/Services/LinuxPlacesService.cs b/src/GuiApp/Avalonia/FileTime.GuiApp/Services/LinuxPlacesService.cs new file mode 100644 index 0000000..8ddafa7 --- /dev/null +++ b/src/GuiApp/Avalonia/FileTime.GuiApp/Services/LinuxPlacesService.cs @@ -0,0 +1,85 @@ +using System.Collections; +using FileTime.Core.Models; +using FileTime.Core.Timeline; +using FileTime.GuiApp.IconProviders; +using FileTime.GuiApp.ViewModels; +using FileTime.Providers.Local; + +namespace FileTime.GuiApp.Services; + +public class LinuxPlacesService : IPlacesService +{ + private readonly ILocalContentProvider _localContentProvider; + private readonly IGuiAppState _guiAppState; + private readonly Dictionary _specialPath = new(); + + public LinuxPlacesService( + ILocalContentProvider localContentProvider, + IGuiAppState guiAppState) + { + _localContentProvider = localContentProvider; + _guiAppState = guiAppState; + } + + public async Task InitAsync() + { + var placeKeys = new Dictionary + { + {"XDG_DESKTOP_DIR", SpecialPathType.Desktop}, + {"XDG_DOWNLOAD_DIR", SpecialPathType.Downloads}, + {"XDG_TEMPLATES_DIR", SpecialPathType.Templates}, + {"XDG_PUBLICSHARE_DIR", SpecialPathType.PublicShare}, + {"XDG_DOCUMENTS_DIR", SpecialPathType.Documents}, + {"XDG_MUSIC_DIR", SpecialPathType.Music}, + {"XDG_PICTURES_DIR", SpecialPathType.Images}, + {"XDG_VIDEOS_DIR", SpecialPathType.Videos}, + }; + + var homeFolder = Environment.GetEnvironmentVariable("HOME"); + if (homeFolder is null) return; + + var userDirsLines = await File.ReadAllLinesAsync(Path.Combine(homeFolder, ".config", "user-dirs.dirs")); + + var placesStrings = placeKeys + .Select(p => (SpecialPath: p.Value, Line: userDirsLines.FirstOrDefault(l => l.StartsWith(p.Key)))) + .Where(l => l.Line is not null) + .Select(l => (l.SpecialPath, Path: l.Line![(l.Line.IndexOf('=') + 1)..].Trim('\"'))) + .Select(l => (l.SpecialPath, Path: ReplaceEnvVars(l.Path))) + .Where(l => Directory.Exists(l.Path)) + .ToList(); + + var places = new List(); + foreach (var place in placesStrings) + { + var resolvedPlace = await _localContentProvider.GetItemByNativePathAsync(new NativePath(place.Path), PointInTime.Present); + + if (resolvedPlace is not IContainer resolvedContainer) continue; + places.Add(new PlaceInfo(resolvedContainer, resolvedContainer.DisplayName)); + _specialPath.Add(place.Path, place.SpecialPath); + } + + var resolvedHomeItem = await _localContentProvider.GetItemByNativePathAsync(new NativePath(homeFolder), PointInTime.Present); + + + IEnumerable finalPlaces = places.OrderBy(p => p.DisplayName); + + if (resolvedHomeItem is IContainer resolvedHomeFolder) + { + finalPlaces = finalPlaces.Prepend(new PlaceInfo(resolvedHomeFolder, resolvedHomeFolder.DisplayName)); + _specialPath.Add(homeFolder, SpecialPathType.Home); + } + + _guiAppState.Places = finalPlaces.ToList().AsReadOnly(); + } + + private string ReplaceEnvVars(string s) + { + return Environment + .GetEnvironmentVariables() + .Cast() + .Select(d => new KeyValuePair(d.Key.ToString()!, d.Value!.ToString()!)) + .Aggregate(s, (c, kvp) => c.Replace("$" + kvp.Key, kvp.Value)); + } + + public Dictionary GetSpecialPaths() => _specialPath; +} \ No newline at end of file diff --git a/src/GuiApp/Avalonia/FileTime.GuiApp/Services/PlacesService.cs b/src/GuiApp/Avalonia/FileTime.GuiApp/Services/WindowsPlacesService.cs similarity index 67% rename from src/GuiApp/Avalonia/FileTime.GuiApp/Services/PlacesService.cs rename to src/GuiApp/Avalonia/FileTime.GuiApp/Services/WindowsPlacesService.cs index 9ff7e82..3891064 100644 --- a/src/GuiApp/Avalonia/FileTime.GuiApp/Services/PlacesService.cs +++ b/src/GuiApp/Avalonia/FileTime.GuiApp/Services/WindowsPlacesService.cs @@ -2,18 +2,19 @@ using System.Runtime.InteropServices; using FileTime.App.Core.Services; using FileTime.Core.Models; using FileTime.Core.Timeline; +using FileTime.GuiApp.IconProviders; using FileTime.GuiApp.ViewModels; using FileTime.Providers.Local; using Syroot.Windows.IO; namespace FileTime.GuiApp.Services; -public class PlacesService : IStartupHandler +public class WindowsPlacesService : IPlacesService { private readonly ILocalContentProvider _localContentProvider; private readonly IGuiAppState _guiAppState; - public PlacesService( + public WindowsPlacesService( ILocalContentProvider localContentProvider, IGuiAppState guiAppState) { @@ -49,4 +50,16 @@ public class PlacesService : IStartupHandler _guiAppState.Places = places.AsReadOnly(); } + + public Dictionary GetSpecialPaths() + => new Dictionary + { + {KnownFolders.Desktop.Path, SpecialPathType.Desktop}, + {KnownFolders.Documents.Path, SpecialPathType.Documents}, + {KnownFolders.DownloadsLocalized.Path, SpecialPathType.Downloads}, + {KnownFolders.MusicLocalized.Path, SpecialPathType.Music}, + {KnownFolders.Pictures.Path, SpecialPathType.Images}, + {KnownFolders.Profile.Path, SpecialPathType.Home}, + {KnownFolders.Videos.Path, SpecialPathType.Videos}, + }; } \ No newline at end of file