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 { private struct FileReadInfoCache { public readonly Guid DepotId { get; init; } public readonly DepotFileInfo FileInfo { get; init; } } private readonly Dictionary _mappings; private readonly CommitManifest _manifest; private readonly Dictionary _headers; private readonly Dictionary _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 currentReadFs, CommitManifest manifest) { IsCached = false; _mappings = currentReadFs; _manifest = manifest; _headers = new Dictionary(); _cached = new Dictionary(); _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 GetEnumerator() { return _cached.Values .Select(cv => new WorkspaceFile(cv.FileInfo.ModifyTime, cv.FileInfo.Path)).GetEnumerator(); } IEnumerator IEnumerable.GetEnumerator() { return GetEnumerator(); } }