using System; using System.Collections.Generic; using System.Collections.Immutable; using System.IO; using System.Linq; using Flawless.Abstraction; using Flawless.Client.Models; using Flawless.Core.Modal; namespace Flawless.Client.Service; public enum ChangeInfoType { Folder = 0, Add, Remove, Modify } public struct ChangeInfo : IEquatable { public ChangeInfoType Type { get; } public WorkspaceFile File { get; } public ChangeInfo(ChangeInfoType type, WorkspaceFile file) { Type = type; File = file; } public bool Equals(ChangeInfo other) { return Type == other.Type && File.Equals(other.File); } public override bool Equals(object? obj) { return obj is ChangeInfo other && Equals(other); } public override int GetHashCode() { return HashCode.Combine((int)Type, File); } public static bool operator ==(ChangeInfo left, ChangeInfo right) { return left.Equals(right); } public static bool operator !=(ChangeInfo left, ChangeInfo right) { return !left.Equals(right); } } public class LocalFileTreeAccessor { private static readonly string[] IgnoredDirectories = { AppDefaultValues.RepoLocalStorageManagerFolder }; private readonly RepositoryModel _repo; private readonly string _rootDirectory; private IReadOnlyDictionary _baseline; private Dictionary _changes = new(); private Dictionary _currentFiles = new(); private object _optLock = new(); public DateTime LastScanTimeUtc { get; private set; } public string WorkingDirectory => _rootDirectory; public IReadOnlyDictionary BaselineFiles => _baseline; public IReadOnlyDictionary Changes => _changes; public IReadOnlyDictionary CurrentFiles => _currentFiles; public LocalFileTreeAccessor(RepositoryModel repo, IEnumerable baselines) { _repo = repo; _baseline = baselines.ToImmutableDictionary(b => b.WorkPath); _rootDirectory = PathUtility.GetWorkspacePath(Api.Current.Username.Value!, repo.OwnerName, repo.Name); } public void SetBaseline(IEnumerable baselines) { lock (_optLock) { _baseline = baselines.ToImmutableDictionary(b => b.WorkPath); _changes.Clear(); RefreshInternal(); } } public void Refresh() { lock (_optLock) { _changes.Clear(); RefreshInternal(); } } private void RefreshInternal() { _currentFiles.Clear(); foreach (var f in Directory.GetFiles(_rootDirectory, "*", SearchOption.AllDirectories)) { var workPath = WorkPath.FromPlatformPath(f, _rootDirectory); if (!WorkPath.IsPathValid(workPath) || IgnoredDirectories.Any(d => workPath.StartsWith(d))) continue; var inf = new FileInfo(f); _currentFiles.Add(workPath, new WorkspaceFile(inf.LastWriteTimeUtc, workPath, inf.Length)); } LastScanTimeUtc = DateTime.UtcNow; // Find those are changed var changes = _currentFiles.Values.Where(v => _baseline.TryGetValue(v.WorkPath, out var fi) && (fi.ModifyTime != v.ModifyTime || fi.Size != v.Size)); var news = _currentFiles.Values.Where(v => !_baseline.ContainsKey(v.WorkPath)); var removed = _baseline.Values.Where(v => !_currentFiles.ContainsKey(v.WorkPath)); foreach (var f in changes) _changes.Add(f.WorkPath, new ChangeInfo(ChangeInfoType.Modify, f)); foreach (var f in news) _changes.Add(f.WorkPath, new ChangeInfo(ChangeInfoType.Add, f)); foreach (var f in removed) _changes.Add(f.WorkPath, new ChangeInfo(ChangeInfoType.Remove, f)); } }