1
0
Flawless-Version-Control/Flawless.Client/Service/RepositoryFileTreeAccessor.cs

166 lines
4.9 KiB
C#

using System;
using System.Collections;
using System.Collections.Generic;
using System.Collections.Immutable;
using System.IO;
using System.Linq;
using System.Threading.Tasks;
using Flawless.Core.BinaryDataFormat;
using Flawless.Core.Modal;
namespace Flawless.Client.Service;
public class RepositoryFileTreeAccessor : IDisposable, IAsyncDisposable, IEnumerable<WorkspaceFile>
{
private struct FileReadInfoCache
{
public readonly Guid DepotId { get; init; }
public readonly DepotFileInfo FileInfo { get; init; }
}
private readonly Dictionary<Guid, Stream> _mappings;
private readonly CommitManifest _manifest;
private readonly Dictionary<Guid, StandardDepotHeaderV1> _headers;
private readonly Dictionary<string, FileReadInfoCache> _cached;
private object _readonlyLock = new(); // todo support async method.
private bool _disposed;
public CommitManifest Manifest => _manifest;
public bool IsCached { get; private set; }
public RepositoryFileTreeAccessor(Dictionary<Guid, Stream> currentReadFs, CommitManifest manifest)
{
IsCached = false;
_mappings = currentReadFs;
_manifest = manifest;
_headers = new Dictionary<Guid, StandardDepotHeaderV1>();
_cached = new Dictionary<string, FileReadInfoCache>();
_disposed = false;
}
public void Dispose()
{
if (_disposed) return;
_disposed = true;
foreach (var mv in _mappings.Values)
{
try { mv.Dispose(); }
catch (Exception e) { Console.WriteLine(e); }
}
}
public async ValueTask DisposeAsync()
{
if (_disposed) return;
_disposed = true;
foreach (var mv in _mappings.Values)
{
try { await mv.DisposeAsync(); }
catch (Exception e) { Console.WriteLine(e); }
}
}
public async Task CreateCacheAsync()
{
DisposeCheck();
lock (_readonlyLock) if (IsCached) return;
var ms = _manifest.FilePaths.ToImmutableSortedDictionary(x => x.WorkPath, x => x.ModifyTime);
foreach (var (id, stream) in _mappings)
{
stream.Seek(0, SeekOrigin.Begin);
var header = DataTransformer.ExtractStandardDepotHeaderV1(stream);
_headers.Add(id, header);
await foreach (var inf in DataTransformer.ExtractDepotFileInfoMapAsync(stream, header.FileMapSize))
{
if (ms.TryGetValue(inf.Path, out var targetTime) && targetTime == inf.ModifyTime)
{
_cached.Add(inf.Path, new FileReadInfoCache { DepotId = id, FileInfo = inf });
}
}
}
if (ms.Count != _cached.Count)
throw new FileNotFoundException("Some of those files are not able to be fount in any depots!");
lock (_readonlyLock) IsCached = true;
}
public bool TryGetFileInfo(string workPath, out DepotFileInfo info)
{
DisposeCheck();
lock (_readonlyLock) if (!IsCached) throw new InvalidOperationException("Not cached!");
if (_cached.TryGetValue(workPath, out var r))
{
info = r.FileInfo;
return true;
}
info = default;
return false;
}
public bool TryWriteDataIntoDisk(string workPath, Stream stream, out DateTime modifyTime)
{
DisposeCheck();
if (stream == null || !stream.CanWrite) throw new ArgumentException("Stream is not writable!");
if (_cached.TryGetValue(workPath, out var r))
{
var baseStream = DataTransformer.ExtractStandardDepotFile(_mappings[r.DepotId], r.FileInfo);
baseStream.CopyTo(stream);
modifyTime = r.FileInfo.ModifyTime;
return true;
}
modifyTime = DateTime.MinValue;
return false;
}
public bool TryWriteDataIntoDisk(string workPath, string destinationPath)
{
DisposeCheck();
if (_cached.TryGetValue(workPath, out var r))
{
using (var ws = new FileStream(destinationPath, FileMode.OpenOrCreate, FileAccess.Write))
{
var baseStream = DataTransformer.ExtractStandardDepotFile(_mappings[r.DepotId], r.FileInfo);
baseStream.CopyTo(ws);
}
File.SetLastWriteTimeUtc(destinationPath, r.FileInfo.ModifyTime);
return true;
}
return false;
}
private void DisposeCheck()
{
if (_disposed) throw new ObjectDisposedException("Accessor has already been disposed");
}
public IEnumerator<WorkspaceFile> GetEnumerator()
{
return _cached.Values
.Select(cv => new WorkspaceFile(cv.FileInfo.ModifyTime, cv.FileInfo.Path)).GetEnumerator();
}
IEnumerator IEnumerable.GetEnumerator()
{
return GetEnumerator();
}
}