diff --git a/src/AppCommon/FileTime.App.Core.Abstraction/Models/FileExtension.cs b/src/AppCommon/FileTime.App.Core.Abstraction/Models/FileExtension.cs new file mode 100644 index 0000000..f081c5a --- /dev/null +++ b/src/AppCommon/FileTime.App.Core.Abstraction/Models/FileExtension.cs @@ -0,0 +1,3 @@ +namespace FileTime.App.Core.Models; + +public record FileExtension(long? Size); \ No newline at end of file diff --git a/src/AppCommon/FileTime.App.Core.Abstraction/Models/ItemPreviewMode.cs b/src/AppCommon/FileTime.App.Core.Abstraction/Models/ItemPreviewMode.cs new file mode 100644 index 0000000..9e7bd89 --- /dev/null +++ b/src/AppCommon/FileTime.App.Core.Abstraction/Models/ItemPreviewMode.cs @@ -0,0 +1,8 @@ +namespace FileTime.App.Core.Models; + +public enum ItemPreviewMode +{ + Unknown, + Text, + Empty +} \ No newline at end of file diff --git a/src/AppCommon/FileTime.App.Core.Abstraction/Services/IItemPreviewService.cs b/src/AppCommon/FileTime.App.Core.Abstraction/Services/IItemPreviewService.cs new file mode 100644 index 0000000..597e2ea --- /dev/null +++ b/src/AppCommon/FileTime.App.Core.Abstraction/Services/IItemPreviewService.cs @@ -0,0 +1,8 @@ +using FileTime.App.Core.ViewModels.ItemPreview; + +namespace FileTime.App.Core.Services; + +public interface IItemPreviewService +{ + IObservable ItemPreview { get; } +} \ No newline at end of file diff --git a/src/AppCommon/FileTime.App.Core.Abstraction/ViewModels/IFileViewModel.cs b/src/AppCommon/FileTime.App.Core.Abstraction/ViewModels/IFileViewModel.cs index 2ce77df..d12cbbf 100644 --- a/src/AppCommon/FileTime.App.Core.Abstraction/ViewModels/IFileViewModel.cs +++ b/src/AppCommon/FileTime.App.Core.Abstraction/ViewModels/IFileViewModel.cs @@ -1,9 +1,10 @@ +using FileTime.App.Core.Models; using FileTime.App.Core.Models.Enums; using FileTime.Core.Models; using InitableService; namespace FileTime.App.Core.ViewModels; -public interface IFileViewModel : IElementViewModel, IInitable +public interface IFileViewModel : IElementViewModel, IInitable { } \ No newline at end of file diff --git a/src/AppCommon/FileTime.App.Core.Abstraction/ViewModels/ItemPreview/IItemPreviewViewModel.cs b/src/AppCommon/FileTime.App.Core.Abstraction/ViewModels/ItemPreview/IItemPreviewViewModel.cs new file mode 100644 index 0000000..863fd0c --- /dev/null +++ b/src/AppCommon/FileTime.App.Core.Abstraction/ViewModels/ItemPreview/IItemPreviewViewModel.cs @@ -0,0 +1,8 @@ +using FileTime.App.Core.Models; + +namespace FileTime.App.Core.ViewModels.ItemPreview; + +public interface IItemPreviewViewModel +{ + ItemPreviewMode Mode { get; } +} \ No newline at end of file diff --git a/src/AppCommon/FileTime.App.Core/Services/ItemPreviewService.cs b/src/AppCommon/FileTime.App.Core/Services/ItemPreviewService.cs new file mode 100644 index 0000000..dcdff1e --- /dev/null +++ b/src/AppCommon/FileTime.App.Core/Services/ItemPreviewService.cs @@ -0,0 +1,40 @@ +using System.Reactive.Linq; +using FileTime.App.Core.ViewModels; +using FileTime.App.Core.ViewModels.ItemPreview; +using FileTime.Core.Models; +using Microsoft.Extensions.DependencyInjection; + +namespace FileTime.App.Core.Services; + +public class ItemPreviewService : IItemPreviewService +{ + private readonly IServiceProvider _serviceProvider; + public IObservable ItemPreview { get; } + + public ItemPreviewService(IAppState appState, IServiceProvider serviceProvider) + { + _serviceProvider = serviceProvider; + ItemPreview = appState + .SelectedTab + .Select(t => t?.CurrentSelectedItem.Throttle(TimeSpan.FromMilliseconds(250)) ?? Observable.Return(null)) + .Switch() + .Select(item => + item == null + ? Observable.Return(null) + : Observable.FromAsync(async () => await Map(item)) + ) + .Switch() + .Publish(null) + .RefCount(); + } + + private async Task Map(IItemViewModel itemViewModel) + { + return itemViewModel.BaseItem switch + { + IElement element => await _serviceProvider.GetAsyncInitableResolver(element) + .GetRequiredServiceAsync(), + _ => null + }; + } +} \ No newline at end of file diff --git a/src/AppCommon/FileTime.App.Core/Startup.cs b/src/AppCommon/FileTime.App.Core/Startup.cs index c7f0a6e..c84f071 100644 --- a/src/AppCommon/FileTime.App.Core/Startup.cs +++ b/src/AppCommon/FileTime.App.Core/Startup.cs @@ -2,6 +2,7 @@ using FileTime.App.Core.Services; using FileTime.App.Core.Services.UserCommandHandler; using FileTime.App.Core.StartupServices; using FileTime.App.Core.ViewModels; +using FileTime.App.Core.ViewModels.ItemPreview; using Microsoft.Extensions.DependencyInjection; namespace FileTime.App.Core; @@ -17,10 +18,12 @@ public static class Startup .AddTransient() .AddTransient() .AddTransient() + .AddTransient() .AddSingleton() .AddSingleton() .AddSingleton() .AddSingleton() + .AddSingleton() .AddCommandHandlers(); } diff --git a/src/AppCommon/FileTime.App.Core/ViewModels/FileViewModel.cs b/src/AppCommon/FileTime.App.Core/ViewModels/FileViewModel.cs index cd5867a..ffec3c6 100644 --- a/src/AppCommon/FileTime.App.Core/ViewModels/FileViewModel.cs +++ b/src/AppCommon/FileTime.App.Core/ViewModels/FileViewModel.cs @@ -1,3 +1,4 @@ +using FileTime.App.Core.Models; using FileTime.App.Core.Models.Enums; using FileTime.App.Core.Services; using FileTime.Core.Models; @@ -12,8 +13,9 @@ public partial class FileViewModel : ElementViewModel, IFileViewModel { } - public void Init(IFileElement item, ITabViewModel parentTab, ItemViewModelType itemViewModelType) + public void Init(IElement item, FileExtension fileExtension, ITabViewModel parentTab, ItemViewModelType itemViewModelType) { Init((IElement)item, parentTab, itemViewModelType); + Size = fileExtension.Size; } } \ No newline at end of file diff --git a/src/AppCommon/FileTime.App.Core/ViewModels/ItemPreview/ElementPreviewViewModel.cs b/src/AppCommon/FileTime.App.Core/ViewModels/ItemPreview/ElementPreviewViewModel.cs new file mode 100644 index 0000000..2544c51 --- /dev/null +++ b/src/AppCommon/FileTime.App.Core/ViewModels/ItemPreview/ElementPreviewViewModel.cs @@ -0,0 +1,39 @@ +using System.Text; +using FileTime.App.Core.Models; +using FileTime.Core.Models; +using InitableService; +using MvvmGen; + +namespace FileTime.App.Core.ViewModels.ItemPreview; + +[ViewModel] +public partial class ElementPreviewViewModel : IItemPreviewViewModel, IAsyncInitable +{ + private const int MaxTextPreviewSize = 1024 * 1024; + + public ItemPreviewMode Mode { get; private set; } + + [Property] private string? _textContent; + + public async Task InitAsync(IElement element) + { + try + { + var content = await element.Provider.GetContentAsync(element, MaxTextPreviewSize); + + TextContent = content is null + ? "Could not read any data from file " + element.Name + : Encoding.UTF8.GetString(content); + } + catch(Exception ex) + { + TextContent = $"Error while getting content of {element.FullName}. " + ex.ToString(); + } + + Mode = (TextContent?.Length ?? 0) switch + { + 0 => ItemPreviewMode.Empty, + _ => ItemPreviewMode.Text + }; + } +} \ No newline at end of file diff --git a/src/AppCommon/FileTime.App.Core/ViewModels/TabViewModel.cs b/src/AppCommon/FileTime.App.Core/ViewModels/TabViewModel.cs index 899f165..e59f8d2 100644 --- a/src/AppCommon/FileTime.App.Core/ViewModels/TabViewModel.cs +++ b/src/AppCommon/FileTime.App.Core/ViewModels/TabViewModel.cs @@ -4,6 +4,7 @@ using FileTime.App.Core.Extensions; using FileTime.App.Core.Models; using FileTime.App.Core.Models.Enums; using FileTime.App.Core.Services; +using FileTime.App.Core.ViewModels.ItemPreview; using FileTime.Core.Models; using FileTime.Core.Services; using Microsoft.Extensions.DependencyInjection; @@ -34,9 +35,14 @@ public partial class TabViewModel : ITabViewModel, IDisposable public IObservable>?> SelectedsChildren { get; private set; } = null!; public IObservable>?> ParentsChildren { get; private set; } = null!; - public IObservable?> CurrentItemsCollectionObservable { get; private set; } = null!; - public IObservable?> ParentsChildrenCollectionObservable { get; private set; } = null!; - public IObservable?> SelectedsChildrenCollectionObservable { get; private set; } = null!; + public IObservable?> CurrentItemsCollectionObservable { get; private set; } = + null!; + + public IObservable?> ParentsChildrenCollectionObservable { get; private set; } = + null!; + + public IObservable?> + SelectedsChildrenCollectionObservable { get; private set; } = null!; [Property] private BindedCollection? _currentItemsCollection; @@ -78,7 +84,7 @@ public partial class TabViewModel : ITabViewModel, IDisposable tab.CurrentSelectedItem, (currentItems, currentSelectedItemPath) => currentItems == null - ? Observable.Return((IItemViewModel?) null) + ? Observable.Return((IItemViewModel?)null) : currentItems .ToCollection() .Select(items => @@ -117,7 +123,7 @@ public partial class TabViewModel : ITabViewModel, IDisposable .Transform(i => MapItemToViewModel(i, ItemViewModelType.SelectedChild))), currentSelectedItemThrottled .Where(c => c is null or not IContainerViewModel) - .Select(_ => (IObservable>?) null) + .Select(_ => (IObservable>?)null) ) /*.ObserveOn(_rxSchedulerService.GetWorkerScheduler()) .SubscribeOn(_rxSchedulerService.GetUIScheduler())*/ @@ -136,7 +142,7 @@ public partial class TabViewModel : ITabViewModel, IDisposable return Observable.Merge( parentThrottled .Where(p => p is not null) - .Select(p => Observable.FromAsync(async () => (IContainer) await p!.ResolveAsync())) + .Select(p => Observable.FromAsync(async () => (IContainer)await p!.ResolveAsync())) .Switch() .Select(p => p.Items) .Switch() @@ -145,7 +151,7 @@ public partial class TabViewModel : ITabViewModel, IDisposable .Transform(i => MapItemToViewModel(i, ItemViewModelType.Parent))), parentThrottled .Where(p => p is null) - .Select(_ => (IObservable>?) null) + .Select(_ => (IObservable>?)null) ) /*.ObserveOn(_rxSchedulerService.GetWorkerScheduler()) .SubscribeOn(_rxSchedulerService.GetUIScheduler())*/ @@ -158,7 +164,7 @@ public partial class TabViewModel : ITabViewModel, IDisposable { return source .Select(c => - c != null ? c.ToCollection() : Observable.Return((IReadOnlyCollection?) null)) + c != null ? c.ToCollection() : Observable.Return((IReadOnlyCollection?)null)) .Switch() .Publish(null) .RefCount(); @@ -179,22 +185,27 @@ public partial class TabViewModel : ITabViewModel, IDisposable return containerViewModel; } - else if (item is IFileElement fileElement) - { - var fileViewModel = _serviceProvider - .GetInitableResolver(fileElement, this, type) - .GetRequiredService(); - fileViewModel.Size = fileElement.Size; - - return fileViewModel; - } else if (item is IElement element) { - var elementViewModel = _serviceProvider - .GetInitableResolver(element, this, type) - .GetRequiredService(); + var fileExtension = element.GetExtension(); - return elementViewModel; + if (fileExtension is not null) + { + var fileViewModel = _serviceProvider + .GetInitableResolver( + element, fileExtension, this, type) + .GetRequiredService(); + + return fileViewModel; + } + else + { + var elementViewModel = _serviceProvider + .GetInitableResolver(element, this, type) + .GetRequiredService(); + + return elementViewModel; + } } throw new ArgumentException($"{nameof(item)} is not {nameof(IContainer)} neither {nameof(IElement)}"); diff --git a/src/Core/FileTime.Core.Abstraction/Models/ExtensionCollection.cs b/src/Core/FileTime.Core.Abstraction/Models/ExtensionCollection.cs new file mode 100644 index 0000000..5101a93 --- /dev/null +++ b/src/Core/FileTime.Core.Abstraction/Models/ExtensionCollection.cs @@ -0,0 +1,47 @@ +using System.Collections; +using System.Diagnostics.CodeAnalysis; +using System.Runtime.CompilerServices; + +namespace FileTime.Core.Models; + +public class ExtensionCollection : IEnumerable +{ + private readonly List _extensions = new(); + + public ExtensionCollection() + { + } + + public ExtensionCollection(IEnumerable objects) + { + foreach (var obj in objects) + { + AddSafe(obj); + } + } + + private void AddSafe(object obj) + { + var objType = obj.GetType(); + if (_extensions.Any(i => i.GetType() == objType)) + throw new ArgumentException($"Collection already contains an item with type {objType.FullName}"); + + _extensions.Add(obj); + } + + public void Add([DisallowNull] T obj, [CallerArgumentExpression("obj")] string? paramName = null) + { + ArgumentNullException.ThrowIfNull(obj, paramName); + AddSafe(obj); + } + + public void Remove() + { + _extensions.RemoveAll(i => i is T); + } + + public ReadOnlyExtensionCollection AsReadOnly() => new ReadOnlyExtensionCollection(this); + + public IEnumerator GetEnumerator() => _extensions.GetEnumerator(); + IEnumerator IEnumerable.GetEnumerator() => _extensions.GetEnumerator(); +} \ No newline at end of file diff --git a/src/Core/FileTime.Core.Abstraction/Models/IFileElement.cs b/src/Core/FileTime.Core.Abstraction/Models/IFileElement.cs deleted file mode 100644 index 7a364a2..0000000 --- a/src/Core/FileTime.Core.Abstraction/Models/IFileElement.cs +++ /dev/null @@ -1,6 +0,0 @@ -namespace FileTime.Core.Models; - -public interface IFileElement : IElement -{ - long Size { get; } -} \ No newline at end of file diff --git a/src/Core/FileTime.Core.Abstraction/Models/IItem.cs b/src/Core/FileTime.Core.Abstraction/Models/IItem.cs index 58208d0..8ccd165 100644 --- a/src/Core/FileTime.Core.Abstraction/Models/IItem.cs +++ b/src/Core/FileTime.Core.Abstraction/Models/IItem.cs @@ -1,3 +1,4 @@ +using System.Reactive.Linq; using FileTime.Core.Enums; using FileTime.Core.Services; @@ -19,4 +20,7 @@ public interface IItem string? Attributes { get; } AbsolutePathType Type { get; } IObservable> Exceptions { get; } + ReadOnlyExtensionCollection Extensions { get; } + + T? GetExtension() => (T?)Extensions.FirstOrDefault(i => i is T); } \ No newline at end of file diff --git a/src/Core/FileTime.Core.Abstraction/Models/ReadOnlyExtensionCollection.cs b/src/Core/FileTime.Core.Abstraction/Models/ReadOnlyExtensionCollection.cs new file mode 100644 index 0000000..af73997 --- /dev/null +++ b/src/Core/FileTime.Core.Abstraction/Models/ReadOnlyExtensionCollection.cs @@ -0,0 +1,17 @@ +using System.Collections; + +namespace FileTime.Core.Models; + +public class ReadOnlyExtensionCollection : IEnumerable +{ + private readonly ExtensionCollection _collection; + + public ReadOnlyExtensionCollection(ExtensionCollection collection) + { + _collection = collection; + } + + public IEnumerator GetEnumerator() => _collection.GetEnumerator(); + + IEnumerator IEnumerable.GetEnumerator() => _collection.GetEnumerator(); +} \ No newline at end of file diff --git a/src/Core/FileTime.Core.Abstraction/Services/IContentProvider.cs b/src/Core/FileTime.Core.Abstraction/Services/IContentProvider.cs index f2aae5e..91022e8 100644 --- a/src/Core/FileTime.Core.Abstraction/Services/IContentProvider.cs +++ b/src/Core/FileTime.Core.Abstraction/Services/IContentProvider.cs @@ -20,4 +20,6 @@ public interface IContentProvider : IContainer, IOnContainerEnter Task> GetItemsByContainerAsync(FullName fullName); NativePath GetNativePath(FullName fullName); + + Task GetContentAsync(IElement element, int? maxLength = null, CancellationToken cancellationToken = default); } \ No newline at end of file diff --git a/src/Core/FileTime.Core.Models/Container.cs b/src/Core/FileTime.Core.Models/Container.cs index 06a7a29..7e33425 100644 --- a/src/Core/FileTime.Core.Models/Container.cs +++ b/src/Core/FileTime.Core.Models/Container.cs @@ -1,3 +1,4 @@ +using System.Collections.ObjectModel; using System.Reactive.Linq; using System.Reactive.Subjects; using DynamicData; @@ -20,6 +21,7 @@ public record Container( string? Attributes, IContentProvider Provider, IObservable> Exceptions, + ReadOnlyExtensionCollection Extensions, IObservable>?> Items) : IContainer { BehaviorSubject IsLoading { get; } = new BehaviorSubject(false); diff --git a/src/Core/FileTime.Core.Models/Element.cs b/src/Core/FileTime.Core.Models/Element.cs index 1733937..ff46f46 100644 --- a/src/Core/FileTime.Core.Models/Element.cs +++ b/src/Core/FileTime.Core.Models/Element.cs @@ -1,3 +1,4 @@ +using System.Collections.ObjectModel; using FileTime.Core.Enums; using FileTime.Core.Services; @@ -16,7 +17,8 @@ public record Element( bool CanRename, string? Attributes, IContentProvider Provider, - IObservable> Exceptions) : IElement + IObservable> Exceptions, + ReadOnlyExtensionCollection Extensions) : IElement { public AbsolutePathType Type => AbsolutePathType.Element; } \ No newline at end of file diff --git a/src/Core/FileTime.Core.Models/FileElement.cs b/src/Core/FileTime.Core.Models/FileElement.cs deleted file mode 100644 index 33da745..0000000 --- a/src/Core/FileTime.Core.Models/FileElement.cs +++ /dev/null @@ -1,35 +0,0 @@ -using FileTime.Core.Enums; -using FileTime.Core.Services; - -namespace FileTime.Core.Models; - -public record FileElement( - string Name, - string DisplayName, - FullName FullName, - NativePath NativePath, - IAbsolutePath? Parent, - bool IsHidden, - bool IsExists, - DateTime? CreatedAt, - SupportsDelete CanDelete, - bool CanRename, - string? Attributes, - IContentProvider Provider, - IObservable> Exceptions, - long Size) - : Element( - Name, - DisplayName, - FullName, - NativePath, - Parent, - IsHidden, - IsExists, - CreatedAt, - CanDelete, - CanRename, - Attributes, - Provider, - Exceptions - ), IFileElement; \ No newline at end of file diff --git a/src/Core/FileTime.Core.Services/ContentProviderBase.cs b/src/Core/FileTime.Core.Services/ContentProviderBase.cs index 4505d20..ce8ea30 100644 --- a/src/Core/FileTime.Core.Services/ContentProviderBase.cs +++ b/src/Core/FileTime.Core.Services/ContentProviderBase.cs @@ -1,3 +1,4 @@ +using System.Collections.ObjectModel; using System.Reactive.Linq; using System.Reactive.Subjects; using DynamicData; @@ -8,7 +9,10 @@ namespace FileTime.Core.Services; public abstract class ContentProviderBase : IContentProvider { + private readonly ReadOnlyExtensionCollection _extensions; + protected BehaviorSubject>?> Items { get; } = new(null); + protected ExtensionCollection Extensions { get; } IObservable>?> IContainer.Items => Items; @@ -44,10 +48,14 @@ public abstract class ContentProviderBase : IContentProvider public IObservable> Exceptions => Observable.Return(Enumerable.Empty()); + ReadOnlyExtensionCollection IItem.Extensions => _extensions; + protected ContentProviderBase(string name) { DisplayName = Name = name; FullName = new FullName(name); + Extensions = new ExtensionCollection(); + _extensions = Extensions.AsReadOnly(); } public virtual Task OnEnter() => Task.CompletedTask; @@ -57,7 +65,8 @@ public abstract class ContentProviderBase : IContentProvider bool forceResolve = false, AbsolutePathType forceResolvePathType = AbsolutePathType.Unknown, ItemInitializationSettings itemInitializationSettings = default) - => await GetItemByNativePathAsync(GetNativePath(fullName), forceResolve, forceResolvePathType, itemInitializationSettings); + => await GetItemByNativePathAsync(GetNativePath(fullName), forceResolve, forceResolvePathType, + itemInitializationSettings); public abstract Task GetItemByNativePathAsync( NativePath nativePath, @@ -67,4 +76,8 @@ public abstract class ContentProviderBase : IContentProvider public abstract Task> GetItemsByContainerAsync(FullName fullName); public abstract NativePath GetNativePath(FullName fullName); + + public abstract Task GetContentAsync(IElement element, + int? maxLength = null, + CancellationToken cancellationToken = default); } \ No newline at end of file diff --git a/src/GuiApp/Avalonia/FileTime.GuiApp/ViewModels/MainWindowViewModel.cs b/src/GuiApp/Avalonia/FileTime.GuiApp/ViewModels/MainWindowViewModel.cs index 4fe4a3c..659770d 100644 --- a/src/GuiApp/Avalonia/FileTime.GuiApp/ViewModels/MainWindowViewModel.cs +++ b/src/GuiApp/Avalonia/FileTime.GuiApp/ViewModels/MainWindowViewModel.cs @@ -20,6 +20,7 @@ namespace FileTime.GuiApp.ViewModels; [Inject(typeof(IKeyInputHandlerService), PropertyName = "_keyInputHandlerService")] [Inject(typeof(IUserCommandHandlerService), PropertyAccessModifier = AccessModifier.Public)] [Inject(typeof(LifecycleService), PropertyName = "_lifecycleService")] +[Inject(typeof(IItemPreviewService), PropertyAccessModifier = AccessModifier.Public)] public partial class MainWindowViewModel : IMainWindowViewModelBase { public bool Loading => false; diff --git a/src/GuiApp/Avalonia/FileTime.GuiApp/Views/MainWindow.axaml b/src/GuiApp/Avalonia/FileTime.GuiApp/Views/MainWindow.axaml index 07041b2..8dde8b5 100644 --- a/src/GuiApp/Avalonia/FileTime.GuiApp/Views/MainWindow.axaml +++ b/src/GuiApp/Avalonia/FileTime.GuiApp/Views/MainWindow.axaml @@ -8,6 +8,7 @@ xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:vm="using:FileTime.GuiApp.ViewModels" xmlns:config="using:FileTime.GuiApp.Configuration" + xmlns:appCoreModels="using:FileTime.App.Core.Models" Title="FileTime" d:DesignHeight="450" d:DesignWidth="800" @@ -44,89 +45,97 @@ - + - + - - + + - + - - - - - - + + + + + + - + - + - - + + - + - - + + - - + + - - - + + + - - - - - - - - + + + + + + + + - - - + + + - - - - - - - + IsVisible="{Binding ItemPreviewService.ItemPreview^,Converter={x:Static ObjectConverters.IsNull}}"> + + + + + + + + - - Empty - + + Empty + + + + + + + + + + + + + + - - - - - - - - - - + IsVisible="{Binding ItemPreviewService.ItemPreview^,Converter={x:Static ObjectConverters.IsNotNull}}"> + + + + + diff --git a/src/Providers/FileTime.Providers.Local/LocalContentProvider.cs b/src/Providers/FileTime.Providers.Local/LocalContentProvider.cs index 1e651a5..cbae7d7 100644 --- a/src/Providers/FileTime.Providers.Local/LocalContentProvider.cs +++ b/src/Providers/FileTime.Providers.Local/LocalContentProvider.cs @@ -2,6 +2,7 @@ using System.Reactive.Linq; using System.Reactive.Subjects; using System.Runtime.InteropServices; using DynamicData; +using FileTime.App.Core.Models; using FileTime.Core.Enums; using FileTime.Core.Models; using FileTime.Core.Services; @@ -133,6 +134,7 @@ public sealed partial class LocalContentProvider : ContentProviderBase, ILocalCo "???", this, nonNullExceptions, + new ExtensionCollection().AsReadOnly(), Observable.Return>?>(null) ); } @@ -195,6 +197,7 @@ public sealed partial class LocalContentProvider : ContentProviderBase, ILocalCo GetDirectoryAttributes(directoryInfo), this, exceptions, + new ExtensionCollection().AsReadOnly(), Observable.FromAsync(async () => await Task.Run(InitChildren)) ); @@ -226,7 +229,12 @@ public sealed partial class LocalContentProvider : ContentProviderBase, ILocalCo throw new Exception($"Path does not have parent: '{fileInfo.FullName}'"); var parent = new AbsolutePath(this, parentFullName, AbsolutePathType.Container); - return new FileElement( + var extensions = new ExtensionCollection() + { + new FileExtension(fileInfo.Length) + }; + + return new Element( fileInfo.Name, fileInfo.Name, fullName, @@ -240,7 +248,7 @@ public sealed partial class LocalContentProvider : ContentProviderBase, ILocalCo GetFileAttributes(fileInfo), this, Observable.Return(Enumerable.Empty()), - fileInfo.Length + extensions.AsReadOnly() ); } @@ -261,4 +269,28 @@ public sealed partial class LocalContentProvider : ContentProviderBase, ILocalCo if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux) && !path.StartsWith("/")) path = "/" + path; return new NativePath(path); } + + public override async Task GetContentAsync(IElement element, int? maxLength = null, + CancellationToken cancellationToken = default) + { + if (cancellationToken.IsCancellationRequested) return null; + if (!File.Exists(element.NativePath!.Path)) + throw new FileNotFoundException("File does not exist", element.NativePath.Path); + + await using var reader = new FileStream(element.NativePath!.Path, FileMode.Open, FileAccess.Read, FileShare.Read, + bufferSize: 1, // bufferSize == 1 used to avoid unnecessary buffer in FileStream + FileOptions.Asynchronous | FileOptions.SequentialScan); + + var realFileSize = new FileInfo(element.NativePath!.Path).Length; + + var size = maxLength ?? realFileSize switch + { + > int.MaxValue => int.MaxValue, + _ => (int)realFileSize + }; + var buffer = new byte[size]; + await reader.ReadAsync(buffer, 0, size); + + return buffer; + } } \ No newline at end of file