Content Reader/Writer, StreamCopyCommandHandler
This commit is contained in:
@@ -1,6 +1,6 @@
|
|||||||
using FileTime.App.Core.Models;
|
|
||||||
using FileTime.App.Core.Models.Enums;
|
using FileTime.App.Core.Models.Enums;
|
||||||
using FileTime.Core.Models;
|
using FileTime.Core.Models;
|
||||||
|
using FileTime.Core.Models.Extensions;
|
||||||
using InitableService;
|
using InitableService;
|
||||||
|
|
||||||
namespace FileTime.App.Core.ViewModels;
|
namespace FileTime.App.Core.ViewModels;
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ using FileTime.App.Core.Services.Persistence;
|
|||||||
using FileTime.Core.Command;
|
using FileTime.Core.Command;
|
||||||
using FileTime.Core.Command.CreateContainer;
|
using FileTime.Core.Command.CreateContainer;
|
||||||
using FileTime.Core.Command.CreateElement;
|
using FileTime.Core.Command.CreateElement;
|
||||||
|
using FileTime.Core.CommandHandlers;
|
||||||
using FileTime.Core.ContentAccess;
|
using FileTime.Core.ContentAccess;
|
||||||
using FileTime.Core.Services;
|
using FileTime.Core.Services;
|
||||||
using FileTime.Core.Timeline;
|
using FileTime.Core.Timeline;
|
||||||
@@ -34,10 +35,11 @@ public static class DependencyInjection
|
|||||||
return serviceCollection
|
return serviceCollection
|
||||||
.AddCoreAppServices()
|
.AddCoreAppServices()
|
||||||
.AddLocalServices()
|
.AddLocalServices()
|
||||||
.RegisterCommands();
|
.RegisterCommands()
|
||||||
|
.AddDefaultCommandHandlers();
|
||||||
}
|
}
|
||||||
|
|
||||||
public static IServiceCollection RegisterCommands(this IServiceCollection serviceCollection)
|
private static IServiceCollection RegisterCommands(this IServiceCollection serviceCollection)
|
||||||
{
|
{
|
||||||
return serviceCollection
|
return serviceCollection
|
||||||
.AddTransient<CreateContainerCommand>()
|
.AddTransient<CreateContainerCommand>()
|
||||||
|
|||||||
@@ -14,6 +14,7 @@
|
|||||||
<PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="6.0.0" />
|
<PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="6.0.0" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
|
<ProjectReference Include="..\..\Core\FileTime.Core.CommandHandlers\FileTime.Core.CommandHandlers.csproj" />
|
||||||
<ProjectReference Include="..\..\Core\FileTime.Core.Timeline\FileTime.Core.Timeline.csproj" />
|
<ProjectReference Include="..\..\Core\FileTime.Core.Timeline\FileTime.Core.Timeline.csproj" />
|
||||||
<ProjectReference Include="..\..\Providers\FileTime.Providers.Local\FileTime.Providers.Local.csproj" />
|
<ProjectReference Include="..\..\Providers\FileTime.Providers.Local\FileTime.Providers.Local.csproj" />
|
||||||
<ProjectReference Include="..\FileTime.App.Core\FileTime.App.Core.csproj" />
|
<ProjectReference Include="..\FileTime.App.Core\FileTime.App.Core.csproj" />
|
||||||
|
|||||||
@@ -1,5 +1,3 @@
|
|||||||
using FileTime.Core.Timeline;
|
|
||||||
|
|
||||||
namespace FileTime.Core.Command;
|
namespace FileTime.Core.Command;
|
||||||
|
|
||||||
public interface ICommandHandler
|
public interface ICommandHandler
|
||||||
|
|||||||
@@ -0,0 +1,80 @@
|
|||||||
|
namespace FileTime.Core.ContentAccess;
|
||||||
|
|
||||||
|
public class ContentAccessStream : Stream
|
||||||
|
{
|
||||||
|
private readonly IContentReader? _contentReader;
|
||||||
|
private readonly IContentWriter? _contentWriter;
|
||||||
|
public override bool CanRead => _contentReader != null;
|
||||||
|
|
||||||
|
public override bool CanSeek => _contentReader != null;
|
||||||
|
|
||||||
|
public override bool CanWrite => _contentWriter != null;
|
||||||
|
|
||||||
|
public override long Length => throw new NotImplementedException();
|
||||||
|
|
||||||
|
public override long Position
|
||||||
|
{
|
||||||
|
get => throw new NotImplementedException();
|
||||||
|
set => throw new NotImplementedException();
|
||||||
|
}
|
||||||
|
|
||||||
|
public ContentAccessStream(IContentReader contentReader)
|
||||||
|
{
|
||||||
|
_contentReader = contentReader;
|
||||||
|
}
|
||||||
|
|
||||||
|
public ContentAccessStream(IContentWriter contentWriter)
|
||||||
|
{
|
||||||
|
_contentWriter = contentWriter;
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void Flush()
|
||||||
|
{
|
||||||
|
if (_contentWriter == null) throw new NotSupportedException();
|
||||||
|
Task.Run(async () => await _contentWriter.FlushAsync()).Wait();
|
||||||
|
}
|
||||||
|
|
||||||
|
public override int Read(byte[] buffer, int offset, int count)
|
||||||
|
{
|
||||||
|
if (_contentReader == null) throw new IOException("This stream is not readable");
|
||||||
|
var dataTask = Task.Run(async () => await _contentReader.ReadBytesAsync(count, offset));
|
||||||
|
dataTask.Wait();
|
||||||
|
var data = dataTask.Result;
|
||||||
|
|
||||||
|
if (data.Length > count) throw new Exception("More bytes has been read than requested");
|
||||||
|
Array.Copy(data, buffer, data.Length);
|
||||||
|
return data.Length;
|
||||||
|
}
|
||||||
|
|
||||||
|
public override long Seek(long offset, SeekOrigin origin)
|
||||||
|
{
|
||||||
|
if (_contentReader == null) throw new NotSupportedException();
|
||||||
|
|
||||||
|
var newPosition = origin switch
|
||||||
|
{
|
||||||
|
SeekOrigin.Begin => offset,
|
||||||
|
SeekOrigin.Current => _contentReader.Position ?? 0 + offset,
|
||||||
|
_ => throw new NotSupportedException()
|
||||||
|
};
|
||||||
|
_contentReader.SetPosition(newPosition);
|
||||||
|
return newPosition;
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void SetLength(long value)
|
||||||
|
{
|
||||||
|
throw new NotImplementedException();
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void Write(byte[] buffer, int offset, int count)
|
||||||
|
{
|
||||||
|
if (_contentWriter == null) throw new NotSupportedException();
|
||||||
|
var data = buffer;
|
||||||
|
if (buffer.Length != count)
|
||||||
|
{
|
||||||
|
data = new byte[count];
|
||||||
|
Array.Copy(buffer, data, count);
|
||||||
|
}
|
||||||
|
|
||||||
|
Task.Run(async () => await _contentWriter.WriteBytesAsync(data, offset)).Wait();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -4,4 +4,8 @@ public interface IContentAccessorFactory
|
|||||||
{
|
{
|
||||||
IItemCreator<TContentProvider> GetItemCreator<TContentProvider>() where TContentProvider : IContentProvider;
|
IItemCreator<TContentProvider> GetItemCreator<TContentProvider>() where TContentProvider : IContentProvider;
|
||||||
IItemCreator GetItemCreator(IContentProvider provider);
|
IItemCreator GetItemCreator(IContentProvider provider);
|
||||||
|
IContentReaderFactory<TContentProvider> GetContentReaderFactory<TContentProvider>() where TContentProvider : IContentProvider;
|
||||||
|
IContentReaderFactory GetContentReaderFactory(IContentProvider provider);
|
||||||
|
IContentWriterFactory<TContentProvider> GetContentWriterFactory<TContentProvider>() where TContentProvider : IContentProvider;
|
||||||
|
IContentWriterFactory GetContentWriterFactory(IContentProvider provider);
|
||||||
}
|
}
|
||||||
@@ -7,6 +7,8 @@ namespace FileTime.Core.ContentAccess;
|
|||||||
|
|
||||||
public interface IContentProvider : IContainer, IOnContainerEnter
|
public interface IContentProvider : IContainer, IOnContainerEnter
|
||||||
{
|
{
|
||||||
|
bool SupportsContentStreams { get; }
|
||||||
|
|
||||||
Task<IItem> GetItemByFullNameAsync(
|
Task<IItem> GetItemByFullNameAsync(
|
||||||
FullName fullName,
|
FullName fullName,
|
||||||
PointInTime pointInTime,
|
PointInTime pointInTime,
|
||||||
@@ -25,4 +27,5 @@ public interface IContentProvider : IContainer, IOnContainerEnter
|
|||||||
|
|
||||||
Task<byte[]?> GetContentAsync(IElement element, int? maxLength = null, CancellationToken cancellationToken = default);
|
Task<byte[]?> GetContentAsync(IElement element, int? maxLength = null, CancellationToken cancellationToken = default);
|
||||||
bool CanHandlePath(NativePath path);
|
bool CanHandlePath(NativePath path);
|
||||||
|
bool CanHandlePath(FullName path);
|
||||||
}
|
}
|
||||||
@@ -0,0 +1,8 @@
|
|||||||
|
namespace FileTime.Core.ContentAccess;
|
||||||
|
|
||||||
|
public interface IContentProviderRegistry
|
||||||
|
{
|
||||||
|
IEnumerable<IContentProvider> ContentProviders { get; }
|
||||||
|
void AddContentProvider(IContentProvider contentProvider);
|
||||||
|
void RemoveContentProvider(IContentProvider contentProvider);
|
||||||
|
}
|
||||||
@@ -0,0 +1,10 @@
|
|||||||
|
namespace FileTime.Core.ContentAccess;
|
||||||
|
|
||||||
|
public interface IContentReader : IDisposable
|
||||||
|
{
|
||||||
|
int PreferredBufferSize { get; }
|
||||||
|
long? Position { get; }
|
||||||
|
|
||||||
|
Task<byte[]> ReadBytesAsync(int bufferSize, int? offset = null);
|
||||||
|
void SetPosition(long position);
|
||||||
|
}
|
||||||
@@ -0,0 +1,12 @@
|
|||||||
|
using FileTime.Core.Models;
|
||||||
|
|
||||||
|
namespace FileTime.Core.ContentAccess;
|
||||||
|
|
||||||
|
public interface IContentReaderFactory
|
||||||
|
{
|
||||||
|
Task<IContentReader> CreateContentReaderAsync(IElement element);
|
||||||
|
}
|
||||||
|
|
||||||
|
public interface IContentReaderFactory<in TContentProvider> : IContentReaderFactory where TContentProvider : IContentProvider
|
||||||
|
{
|
||||||
|
}
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
namespace FileTime.Core.ContentAccess;
|
||||||
|
|
||||||
|
public interface IContentWriter : IDisposable
|
||||||
|
{
|
||||||
|
int PreferredBufferSize { get; }
|
||||||
|
|
||||||
|
Task WriteBytesAsync(byte[] data, int? index = null);
|
||||||
|
Task FlushAsync();
|
||||||
|
}
|
||||||
@@ -0,0 +1,12 @@
|
|||||||
|
using FileTime.Core.Models;
|
||||||
|
|
||||||
|
namespace FileTime.Core.ContentAccess;
|
||||||
|
|
||||||
|
public interface IContentWriterFactory
|
||||||
|
{
|
||||||
|
Task<IContentWriter> CreateContentWriterAsync(IElement element);
|
||||||
|
}
|
||||||
|
|
||||||
|
public interface IContentWriterFactory<in TContentProvider> : IContentWriterFactory where TContentProvider : IContentProvider
|
||||||
|
{
|
||||||
|
}
|
||||||
@@ -53,4 +53,7 @@ public class AbsolutePath
|
|||||||
|
|
||||||
public AbsolutePath GetChild(string childName, AbsolutePathType type)
|
public AbsolutePath GetChild(string childName, AbsolutePathType type)
|
||||||
=> new (TimelessProvider, PointInTime, Path.GetChild(childName), type);
|
=> new (TimelessProvider, PointInTime, Path.GetChild(childName), type);
|
||||||
|
|
||||||
|
public AbsolutePath? GetParent()
|
||||||
|
=> Path.GetParent() is { } parentFullName ? new (TimelessProvider, PointInTime, parentFullName, AbsolutePathType.Container) : null;
|
||||||
}
|
}
|
||||||
@@ -0,0 +1,14 @@
|
|||||||
|
<Project Sdk="Microsoft.NET.Sdk">
|
||||||
|
|
||||||
|
<PropertyGroup>
|
||||||
|
<TargetFramework>net6.0</TargetFramework>
|
||||||
|
<ImplicitUsings>enable</ImplicitUsings>
|
||||||
|
<Nullable>enable</Nullable>
|
||||||
|
</PropertyGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<ProjectReference Include="..\FileTime.Core.Abstraction\FileTime.Core.Abstraction.csproj" />
|
||||||
|
<ProjectReference Include="..\FileTime.Core.Command\FileTime.Core.Command.csproj" />
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
</Project>
|
||||||
13
src/Core/FileTime.Core.CommandHandlers/Startup.cs
Normal file
13
src/Core/FileTime.Core.CommandHandlers/Startup.cs
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
using FileTime.Core.Command;
|
||||||
|
using Microsoft.Extensions.DependencyInjection;
|
||||||
|
|
||||||
|
namespace FileTime.Core.CommandHandlers;
|
||||||
|
|
||||||
|
public static class Startup
|
||||||
|
{
|
||||||
|
public static IServiceCollection AddDefaultCommandHandlers(this IServiceCollection serviceCollection)
|
||||||
|
{
|
||||||
|
return serviceCollection
|
||||||
|
.AddSingleton<ICommandHandler, StreamCopyCommandHandler>();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,85 @@
|
|||||||
|
using FileTime.Core.Command;
|
||||||
|
using FileTime.Core.Command.Copy;
|
||||||
|
using FileTime.Core.ContentAccess;
|
||||||
|
using FileTime.Core.Extensions;
|
||||||
|
using FileTime.Core.Models;
|
||||||
|
|
||||||
|
namespace FileTime.Core.CommandHandlers;
|
||||||
|
|
||||||
|
public class StreamCopyCommandHandler : ICommandHandler
|
||||||
|
{
|
||||||
|
private readonly IContentProviderRegistry _contentProviderRegistry;
|
||||||
|
private readonly IContentAccessorFactory _contentAccessorFactory;
|
||||||
|
|
||||||
|
public StreamCopyCommandHandler(
|
||||||
|
IContentProviderRegistry contentProviderRegistry,
|
||||||
|
IContentAccessorFactory contentAccessorFactory)
|
||||||
|
{
|
||||||
|
_contentProviderRegistry = contentProviderRegistry;
|
||||||
|
_contentAccessorFactory = contentAccessorFactory;
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool CanHandle(ICommand command)
|
||||||
|
{
|
||||||
|
if (command is not CopyCommand copyCommand) return false;
|
||||||
|
|
||||||
|
var targetSupportsContentStream =
|
||||||
|
_contentProviderRegistry
|
||||||
|
.ContentProviders
|
||||||
|
.FirstOrDefault(p => p.CanHandlePath(copyCommand.Target!))
|
||||||
|
?.SupportsContentStreams ?? false;
|
||||||
|
|
||||||
|
var allSourcesSupportsContentStream =
|
||||||
|
copyCommand
|
||||||
|
.Sources
|
||||||
|
.Select(s =>
|
||||||
|
_contentProviderRegistry
|
||||||
|
.ContentProviders
|
||||||
|
.FirstOrDefault(p => p.CanHandlePath(s))
|
||||||
|
)
|
||||||
|
.All(p => p?.SupportsContentStreams ?? false);
|
||||||
|
|
||||||
|
return targetSupportsContentStream && allSourcesSupportsContentStream;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task ExecuteAsync(ICommand command)
|
||||||
|
{
|
||||||
|
if (command is not CopyCommand copyCommand) throw new ArgumentException($"Can not execute command of type '{command.GetType()}'.");
|
||||||
|
|
||||||
|
await copyCommand.ExecuteAsync(CopyElement);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task CopyElement(AbsolutePath sourcePath, AbsolutePath targetPath, CopyCommandContext copyCommandContext)
|
||||||
|
{
|
||||||
|
var parent = (IContainer?) (await targetPath.GetParent()!.ResolveAsync())!;
|
||||||
|
var elementName = targetPath.Path;
|
||||||
|
var parentChildren = await parent.Items.GetItemsAsync();
|
||||||
|
if (parentChildren!.All(e => e.Path.GetName() != elementName.GetName()))
|
||||||
|
{
|
||||||
|
var itemCreator = _contentAccessorFactory.GetItemCreator(parent.Provider);
|
||||||
|
await itemCreator.CreateElementAsync(parent.Provider, elementName);
|
||||||
|
}
|
||||||
|
|
||||||
|
var source = (IElement?) (await sourcePath.ResolveAsync())!;
|
||||||
|
var target = (IElement?) (await targetPath.ResolveAsync())!;
|
||||||
|
|
||||||
|
using var reader = await _contentAccessorFactory.GetContentReaderFactory(source.Provider).CreateContentReaderAsync(source);
|
||||||
|
using var writer = await _contentAccessorFactory.GetContentWriterFactory(target.Provider).CreateContentWriterAsync(target);
|
||||||
|
|
||||||
|
byte[] dataRead;
|
||||||
|
var currentProgress = 0L;
|
||||||
|
|
||||||
|
do
|
||||||
|
{
|
||||||
|
dataRead = await reader.ReadBytesAsync(writer.PreferredBufferSize);
|
||||||
|
if (dataRead.Length > 0)
|
||||||
|
{
|
||||||
|
await writer.WriteBytesAsync(dataRead);
|
||||||
|
await writer.FlushAsync();
|
||||||
|
currentProgress += dataRead.LongLength;
|
||||||
|
copyCommandContext.CurrentProgress?.SetProgress(currentProgress);
|
||||||
|
await copyCommandContext.UpdateProgress();
|
||||||
|
}
|
||||||
|
} while (dataRead.Length > 0);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -24,4 +24,32 @@ public class ContentAccessorFactory : IContentAccessorFactory
|
|||||||
|
|
||||||
return (IItemCreator)_serviceProvider.GetRequiredService(genericType);
|
return (IItemCreator)_serviceProvider.GetRequiredService(genericType);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public IContentReaderFactory<TContentProvider> GetContentReaderFactory<TContentProvider>() where TContentProvider : IContentProvider
|
||||||
|
{
|
||||||
|
var genericType = typeof(IContentReaderFactory<>).MakeGenericType(typeof(TContentProvider));
|
||||||
|
|
||||||
|
return (IContentReaderFactory<TContentProvider>)_serviceProvider.GetRequiredService(genericType);
|
||||||
|
}
|
||||||
|
|
||||||
|
public IContentReaderFactory GetContentReaderFactory(IContentProvider provider)
|
||||||
|
{
|
||||||
|
var genericType = typeof(IContentReaderFactory<>).MakeGenericType(provider.GetType());
|
||||||
|
|
||||||
|
return (IContentReaderFactory)_serviceProvider.GetRequiredService(genericType);
|
||||||
|
}
|
||||||
|
|
||||||
|
public IContentWriterFactory<TContentProvider> GetContentWriterFactory<TContentProvider>() where TContentProvider : IContentProvider
|
||||||
|
{
|
||||||
|
var genericType = typeof(IContentWriterFactory<>).MakeGenericType(typeof(TContentProvider));
|
||||||
|
|
||||||
|
return (IContentWriterFactory<TContentProvider>)_serviceProvider.GetRequiredService(genericType);
|
||||||
|
}
|
||||||
|
|
||||||
|
public IContentWriterFactory GetContentWriterFactory(IContentProvider provider)
|
||||||
|
{
|
||||||
|
var genericType = typeof(IContentWriterFactory<>).MakeGenericType(provider.GetType());
|
||||||
|
|
||||||
|
return (IContentWriterFactory)_serviceProvider.GetRequiredService(genericType);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -61,6 +61,8 @@ public abstract class ContentProviderBase : IContentProvider
|
|||||||
|
|
||||||
public virtual Task OnEnter() => Task.CompletedTask;
|
public virtual Task OnEnter() => Task.CompletedTask;
|
||||||
|
|
||||||
|
public virtual bool SupportsContentStreams { get; protected set; }
|
||||||
|
|
||||||
public virtual async Task<IItem> GetItemByFullNameAsync(FullName fullName,
|
public virtual async Task<IItem> GetItemByFullNameAsync(FullName fullName,
|
||||||
PointInTime pointInTime,
|
PointInTime pointInTime,
|
||||||
bool forceResolve = false,
|
bool forceResolve = false,
|
||||||
@@ -83,4 +85,5 @@ public abstract class ContentProviderBase : IContentProvider
|
|||||||
CancellationToken cancellationToken = default);
|
CancellationToken cancellationToken = default);
|
||||||
|
|
||||||
public abstract bool CanHandlePath(NativePath path);
|
public abstract bool CanHandlePath(NativePath path);
|
||||||
|
public bool CanHandlePath(FullName path) => CanHandlePath(GetNativePath(path));
|
||||||
}
|
}
|
||||||
@@ -0,0 +1,21 @@
|
|||||||
|
using Microsoft.Extensions.DependencyInjection;
|
||||||
|
|
||||||
|
namespace FileTime.Core.ContentAccess;
|
||||||
|
|
||||||
|
public class ContentProviderRegistry : IContentProviderRegistry
|
||||||
|
{
|
||||||
|
private readonly IServiceProvider _serviceProvider;
|
||||||
|
private readonly Lazy<IList<IContentProvider>> _defaultContentProviders;
|
||||||
|
private readonly List<IContentProvider> _additionalContentProviders = new();
|
||||||
|
|
||||||
|
public ContentProviderRegistry(IServiceProvider serviceProvider)
|
||||||
|
{
|
||||||
|
_serviceProvider = serviceProvider;
|
||||||
|
_defaultContentProviders = new Lazy<IList<IContentProvider>>(() => serviceProvider.GetServices<IContentProvider>().ToList());
|
||||||
|
}
|
||||||
|
|
||||||
|
public IEnumerable<IContentProvider> ContentProviders => _defaultContentProviders.Value.Concat(_additionalContentProviders);
|
||||||
|
|
||||||
|
public void AddContentProvider(IContentProvider contentProvider) => _additionalContentProviders.Add(contentProvider);
|
||||||
|
public void RemoveContentProvider(IContentProvider contentProvider) => _additionalContentProviders.Remove(contentProvider);
|
||||||
|
}
|
||||||
@@ -2,20 +2,18 @@ using System.Reactive.Subjects;
|
|||||||
using FileTime.Core.ContentAccess;
|
using FileTime.Core.ContentAccess;
|
||||||
using FileTime.Core.Enums;
|
using FileTime.Core.Enums;
|
||||||
using FileTime.Core.Models;
|
using FileTime.Core.Models;
|
||||||
using Microsoft.Extensions.DependencyInjection;
|
|
||||||
|
|
||||||
namespace FileTime.Core.Timeline;
|
namespace FileTime.Core.Timeline;
|
||||||
|
|
||||||
public class TimelessContentProvider : ITimelessContentProvider
|
public class TimelessContentProvider : ITimelessContentProvider
|
||||||
{
|
{
|
||||||
private readonly Lazy<List<IContentProvider>> _contentProviders;
|
private readonly IContentProviderRegistry _contentProviderRegistry;
|
||||||
|
|
||||||
public BehaviorSubject<PointInTime> CurrentPointInTime { get; } = new(PointInTime.Present);
|
public BehaviorSubject<PointInTime> CurrentPointInTime { get; } = new(PointInTime.Present);
|
||||||
|
|
||||||
public TimelessContentProvider(IServiceProvider serviceProvider)
|
public TimelessContentProvider(IContentProviderRegistry contentProviderRegistry)
|
||||||
{
|
{
|
||||||
_contentProviders =
|
_contentProviderRegistry = contentProviderRegistry;
|
||||||
new Lazy<List<IContentProvider>>(() => serviceProvider.GetServices<IContentProvider>().ToList());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<IItem> GetItemByFullNameAsync(FullName fullName, PointInTime? pointInTime,
|
public async Task<IItem> GetItemByFullNameAsync(FullName fullName, PointInTime? pointInTime,
|
||||||
@@ -25,7 +23,7 @@ public class TimelessContentProvider : ITimelessContentProvider
|
|||||||
{
|
{
|
||||||
//TODO time modifications
|
//TODO time modifications
|
||||||
var contentProviderName = fullName.Path.Split(Constants.SeparatorChar).FirstOrDefault();
|
var contentProviderName = fullName.Path.Split(Constants.SeparatorChar).FirstOrDefault();
|
||||||
var contentProvider = _contentProviders.Value.FirstOrDefault(p => p.Name == contentProviderName);
|
var contentProvider = _contentProviderRegistry.ContentProviders.FirstOrDefault(p => p.Name == contentProviderName);
|
||||||
|
|
||||||
if (contentProvider is null)
|
if (contentProvider is null)
|
||||||
throw new Exception($"No content provider is found for name '{contentProviderName}'");
|
throw new Exception($"No content provider is found for name '{contentProviderName}'");
|
||||||
@@ -37,7 +35,7 @@ public class TimelessContentProvider : ITimelessContentProvider
|
|||||||
|
|
||||||
public async Task<IItem?> GetItemByNativePathAsync(NativePath nativePath, PointInTime? pointInTime = null)
|
public async Task<IItem?> GetItemByNativePathAsync(NativePath nativePath, PointInTime? pointInTime = null)
|
||||||
{
|
{
|
||||||
foreach (var contentProvider in _contentProviders.Value)
|
foreach (var contentProvider in _contentProviderRegistry.ContentProviders)
|
||||||
{
|
{
|
||||||
if(!contentProvider.CanHandlePath(nativePath)) continue;
|
if(!contentProvider.CanHandlePath(nativePath)) continue;
|
||||||
|
|
||||||
|
|||||||
@@ -57,6 +57,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "FileTime.Core.Timeline", "C
|
|||||||
EndProject
|
EndProject
|
||||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "FileTime.Core.ContentAccess", "Core\FileTime.Core.ContentAccess\FileTime.Core.ContentAccess.csproj", "{88BBB541-7306-44AE-95B1-0F8765AF1D4E}"
|
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "FileTime.Core.ContentAccess", "Core\FileTime.Core.ContentAccess\FileTime.Core.ContentAccess.csproj", "{88BBB541-7306-44AE-95B1-0F8765AF1D4E}"
|
||||||
EndProject
|
EndProject
|
||||||
|
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "FileTime.Core.CommandHandlers", "Core\FileTime.Core.CommandHandlers\FileTime.Core.CommandHandlers.csproj", "{9B161766-A672-4D59-B591-C68907905158}"
|
||||||
|
EndProject
|
||||||
Global
|
Global
|
||||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||||
Debug|Any CPU = Debug|Any CPU
|
Debug|Any CPU = Debug|Any CPU
|
||||||
@@ -139,6 +141,10 @@ Global
|
|||||||
{88BBB541-7306-44AE-95B1-0F8765AF1D4E}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
{88BBB541-7306-44AE-95B1-0F8765AF1D4E}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||||
{88BBB541-7306-44AE-95B1-0F8765AF1D4E}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
{88BBB541-7306-44AE-95B1-0F8765AF1D4E}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||||
{88BBB541-7306-44AE-95B1-0F8765AF1D4E}.Release|Any CPU.Build.0 = Release|Any CPU
|
{88BBB541-7306-44AE-95B1-0F8765AF1D4E}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||||
|
{9B161766-A672-4D59-B591-C68907905158}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||||
|
{9B161766-A672-4D59-B591-C68907905158}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||||
|
{9B161766-A672-4D59-B591-C68907905158}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||||
|
{9B161766-A672-4D59-B591-C68907905158}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||||
EndGlobalSection
|
EndGlobalSection
|
||||||
GlobalSection(SolutionProperties) = preSolution
|
GlobalSection(SolutionProperties) = preSolution
|
||||||
HideSolutionNode = FALSE
|
HideSolutionNode = FALSE
|
||||||
@@ -164,6 +170,7 @@ Global
|
|||||||
{1846BE76-8F68-4FC3-8954-871F14743F0B} = {3324D046-1E05-46B5-B1BA-82910D56B332}
|
{1846BE76-8F68-4FC3-8954-871F14743F0B} = {3324D046-1E05-46B5-B1BA-82910D56B332}
|
||||||
{2AC5CAFF-EBDA-4C6E-BCFF-304B651F2906} = {3324D046-1E05-46B5-B1BA-82910D56B332}
|
{2AC5CAFF-EBDA-4C6E-BCFF-304B651F2906} = {3324D046-1E05-46B5-B1BA-82910D56B332}
|
||||||
{88BBB541-7306-44AE-95B1-0F8765AF1D4E} = {3324D046-1E05-46B5-B1BA-82910D56B332}
|
{88BBB541-7306-44AE-95B1-0F8765AF1D4E} = {3324D046-1E05-46B5-B1BA-82910D56B332}
|
||||||
|
{9B161766-A672-4D59-B591-C68907905158} = {3324D046-1E05-46B5-B1BA-82910D56B332}
|
||||||
EndGlobalSection
|
EndGlobalSection
|
||||||
GlobalSection(ExtensibilityGlobals) = postSolution
|
GlobalSection(ExtensibilityGlobals) = postSolution
|
||||||
SolutionGuid = {859FB3DF-C60A-46B1-82E5-90274905D1EF}
|
SolutionGuid = {859FB3DF-C60A-46B1-82E5-90274905D1EF}
|
||||||
|
|||||||
@@ -2,14 +2,13 @@ using System.Reactive.Linq;
|
|||||||
using System.Reactive.Subjects;
|
using System.Reactive.Subjects;
|
||||||
using System.Runtime.InteropServices;
|
using System.Runtime.InteropServices;
|
||||||
using DynamicData;
|
using DynamicData;
|
||||||
using FileTime.App.Core.Models;
|
|
||||||
using FileTime.Core.ContentAccess;
|
using FileTime.Core.ContentAccess;
|
||||||
using FileTime.Core.Enums;
|
using FileTime.Core.Enums;
|
||||||
using FileTime.Core.Models;
|
using FileTime.Core.Models;
|
||||||
|
using FileTime.Core.Models.Extensions;
|
||||||
using FileTime.Core.Timeline;
|
using FileTime.Core.Timeline;
|
||||||
|
|
||||||
namespace FileTime.Providers.Local;
|
namespace FileTime.Providers.Local;
|
||||||
|
|
||||||
public sealed partial class LocalContentProvider : ContentProviderBase, ILocalContentProvider
|
public sealed partial class LocalContentProvider : ContentProviderBase, ILocalContentProvider
|
||||||
{
|
{
|
||||||
private readonly ITimelessContentProvider _timelessContentProvider;
|
private readonly ITimelessContentProvider _timelessContentProvider;
|
||||||
@@ -21,6 +20,8 @@ public sealed partial class LocalContentProvider : ContentProviderBase, ILocalCo
|
|||||||
_timelessContentProvider = timelessContentProvider;
|
_timelessContentProvider = timelessContentProvider;
|
||||||
_isCaseInsensitive = RuntimeInformation.IsOSPlatform(OSPlatform.Windows);
|
_isCaseInsensitive = RuntimeInformation.IsOSPlatform(OSPlatform.Windows);
|
||||||
|
|
||||||
|
SupportsContentStreams = true;
|
||||||
|
|
||||||
RefreshRootDirectories();
|
RefreshRootDirectories();
|
||||||
|
|
||||||
Items.OnNext(_rootDirectories.Connect());
|
Items.OnNext(_rootDirectories.Connect());
|
||||||
@@ -322,7 +323,7 @@ public sealed partial class LocalContentProvider : ContentProviderBase, ILocalCo
|
|||||||
_ => (int)realFileSize
|
_ => (int)realFileSize
|
||||||
};
|
};
|
||||||
var buffer = new byte[size];
|
var buffer = new byte[size];
|
||||||
await reader.ReadAsync(buffer, 0, size);
|
await reader.ReadAsync(buffer.AsMemory(0, size), cancellationToken);
|
||||||
|
|
||||||
return buffer;
|
return buffer;
|
||||||
}
|
}
|
||||||
|
|||||||
71
src/Providers/FileTime.Providers.Local/LocalContentReader.cs
Normal file
71
src/Providers/FileTime.Providers.Local/LocalContentReader.cs
Normal file
@@ -0,0 +1,71 @@
|
|||||||
|
using FileTime.Core.ContentAccess;
|
||||||
|
|
||||||
|
namespace FileTime.Providers.Local;
|
||||||
|
|
||||||
|
public class LocalContentReader : IContentReader
|
||||||
|
{
|
||||||
|
private readonly FileStream _readerStream;
|
||||||
|
private readonly BinaryReader _binaryReader;
|
||||||
|
private bool _disposed;
|
||||||
|
|
||||||
|
public int PreferredBufferSize => 1024 * 1024;
|
||||||
|
public long? Position { get; private set; }
|
||||||
|
|
||||||
|
public LocalContentReader(FileStream readerStream)
|
||||||
|
{
|
||||||
|
_readerStream = readerStream;
|
||||||
|
_binaryReader = new BinaryReader(_readerStream);
|
||||||
|
}
|
||||||
|
|
||||||
|
public Task<byte[]> ReadBytesAsync(int bufferSize, int? offset = null)
|
||||||
|
{
|
||||||
|
var max = bufferSize > 0 && bufferSize < PreferredBufferSize ? bufferSize : PreferredBufferSize;
|
||||||
|
|
||||||
|
if (offset != null)
|
||||||
|
{
|
||||||
|
if (Position == null) Position = 0;
|
||||||
|
var buffer = new byte[max];
|
||||||
|
var bytesRead = _binaryReader.Read(buffer, offset.Value, max);
|
||||||
|
Position += bytesRead;
|
||||||
|
|
||||||
|
if (buffer.Length != bytesRead)
|
||||||
|
{
|
||||||
|
Array.Resize(ref buffer, bytesRead);
|
||||||
|
}
|
||||||
|
return Task.FromResult(buffer);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
return Task.FromResult(_binaryReader.ReadBytes(max));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void SetPosition(long position)
|
||||||
|
{
|
||||||
|
Position = position;
|
||||||
|
}
|
||||||
|
|
||||||
|
~LocalContentReader()
|
||||||
|
{
|
||||||
|
Dispose(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Dispose()
|
||||||
|
{
|
||||||
|
Dispose(true);
|
||||||
|
GC.SuppressFinalize(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void Dispose(bool disposing)
|
||||||
|
{
|
||||||
|
if (!_disposed)
|
||||||
|
{
|
||||||
|
if (disposing)
|
||||||
|
{
|
||||||
|
_readerStream.Dispose();
|
||||||
|
_binaryReader.Dispose();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_disposed = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,10 @@
|
|||||||
|
using FileTime.Core.ContentAccess;
|
||||||
|
using FileTime.Core.Models;
|
||||||
|
|
||||||
|
namespace FileTime.Providers.Local;
|
||||||
|
|
||||||
|
public class LocalContentReaderFactory : IContentReaderFactory<ILocalContentProvider>
|
||||||
|
{
|
||||||
|
public Task<IContentReader> CreateContentReaderAsync(IElement element)
|
||||||
|
=> Task.FromResult((IContentReader)new LocalContentReader(File.OpenRead(element.NativePath!.Path)));
|
||||||
|
}
|
||||||
60
src/Providers/FileTime.Providers.Local/LocalContentWriter.cs
Normal file
60
src/Providers/FileTime.Providers.Local/LocalContentWriter.cs
Normal file
@@ -0,0 +1,60 @@
|
|||||||
|
using FileTime.Core.ContentAccess;
|
||||||
|
|
||||||
|
namespace FileTime.Providers.Local;
|
||||||
|
|
||||||
|
public class LocalContentWriter : IContentWriter
|
||||||
|
{
|
||||||
|
private readonly FileStream _writerStream;
|
||||||
|
private readonly BinaryWriter _binaryWriter;
|
||||||
|
private bool disposed;
|
||||||
|
public int PreferredBufferSize => 1024 * 1024;
|
||||||
|
|
||||||
|
public LocalContentWriter(FileStream writerStream)
|
||||||
|
{
|
||||||
|
_writerStream = writerStream;
|
||||||
|
_binaryWriter = new BinaryWriter(_writerStream);
|
||||||
|
}
|
||||||
|
|
||||||
|
public Task WriteBytesAsync(byte[] data, int? index = null)
|
||||||
|
{
|
||||||
|
if (index != null)
|
||||||
|
{
|
||||||
|
_binaryWriter.Write(data, index.Value, data.Length);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
_binaryWriter.Write(data);
|
||||||
|
}
|
||||||
|
return Task.CompletedTask;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Task FlushAsync()
|
||||||
|
{
|
||||||
|
_binaryWriter.Flush();
|
||||||
|
return Task.CompletedTask;
|
||||||
|
}
|
||||||
|
|
||||||
|
~LocalContentWriter()
|
||||||
|
{
|
||||||
|
Dispose(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Dispose()
|
||||||
|
{
|
||||||
|
Dispose(true);
|
||||||
|
GC.SuppressFinalize(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void Dispose(bool disposing)
|
||||||
|
{
|
||||||
|
if (!disposed)
|
||||||
|
{
|
||||||
|
if (disposing)
|
||||||
|
{
|
||||||
|
_writerStream.Dispose();
|
||||||
|
_binaryWriter.Dispose();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
disposed = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,10 @@
|
|||||||
|
using FileTime.Core.ContentAccess;
|
||||||
|
using FileTime.Core.Models;
|
||||||
|
|
||||||
|
namespace FileTime.Providers.Local;
|
||||||
|
|
||||||
|
public class LocalContentWriterFactory : IContentWriterFactory<ILocalContentProvider>
|
||||||
|
{
|
||||||
|
public Task<IContentWriter> CreateContentWriterAsync(IElement element)
|
||||||
|
=> Task.FromResult((IContentWriter)new LocalContentWriter(File.OpenRead(element.NativePath!.Path)));
|
||||||
|
}
|
||||||
@@ -12,6 +12,10 @@ public static class Startup
|
|||||||
serviceCollection.TryAddSingleton<IContentProvider>(sp => sp.GetRequiredService<ILocalContentProvider>());
|
serviceCollection.TryAddSingleton<IContentProvider>(sp => sp.GetRequiredService<ILocalContentProvider>());
|
||||||
serviceCollection.TryAddSingleton<IItemCreator<ILocalContentProvider>, LocalItemCreator>();
|
serviceCollection.TryAddSingleton<IItemCreator<ILocalContentProvider>, LocalItemCreator>();
|
||||||
serviceCollection.TryAddSingleton<IItemCreator<LocalContentProvider>>(sp => sp.GetRequiredService<IItemCreator<ILocalContentProvider>>());
|
serviceCollection.TryAddSingleton<IItemCreator<LocalContentProvider>>(sp => sp.GetRequiredService<IItemCreator<ILocalContentProvider>>());
|
||||||
|
serviceCollection.TryAddSingleton<IContentReaderFactory<ILocalContentProvider>, LocalContentReaderFactory>();
|
||||||
|
serviceCollection.TryAddSingleton<IContentReaderFactory<LocalContentProvider>>(sp => sp.GetRequiredService<IContentReaderFactory<ILocalContentProvider>>());
|
||||||
|
serviceCollection.TryAddSingleton<IContentWriterFactory<ILocalContentProvider>, LocalContentWriterFactory>();
|
||||||
|
serviceCollection.TryAddSingleton<IContentWriterFactory<LocalContentProvider>>(sp => sp.GetRequiredService<IContentWriterFactory<ILocalContentProvider>>());
|
||||||
return serviceCollection;
|
return serviceCollection;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Reference in New Issue
Block a user