using System; using System.Collections.Generic; using System.IO; using System.Threading; namespace SourceGit.Models { /// /// 文件系统更新监视 /// public class Watcher { /// /// 打开仓库事件 /// public static event Action Opened; /// /// 跳转到指定提交的事件 /// public event Action Navigate; /// /// 工作副本变更 /// public event Action WorkingCopyChanged; /// /// 分支数据变更 /// public event Action BranchChanged; /// /// 标签变更 /// public event Action TagChanged; /// /// 贮藏变更 /// public event Action StashChanged; /// /// 子模块变更 /// public event Action SubmoduleChanged; /// /// 树更新 /// public event Action SubTreeChanged; /// /// 打开仓库事件 /// /// public static void Open(Repository repo) { if (all.ContainsKey(repo.Path)) { Opened?.Invoke(repo); return; } var watcher = new Watcher(); watcher.Start(repo.Path, repo.GitDir); all.Add(repo.Path, watcher); Opened?.Invoke(repo); } /// /// 停止指定的监视器 /// /// public static void Close(string repoPath) { if (!all.ContainsKey(repoPath)) return; all[repoPath].Stop(); all.Remove(repoPath); } /// /// 取得一个仓库的监视器 /// /// /// public static Watcher Get(string repoPath) { if (all.ContainsKey(repoPath)) return all[repoPath]; return null; } /// /// 暂停或启用监听 /// /// /// public static void SetEnabled(string repoPath, bool enabled) { if (all.ContainsKey(repoPath)) { var watcher = all[repoPath]; if (enabled) { if (watcher.lockCount > 0) watcher.lockCount--; } else { watcher.lockCount++; } } } /// /// 跳转到指定的提交 /// /// public void NavigateTo(string commit) { Navigate?.Invoke(commit); } /// /// 仅强制更新本地变化 /// public void RefreshWC() { updateWC = 0; WorkingCopyChanged?.Invoke(); } /// /// 通知更新子树列表 /// public void RefreshSubTrees() { SubTreeChanged?.Invoke(); } private void Start(string repo, string gitDir) { wcWatcher = new FileSystemWatcher(); wcWatcher.Path = repo; wcWatcher.Filter = "*"; wcWatcher.NotifyFilter = NotifyFilters.LastWrite | NotifyFilters.DirectoryName | NotifyFilters.FileName | NotifyFilters.Size | NotifyFilters.CreationTime; wcWatcher.IncludeSubdirectories = true; wcWatcher.Created += OnWorkingCopyChanged; wcWatcher.Renamed += OnWorkingCopyChanged; wcWatcher.Changed += OnWorkingCopyChanged; wcWatcher.Deleted += OnWorkingCopyChanged; wcWatcher.EnableRaisingEvents = true; repoWatcher = new FileSystemWatcher(); repoWatcher.Path = gitDir; repoWatcher.Filter = "*"; repoWatcher.NotifyFilter = NotifyFilters.LastWrite | NotifyFilters.DirectoryName | NotifyFilters.FileName; repoWatcher.IncludeSubdirectories = true; repoWatcher.Created += OnRepositoryChanged; repoWatcher.Renamed += OnRepositoryChanged; repoWatcher.Changed += OnRepositoryChanged; repoWatcher.Deleted += OnRepositoryChanged; repoWatcher.EnableRaisingEvents = true; timer = new Timer(Tick, null, 100, 100); } private void Stop() { repoWatcher.EnableRaisingEvents = false; repoWatcher.Dispose(); repoWatcher = null; wcWatcher.EnableRaisingEvents = false; wcWatcher.Dispose(); wcWatcher = null; timer.Dispose(); timer = null; Navigate = null; WorkingCopyChanged = null; BranchChanged = null; TagChanged = null; StashChanged = null; SubmoduleChanged = null; SubTreeChanged = null; } private void OnRepositoryChanged(object o, FileSystemEventArgs e) { if (string.IsNullOrEmpty(e.Name)) return; if (e.Name.StartsWith("modules", StringComparison.Ordinal)) { updateSubmodules = DateTime.Now.AddSeconds(1).ToFileTime(); } else if (e.Name.StartsWith("refs\\tags", StringComparison.Ordinal)) { updateTags = DateTime.Now.AddSeconds(.5).ToFileTime(); } else if (e.Name.StartsWith("refs\\stash", StringComparison.Ordinal)) { updateStashes = DateTime.Now.AddSeconds(.5).ToFileTime(); } else if (e.Name.Equals("HEAD", StringComparison.Ordinal) || e.Name.StartsWith("refs\\heads\\", StringComparison.Ordinal) || e.Name.StartsWith("refs\\remotes\\", StringComparison.Ordinal) || e.Name.StartsWith("worktrees\\")) { updateBranch = DateTime.Now.AddSeconds(.5).ToFileTime(); } else if (e.Name.StartsWith("objects\\", StringComparison.Ordinal) || e.Name.Equals("index", StringComparison.Ordinal)) { updateWC = DateTime.Now.AddSeconds(.5).ToFileTime(); } } private void OnWorkingCopyChanged(object o, FileSystemEventArgs e) { if (string.IsNullOrEmpty(e.Name)) return; if (e.Name == ".git" || e.Name.StartsWith(".git\\", StringComparison.Ordinal)) return; updateWC = DateTime.Now.AddSeconds(1).ToFileTime(); } private void Tick(object sender) { if (lockCount > 0) return; var now = DateTime.Now.ToFileTime(); if (updateBranch > 0 && now > updateBranch) { BranchChanged?.Invoke(); WorkingCopyChanged?.Invoke(); updateBranch = 0; updateWC = 0; } if (updateWC > 0 && now > updateWC) { WorkingCopyChanged?.Invoke(); updateWC = 0; } if (updateSubmodules > 0 && now > updateSubmodules) { SubmoduleChanged?.Invoke(); updateSubmodules = 0; } if (updateStashes > 0 && now > updateStashes) { StashChanged?.Invoke(); updateStashes = 0; } if (updateTags > 0 && now > updateTags) { TagChanged?.Invoke(); updateTags = 0; } } #region PRIVATES private static Dictionary all = new Dictionary(); private FileSystemWatcher repoWatcher = null; private FileSystemWatcher wcWatcher = null; private Timer timer = null; private int lockCount = 0; private long updateWC = 0; private long updateBranch = 0; private long updateSubmodules = 0; private long updateStashes = 0; private long updateTags = 0; #endregion } }