Browse ISOs

This commit is contained in:
2023-09-04 15:19:20 +02:00
parent 38979d8572
commit a323edafd3
29 changed files with 555 additions and 60 deletions

View File

@@ -28,7 +28,7 @@ public class ContainerSizeSizeScanProvider : ContentProviderBase, IContainerSize
PointInTime pointInTime,
bool forceResolve = false,
AbsolutePathType forceResolvePathType = AbsolutePathType.Unknown,
ItemInitializationSettings itemInitializationSettings = null
ItemInitializationSettings itemInitializationSettings = default
)
{
if (fullName.Path == ContentProviderName)

View File

@@ -0,0 +1,6 @@
namespace FileTime.App.Core.Services;
public interface IPreStartupHandler
{
Task InitAsync();
}

View File

@@ -5,15 +5,18 @@ namespace FileTime.App.Core.Services;
public class LifecycleService : ILifecycleService
{
private readonly IEnumerable<IPreStartupHandler> _preStartupHandlers;
private readonly IEnumerable<IExitHandler> _exitHandlers;
private readonly IEnumerable<IStartupHandler> _startupHandlers;
private readonly ILogger<LifecycleService> _logger;
public LifecycleService(
IEnumerable<IPreStartupHandler> preStartupHandlers,
IEnumerable<IStartupHandler> startupHandlers,
IEnumerable<IExitHandler> exitHandlers,
ILogger<LifecycleService> logger)
{
_preStartupHandlers = preStartupHandlers;
_exitHandlers = exitHandlers;
_startupHandlers = startupHandlers;
_logger = logger;
@@ -21,6 +24,18 @@ public class LifecycleService : ILifecycleService
public async Task InitStartupHandlersAsync()
{
foreach (var preStartupHandler in _preStartupHandlers)
{
try
{
await preStartupHandler.InitAsync();
}
catch (Exception ex)
{
_logger.LogError(ex, "Error while running pre-startup handler {Handler}", preStartupHandler.GetType().FullName);
}
}
foreach (var startupHandler in _startupHandlers)
{
try
@@ -29,7 +44,7 @@ public class LifecycleService : ILifecycleService
}
catch (Exception ex)
{
_logger.LogError(ex, "Error while running startup handler {Handler}", startupHandler?.GetType().FullName);
_logger.LogError(ex, "Error while running startup handler {Handler}", startupHandler.GetType().FullName);
}
}
}

View File

@@ -8,6 +8,7 @@ using FileTime.App.Core.Models.Enums;
using FileTime.App.Core.UserCommand;
using FileTime.App.Core.ViewModels;
using FileTime.App.FrequencyNavigation.Services;
using FileTime.Core.ContentAccess;
using FileTime.Core.Interactions;
using FileTime.Core.Models;
using FileTime.Core.Services;
@@ -29,6 +30,7 @@ public class NavigationUserCommandHandlerService : UserCommandHandlerServiceBase
private readonly IUserCommunicationService _userCommunicationService;
private readonly IFrequencyNavigationService _frequencyNavigationService;
private readonly ICommandPaletteService _commandPaletteService;
private readonly IContentProviderRegistry _contentProviderRegistry;
private readonly ILogger<NavigationUserCommandHandlerService> _logger;
private readonly ApplicationConfiguration _applicationConfiguration;
private ITabViewModel? _selectedTab;
@@ -46,6 +48,7 @@ public class NavigationUserCommandHandlerService : UserCommandHandlerServiceBase
IUserCommunicationService userCommunicationService,
IFrequencyNavigationService frequencyNavigationService,
ICommandPaletteService commandPaletteService,
IContentProviderRegistry contentProviderRegistry,
ILogger<NavigationUserCommandHandlerService> logger,
ApplicationConfiguration applicationConfiguration) : base(appState)
{
@@ -57,6 +60,7 @@ public class NavigationUserCommandHandlerService : UserCommandHandlerServiceBase
_userCommunicationService = userCommunicationService;
_frequencyNavigationService = frequencyNavigationService;
_commandPaletteService = commandPaletteService;
_contentProviderRegistry = contentProviderRegistry;
_logger = logger;
_applicationConfiguration = applicationConfiguration;
@@ -249,13 +253,40 @@ public class NavigationUserCommandHandlerService : UserCommandHandlerServiceBase
private async Task OpenSelected()
{
if (_currentSelectedItem?.Value is not IContainerViewModel containerViewModel || containerViewModel.Container is null)
var targetContainer = await GetSubContainer();
if (targetContainer is null
&& _currentSelectedItem?.Value is IContainerViewModel {Container: { } container})
{
targetContainer = container;
}
if (targetContainer is null)
{
return;
}
await _appState.SetRapidTravelTextAsync("");
if (_selectedTab?.Tab is { } tab)
{
await tab.SetCurrentLocation(containerViewModel.Container);
await tab.SetCurrentLocation(targetContainer);
}
async Task<IContainer?> GetSubContainer()
{
if (_currentSelectedItem?.Value is not {BaseItem: IElement element})
{
return null;
}
var subContentProvider = await _contentProviderRegistry.GetSubContentProviderForElement(element);
if (subContentProvider is null) return null;
var resolvedItem = await subContentProvider.GetItemByFullNameAsync(
element,
new FullName(""),
_timelessContentProvider.CurrentPointInTime.Value!);
return resolvedItem as IContainer;
}
}

View File

@@ -184,7 +184,9 @@ public partial class TabViewModel : ITabViewModel
private static IItem MapItem(AbsolutePath item)
{
var t = Task.Run(async () => await MapItemAsync(item));
var t = Task.Run(async () =>
await MapItemAsync(item)
?? throw new Exception("Could not resolve path " + item.Path.Path));
t.Wait();
return t.Result;
}

View File

@@ -6,6 +6,7 @@ using FileTime.Core;
using FileTime.Providers.Local;
using FileTime.Providers.LocalAdmin;
using FileTime.Providers.Remote;
using FileTime.Tools.VirtualDiskSources;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.DependencyInjection.Extensions;
@@ -28,6 +29,7 @@ public static class DependencyInjection
.AddAppCoreDependencies(configuration)
.AddLocalProviderServices()
.AddLocalAdminProviderServices(configuration)
.AddRemoteProviderServices();
.AddRemoteProviderServices()
.AddVirtualDisk();
}
}

View File

@@ -20,6 +20,7 @@
<ProjectReference Include="..\..\Core\FileTime.Core.Timeline\FileTime.Core.Timeline.csproj" />
<ProjectReference Include="..\..\Providers\FileTime.Providers.LocalAdmin\FileTime.Providers.LocalAdmin.csproj" />
<ProjectReference Include="..\..\Providers\FileTime.Providers.Local\FileTime.Providers.Local.csproj" />
<ProjectReference Include="..\..\Tools\FileTime.Tools.VirtualDiskSources\FileTime.Tools.VirtualDiskSources.csproj" />
<ProjectReference Include="..\FileTime.App.Core\FileTime.App.Core.csproj" />
</ItemGroup>

View File

@@ -24,7 +24,7 @@ public class SearchContentProvider : ContentProviderBase, ISearchContentProvider
PointInTime pointInTime,
bool forceResolve = false,
AbsolutePathType forceResolvePathType = AbsolutePathType.Unknown,
ItemInitializationSettings itemInitializationSettings = null
ItemInitializationSettings itemInitializationSettings = default
)
{
if (fullName.Path == ContentProviderName)

View File

@@ -1,10 +1,13 @@
using System.Collections.ObjectModel;
using FileTime.Core.Models;
namespace FileTime.Core.ContentAccess;
public interface IContentProviderRegistry
{
ReadOnlyObservableCollection<IContentProvider> ContentProviders { get; }
ReadOnlyObservableCollection<ISubContentProvider> SubContentProviders { get; }
void AddContentProvider(IContentProvider contentProvider);
void RemoveContentProvider(IContentProvider contentProvider);
Task<ISubContentProvider?> GetSubContentProviderForElement(IElement parentElement);
}

View File

@@ -0,0 +1,17 @@
using FileTime.Core.Enums;
using FileTime.Core.Models;
using FileTime.Core.Timeline;
namespace FileTime.Core.ContentAccess;
public interface ISubContentProvider
{
Task<IItem?> GetItemByFullNameAsync(
IElement parentElement,
FullName itemPath,
PointInTime pointInTime,
AbsolutePathType forceResolvePathType = AbsolutePathType.Unknown,
ItemInitializationSettings itemInitializationSettings = default);
Task<bool> CanHandleAsync(IElement parentElement);
}

View File

@@ -1,8 +1,10 @@
using System.Diagnostics;
using FileTime.Core.Enums;
using FileTime.Core.Timeline;
namespace FileTime.Core.Models;
[DebuggerDisplay("{Path.Path}, {Type}")]
public class AbsolutePath
{
public ITimelessContentProvider TimelessProvider { get; }

View File

@@ -3,4 +3,5 @@ namespace FileTime.Core.Models;
public static class Constants
{
public const char SeparatorChar = '/';
public const string SubContentProviderRootContainer = ":";
}

View File

@@ -1,5 +1,8 @@
using System.Diagnostics;
namespace FileTime.Core.Models;
[DebuggerDisplay("{Path}")]
public record FullName(string Path)
{
public FullName? GetParent()

View File

@@ -1,6 +1,6 @@
namespace FileTime.Core.Models;
public sealed class ItemInitializationSettings
public readonly struct ItemInitializationSettings
{
public bool SkipChildInitialization { get; init; }
public AbsolutePath? Parent { get; init; }

View File

@@ -1,5 +1,8 @@
using System.Diagnostics;
namespace FileTime.Core.Models;
[DebuggerDisplay("{Path}")]
public record NativePath(string Path)
{
public override string ToString() => Path;

View File

@@ -1,4 +1,4 @@
using System.Reactive.Subjects;
using DeclarativeProperty;
using FileTime.Core.Enums;
using FileTime.Core.Models;
@@ -6,7 +6,7 @@ namespace FileTime.Core.Timeline;
public interface ITimelessContentProvider
{
BehaviorSubject<PointInTime> CurrentPointInTime { get; }
IDeclarativeProperty<PointInTime> CurrentPointInTime { get; }
Task<IItem> GetItemByFullNameAsync(FullName fullName,
PointInTime? pointInTime,

View File

@@ -1,4 +1,7 @@
using System.Collections.ObjectModel;
using FileTime.Core.Enums;
using FileTime.Core.Models;
using FileTime.Core.Timeline;
using Microsoft.Extensions.DependencyInjection;
namespace FileTime.Core.ContentAccess;
@@ -8,13 +11,16 @@ public class ContentProviderRegistry : IContentProviderRegistry
private readonly object _lock = new();
private readonly IServiceProvider _serviceProvider;
private readonly ObservableCollection<IContentProvider> _contentProviders = new();
private readonly ObservableCollection<ISubContentProvider> _subContentProviders = new();
private readonly ReadOnlyObservableCollection<IContentProvider> _contentProvidersReadOnly;
private readonly ReadOnlyObservableCollection<ISubContentProvider> _subContentProvidersReadOnly;
private bool _initialized;
public ContentProviderRegistry(IServiceProvider serviceProvider)
{
_serviceProvider = serviceProvider;
_contentProvidersReadOnly = new ReadOnlyObservableCollection<IContentProvider>(_contentProviders);
_subContentProvidersReadOnly = new ReadOnlyObservableCollection<ISubContentProvider>(_subContentProviders);
}
public ReadOnlyObservableCollection<IContentProvider> ContentProviders
@@ -26,20 +32,33 @@ public class ContentProviderRegistry : IContentProviderRegistry
}
}
public ReadOnlyObservableCollection<ISubContentProvider> SubContentProviders
{
get
{
InitializeContentProviderListIfNeeded();
return _subContentProvidersReadOnly;
}
}
private void InitializeContentProviderListIfNeeded()
{
if (_initialized) return;
lock (_lock)
{
if (!_initialized)
{
foreach (var contentProvider in _serviceProvider.GetServices<IContentProvider>())
{
_contentProviders.Add(contentProvider);
}
if (_initialized) return;
_initialized = true;
foreach (var contentProvider in _serviceProvider.GetServices<IContentProvider>())
{
_contentProviders.Add(contentProvider);
}
foreach (var subContentProvider in _serviceProvider.GetServices<ISubContentProvider>())
{
_subContentProviders.Add(subContentProvider);
}
_initialized = true;
}
}
@@ -62,4 +81,20 @@ public class ContentProviderRegistry : IContentProviderRegistry
_contentProviders.Remove(contentProvider);
}
}
public async Task<ISubContentProvider?> GetSubContentProviderForElement(IElement parentElement)
{
var subContentProviders = _serviceProvider
.GetServices<ISubContentProvider>()
.ToList();
foreach (var subContentProvider in subContentProviders)
{
if(!await subContentProvider.CanHandleAsync(parentElement)) continue;
return subContentProvider;
}
return null;
}
}

View File

@@ -0,0 +1,46 @@
using FileTime.Core.Enums;
using FileTime.Core.Models;
using FileTime.Core.Timeline;
namespace FileTime.Core.ContentAccess;
public abstract class SubContentProviderBase : ContentProviderBase
{
private readonly IContentProvider _parentContentProvider;
protected SubContentProviderBase(
IContentProvider parentContentProvider,
string name,
ITimelessContentProvider timelessContentProvider) : base(name, timelessContentProvider)
{
_parentContentProvider = parentContentProvider;
}
public override async Task<IItem> GetItemByNativePathAsync(
NativePath nativePath,
PointInTime pointInTime,
bool forceResolve = false,
AbsolutePathType forceResolvePathType = AbsolutePathType.Unknown,
ItemInitializationSettings itemInitializationSettings = default)
=> await _parentContentProvider.GetItemByNativePathAsync(
nativePath,
pointInTime,
forceResolve,
forceResolvePathType,
itemInitializationSettings);
public override async ValueTask<NativePath> GetNativePathAsync(FullName fullName)
=> await _parentContentProvider.GetNativePathAsync(fullName);
public override FullName GetFullName(NativePath nativePath)
=> _parentContentProvider.GetFullName(nativePath);
public override async Task<byte[]?> GetContentAsync(IElement element, int? maxLength = null, CancellationToken cancellationToken = default)
=> await _parentContentProvider.GetContentAsync(element, maxLength, cancellationToken);
public override async Task<bool> CanHandlePathAsync(NativePath path)
=> await _parentContentProvider.CanHandlePathAsync(path);
public override VolumeSizeInfo? GetVolumeSizeInfo(FullName path)
=> _parentContentProvider.GetVolumeSizeInfo(path);
}

View File

@@ -1,4 +1,4 @@
using System.Reactive.Subjects;
using DeclarativeProperty;
using FileTime.Core.ContentAccess;
using FileTime.Core.Enums;
using FileTime.Core.Models;
@@ -10,8 +10,8 @@ public class TimelessContentProvider : ITimelessContentProvider
{
private readonly IContentProviderRegistry _contentProviderRegistry;
private readonly Lazy<IRootContentProvider> _rootContentProvider;
public BehaviorSubject<PointInTime> CurrentPointInTime { get; } = new(PointInTime.Present);
private readonly DeclarativeProperty<PointInTime> _currentPointInTime = new(PointInTime.Present);
public IDeclarativeProperty<PointInTime> CurrentPointInTime => _currentPointInTime;
public TimelessContentProvider(
IContentProviderRegistry contentProviderRegistry,

View File

@@ -147,6 +147,10 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "FileTime.Server.Tracker", "
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "FileTime.Server.Extensions.DependencyInjection", "Server\FileTime.Server.Extensions.DependencyInjection\FileTime.Server.Extensions.DependencyInjection.csproj", "{25AA9F04-EEEE-49C4-870B-CDFF71717687}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "FileTime.Tools.VirtualDiskSources", "Tools\FileTime.Tools.VirtualDiskSources\FileTime.Tools.VirtualDiskSources.csproj", "{DBCB10AD-9647-46AB-B9A1-3ACB9BCA46B9}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "FileTime.Tools.VirtualDiskSources.Abstractions", "Tools\FileTime.Tools.VirtualDiskSources.Abstractions\FileTime.Tools.VirtualDiskSources.Abstractions.csproj", "{53E5B762-B620-4106-B481-31A478A1E14F}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
@@ -405,6 +409,14 @@ Global
{25AA9F04-EEEE-49C4-870B-CDFF71717687}.Debug|Any CPU.Build.0 = Debug|Any CPU
{25AA9F04-EEEE-49C4-870B-CDFF71717687}.Release|Any CPU.ActiveCfg = Release|Any CPU
{25AA9F04-EEEE-49C4-870B-CDFF71717687}.Release|Any CPU.Build.0 = Release|Any CPU
{DBCB10AD-9647-46AB-B9A1-3ACB9BCA46B9}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{DBCB10AD-9647-46AB-B9A1-3ACB9BCA46B9}.Debug|Any CPU.Build.0 = Debug|Any CPU
{DBCB10AD-9647-46AB-B9A1-3ACB9BCA46B9}.Release|Any CPU.ActiveCfg = Release|Any CPU
{DBCB10AD-9647-46AB-B9A1-3ACB9BCA46B9}.Release|Any CPU.Build.0 = Release|Any CPU
{53E5B762-B620-4106-B481-31A478A1E14F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{53E5B762-B620-4106-B481-31A478A1E14F}.Debug|Any CPU.Build.0 = Debug|Any CPU
{53E5B762-B620-4106-B481-31A478A1E14F}.Release|Any CPU.ActiveCfg = Release|Any CPU
{53E5B762-B620-4106-B481-31A478A1E14F}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
@@ -474,6 +486,8 @@ Global
{0A29616B-5413-4DA9-96EC-B45D6AF632C9} = {3324D046-1E05-46B5-B1BA-82910D56B332}
{BD382403-86D8-4E99-B279-370FC22F059C} = {778AAF38-20FF-438C-A9C3-60850C8B5A27}
{25AA9F04-EEEE-49C4-870B-CDFF71717687} = {778AAF38-20FF-438C-A9C3-60850C8B5A27}
{DBCB10AD-9647-46AB-B9A1-3ACB9BCA46B9} = {8C3CFEFE-78A5-4940-B388-D15FCE02ECE9}
{53E5B762-B620-4106-B481-31A478A1E14F} = {8C3CFEFE-78A5-4940-B388-D15FCE02ECE9}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {859FB3DF-C60A-46B1-82E5-90274905D1EF}

View File

@@ -113,9 +113,9 @@ public static class Program
private static void NormalStartup(string[] args)
{
AppDomain.CurrentDomain.FirstChanceException += OnFirstChanceException;
/*AppDomain.CurrentDomain.FirstChanceException += OnFirstChanceException;
AppDomain.CurrentDomain.UnhandledException += OnAppDomainUnhandledException;
TaskScheduler.UnobservedTaskException += OnTaskSchedulerUnobservedTaskException;
TaskScheduler.UnobservedTaskException += OnTaskSchedulerUnobservedTaskException;*/
try
{
BuildAvaloniaApp()

View File

@@ -12,15 +12,18 @@ namespace FileTime.Providers.Local;
public sealed partial class LocalContentProvider : ContentProviderBase, ILocalContentProvider
{
private readonly ITimelessContentProvider _timelessContentProvider;
private readonly IContentProviderRegistry _contentProviderRegistry;
private readonly bool _isCaseInsensitive;
private readonly Lazy<ObservableCollection<RootDriveInfo>> _rootDriveInfos;
public LocalContentProvider(
ITimelessContentProvider timelessContentProvider,
IServiceProvider serviceProvider)
IServiceProvider serviceProvider,
IContentProviderRegistry contentProviderRegistry)
: base(LocalContentProviderConstants.ContentProviderId, timelessContentProvider)
{
_timelessContentProvider = timelessContentProvider;
_contentProviderRegistry = contentProviderRegistry;
_isCaseInsensitive = RuntimeInformation.IsOSPlatform(OSPlatform.Windows);
_rootDriveInfos = new Lazy<ObservableCollection<RootDriveInfo>>(() => serviceProvider.GetRequiredService<IRootDriveInfoService>().RootDriveInfos);
@@ -45,12 +48,6 @@ public sealed partial class LocalContentProvider : ContentProviderBase, ILocalCo
Items.Clear();
Items.AddRange(rootDirectories.Select(d => DirectoryToAbsolutePath(d, PointInTime.Present)));
/*Items.Edit(actions =>
{
actions.Clear();
actions.AddOrUpdate(rootDirectories.Select(d => DirectoryToAbsolutePath(d, PointInTime.Present)));
});*/
}
public override async Task<bool> CanHandlePathAsync(NativePath path)
@@ -79,11 +76,11 @@ public sealed partial class LocalContentProvider : ContentProviderBase, ILocalCo
return new VolumeSizeInfo(rootDriveInfo.Size, rootDriveInfo.Free);
}
public override Task<IItem> GetItemByNativePathAsync(NativePath nativePath,
public override async Task<IItem> GetItemByNativePathAsync(NativePath nativePath,
PointInTime pointInTime,
bool forceResolve = false,
AbsolutePathType forceResolvePathType = AbsolutePathType.Unknown,
ItemInitializationSettings? itemInitializationSettings = null)
ItemInitializationSettings itemInitializationSettings = default)
{
var path = nativePath.Path;
Exception? innerException;
@@ -91,19 +88,40 @@ public sealed partial class LocalContentProvider : ContentProviderBase, ILocalCo
{
if (path.Length == 0)
{
return Task.FromResult((IItem) this);
return this;
}
else if (Directory.Exists(path))
if (Directory.Exists(path))
{
return Task.FromResult((IItem) DirectoryToContainer(
new DirectoryInfo(path!.TrimEnd(Path.DirectorySeparatorChar) + Path.DirectorySeparatorChar),
return DirectoryToContainer(
new DirectoryInfo(path.TrimEnd(Path.DirectorySeparatorChar) + Path.DirectorySeparatorChar),
pointInTime,
itemInitializationSettings)
);
itemInitializationSettings);
}
else if (File.Exists(path))
{
return Task.FromResult((IItem) FileToElement(new FileInfo(path), pointInTime));
return FileToElement(new FileInfo(path), pointInTime);
}
var pathParts = path.Split(Path.DirectorySeparatorChar).SelectMany(p => p.Split(Constants.SeparatorChar)).ToArray();
for (var i = pathParts.Length - 1; i > 0; i--)
{
var possibleFile = string.Join(Path.DirectorySeparatorChar, pathParts.Take(i));
if (!File.Exists(possibleFile)) continue;
var element = FileToElement(new FileInfo(possibleFile), pointInTime);
var subContentProvider = await _contentProviderRegistry.GetSubContentProviderForElement(element);
if (subContentProvider is null) break;
var subPath = string.Join(Constants.SeparatorChar, pathParts.Skip(i));
var resolvedItem = await subContentProvider.GetItemByFullNameAsync(element, new FullName(subPath), pointInTime, forceResolvePathType, itemInitializationSettings);
if (resolvedItem is not null)
{
return resolvedItem;
}
}
var type = forceResolvePathType switch
@@ -134,14 +152,12 @@ public sealed partial class LocalContentProvider : ContentProviderBase, ILocalCo
return forceResolvePathType switch
{
AbsolutePathType.Container => Task.FromResult(
(IItem) CreateEmptyContainer(
nativePath,
pointInTime,
new List<Exception>() {innerException}
)
AbsolutePathType.Container => CreateEmptyContainer(
nativePath,
pointInTime,
new List<Exception> {innerException}
),
AbsolutePathType.Element => Task.FromResult(CreateEmptyElement(nativePath)),
AbsolutePathType.Element => CreateEmptyElement(nativePath),
_ => throw new Exception(
$"Could not resolve path '{nativePath.Path}' and could not force create, because {nameof(forceResolvePathType)} is {nameof(AbsolutePathType.Unknown)}.",
innerException)
@@ -211,10 +227,8 @@ public sealed partial class LocalContentProvider : ContentProviderBase, ILocalCo
}
private Container DirectoryToContainer(DirectoryInfo directoryInfo, PointInTime pointInTime,
ItemInitializationSettings? initializationSettings = null)
ItemInitializationSettings initializationSettings = default)
{
initializationSettings ??= new();
var fullName = GetFullName(directoryInfo.FullName);
var parentFullName = fullName.GetParent();
var parent =
@@ -336,17 +350,6 @@ public sealed partial class LocalContentProvider : ContentProviderBase, ILocalCo
}
}
private List<AbsolutePath> GetItemsByContainer(DirectoryInfo directoryInfo, PointInTime pointInTime)
=> directoryInfo
.GetDirectories()
.Select(d => DirectoryToAbsolutePath(d, pointInTime))
.Concat(
directoryInfo
.GetFiles()
.Select(f => FileToAbsolutePath(f, pointInTime))
)
.ToList();
private Element FileToElement(FileInfo fileInfo, PointInTime pointInTime)
{
var fullName = GetFullName(fileInfo);
@@ -387,7 +390,7 @@ public sealed partial class LocalContentProvider : ContentProviderBase, ILocalCo
nativePath.TrimStart(Constants.SeparatorChar).Split(Path.DirectorySeparatorChar)))
.TrimEnd(Constants.SeparatorChar))!;
public override ValueTask<NativePath> GetNativePathAsync(FullName fullName)
public override ValueTask<NativePath> GetNativePathAsync(FullName fullName)
=> ValueTask.FromResult(GetNativePath(fullName));
public NativePath GetNativePath(FullName fullName)

View File

@@ -0,0 +1,13 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net7.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
</PropertyGroup>
<ItemGroup>
<ProjectReference Include="..\..\Core\FileTime.Core.Abstraction\FileTime.Core.Abstraction.csproj" />
</ItemGroup>
</Project>

View File

@@ -0,0 +1,8 @@
using FileTime.Core.ContentAccess;
namespace FileTime.Tools.VirtualDiskSources.Abstractions;
public interface IVirtualDiskSubContentProvider : ISubContentProvider
{
}

View File

@@ -0,0 +1,14 @@
using FileTime.App.Core.Services;
namespace FileTime.Tools.VirtualDiskSources;
public class DiscUtilsInitializer : IPreStartupHandler
{
public Task InitAsync()
{
DiscUtils.Containers.SetupHelper.SetupContainers();
DiscUtils.FileSystems.SetupHelper.SetupFileSystems();
return Task.CompletedTask;
}
}

View File

@@ -0,0 +1,24 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net7.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
</PropertyGroup>
<ItemGroup>
<ProjectReference Include="..\..\AppCommon\FileTime.App.Core.Abstraction\FileTime.App.Core.Abstraction.csproj" />
<ProjectReference Include="..\..\Core\FileTime.Core.Abstraction\FileTime.Core.Abstraction.csproj" />
<ProjectReference Include="..\..\Core\FileTime.Core.ContentAccess\FileTime.Core.ContentAccess.csproj" />
<ProjectReference Include="..\..\Core\FileTime.Core.Models\FileTime.Core.Models.csproj" />
<ProjectReference Include="..\FileTime.Tools.VirtualDiskSources.Abstractions\FileTime.Tools.VirtualDiskSources.Abstractions.csproj" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="LTRData.DiscUtils.Containers" Version="1.0.28" />
<PackageReference Include="LTRData.DiscUtils.FileSystems" Version="1.0.28" />
<PackageReference Include="LTRData.DiscUtils.OpticalDisk" Version="1.0.28" />
<PackageReference Include="LTRData.DiscUtils.VirtualFileSystem" Version="1.0.28" />
</ItemGroup>
</Project>

View File

@@ -0,0 +1,18 @@
using FileTime.App.Core.Services;
using FileTime.Core.ContentAccess;
using FileTime.Tools.VirtualDiskSources.Abstractions;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.DependencyInjection.Extensions;
namespace FileTime.Tools.VirtualDiskSources;
public static class Startup
{
public static IServiceCollection AddVirtualDisk(this IServiceCollection services)
{
services.TryAddSingleton<IVirtualDiskSubContentProvider, VirtualDiskSubContentProvider>();
services.AddSingleton<ISubContentProvider>(sp => sp.GetRequiredService<IVirtualDiskSubContentProvider>());
services.AddSingleton<IPreStartupHandler, DiscUtilsInitializer>();
return services;
}
}

View File

@@ -0,0 +1,14 @@
using FileTime.Core.ContentAccess;
using FileTime.Core.Timeline;
namespace FileTime.Tools.VirtualDiskSources;
public class VirtualDiskContentProvider : SubContentProviderBase
{
public VirtualDiskContentProvider(
IContentProvider parentContentProvider,
ITimelessContentProvider timelessContentProvider)
: base(parentContentProvider, "virtual-disk", timelessContentProvider)
{
}
}

View File

@@ -0,0 +1,220 @@
using System.Collections.ObjectModel;
using DiscUtils;
using DiscUtils.Iso9660;
using DiscUtils.Udf;
using FileTime.Core.ContentAccess;
using FileTime.Core.Enums;
using FileTime.Core.Models;
using FileTime.Core.Timeline;
using FileTime.Tools.VirtualDiskSources.Abstractions;
namespace FileTime.Tools.VirtualDiskSources;
public class VirtualDiskSubContentProvider : IVirtualDiskSubContentProvider
{
private readonly IContentAccessorFactory _contentAccessorFactory;
private readonly ITimelessContentProvider _timelessContentProvider;
public VirtualDiskSubContentProvider(
IContentAccessorFactory contentAccessorFactory,
ITimelessContentProvider timelessContentProvider
)
{
_contentAccessorFactory = contentAccessorFactory;
_timelessContentProvider = timelessContentProvider;
}
public Task<bool> CanHandleAsync(IElement parentElement)
=> Task.FromResult(parentElement.NativePath?.Path.EndsWith(".iso", StringComparison.OrdinalIgnoreCase) ?? false);
public async Task<IItem?> GetItemByFullNameAsync(
IElement parentElement,
FullName itemPath,
PointInTime pointInTime,
AbsolutePathType forceResolvePathType = AbsolutePathType.Unknown,
ItemInitializationSettings itemInitializationSettings = default)
{
var contentReaderFactory = _contentAccessorFactory.GetContentReaderFactory(parentElement.Provider);
var reader = await contentReaderFactory.CreateContentReaderAsync(parentElement);
await using var readerStream = reader.AsStream();
var discReader = new UdfReader(readerStream);
if (itemPath.Path.Length == 0 || itemPath.Path == Constants.SubContentProviderRootContainer)
{
var rootFullNameBase = parentElement.FullName!.Path + Constants.SeparatorChar + Constants.SubContentProviderRootContainer;
var rootNativePathBase = parentElement.NativePath!.Path + Constants.SeparatorChar + Constants.SubContentProviderRootContainer;
var rootFullName = new FullName(rootFullNameBase);
var rootNativePath = new NativePath(rootNativePathBase);
return CreateContainer(discReader.Root,
rootFullName,
rootNativePath,
parentElement.Provider,
parentElement.Parent!,
parentElement.PointInTime,
itemInitializationSettings);
}
return ResolveNonRootChild(discReader, parentElement, itemPath, pointInTime, itemInitializationSettings);
}
private IItem? ResolveNonRootChild(
UdfReader discReader,
IElement parentElement,
FullName itemPath,
PointInTime pointInTime,
ItemInitializationSettings itemInitializationSettings = default)
{
var pathParts = itemPath.Path.Split(Constants.SeparatorChar);
var childFullNameBase = parentElement.FullName!.Path + Constants.SeparatorChar + itemPath.Path;
var childNativePathBase = parentElement.NativePath!.Path + Constants.SeparatorChar + itemPath.Path;
var childFullName = new FullName(childFullNameBase);
var childNativePath = new NativePath(childNativePathBase);
var parent = new AbsolutePath(_timelessContentProvider, pointInTime, childFullName.GetParent()!, AbsolutePathType.Container);
var container = discReader.Root;
for (var i = 1; i < pathParts.Length - 1; i++)
{
if (container is null) break;
container = container.GetDirectories().FirstOrDefault(d => d.Name == pathParts[i]);
}
if (container is null) return null;
if (container.GetDirectories().FirstOrDefault(d => d.Name == pathParts[^1]) is { } childContainer)
{
return CreateContainer(
childContainer,
childFullName,
childNativePath,
parentElement.Provider,
parent,
pointInTime,
itemInitializationSettings
);
}
if (container.GetFiles().FirstOrDefault(d => d.Name == pathParts[^1]) is { } childElement)
{
return CreateElement(
childElement,
childFullName,
childNativePath,
parentElement.Provider,
parent,
pointInTime
);
}
return null;
}
private IContainer CreateContainer(
DiscDirectoryInfo sourceContainer,
FullName fullname,
NativePath nativePath,
IContentProvider parentContentProvider,
AbsolutePath parent,
PointInTime pointInTime,
ItemInitializationSettings initializationSettings)
{
var children = new ObservableCollection<AbsolutePath>();
var exceptions = new ObservableCollection<Exception>();
var container = new Container(
sourceContainer.Name,
sourceContainer.Name,
fullname,
nativePath,
parent,
true,
true,
sourceContainer.CreationTime,
sourceContainer.LastWriteTime,
SupportsDelete.False,
false,
FormatAttributes(sourceContainer.Attributes),
new VirtualDiskContentProvider(parentContentProvider, _timelessContentProvider),
false,
pointInTime,
exceptions,
new ReadOnlyExtensionCollection(new ExtensionCollection()),
children
);
if (!initializationSettings.SkipChildInitialization)
{
ThreadPool.QueueUserWorkItem(_ => LoadChildren(container, sourceContainer, children, pointInTime, exceptions));
}
return container;
}
private void LoadChildren(
Container container,
DiscDirectoryInfo sourceContainer,
ObservableCollection<AbsolutePath> children,
PointInTime pointInTime,
ObservableCollection<Exception> exceptions
)
{
foreach (var discDirectoryInfo in sourceContainer.GetDirectories())
{
children.Add(new AbsolutePath(
_timelessContentProvider,
pointInTime,
container.FullName.GetChild(discDirectoryInfo.Name),
AbsolutePathType.Container)
);
}
foreach (var fileInfo in sourceContainer.GetFiles())
{
children.Add(new AbsolutePath(
_timelessContentProvider,
pointInTime,
container.FullName.GetChild(fileInfo.Name),
AbsolutePathType.Element)
);
}
}
private IElement CreateElement(DiscFileInfo childElement,
FullName fullname,
NativePath nativePath,
IContentProvider parentContentProvider,
AbsolutePath parent,
PointInTime pointInTime)
{
var element = new Element(
childElement.Name,
childElement.Name,
fullname,
nativePath,
parent,
true,
true,
childElement.CreationTime,
childElement.LastWriteTime,
SupportsDelete.False,
false,
FormatAttributes(childElement.Attributes),
childElement.Length,
new VirtualDiskContentProvider(parentContentProvider, _timelessContentProvider),
pointInTime,
new ObservableCollection<Exception>(),
new ReadOnlyExtensionCollection(new ExtensionCollection())
);
return element;
}
private string FormatAttributes(FileAttributes attributes)
{
return "";
}
}