From 65bb325aaf0c21d692f26face035855a7063f326 Mon Sep 17 00:00:00 2001 From: Scepheo Date: Mon, 17 Jun 2019 11:41:00 +0200 Subject: [PATCH] Monitor repository folder Handle changes to the repository folder as they happen, rather than rebuilding the entire menu evey time it's opened. --- src/GitMan/Config/Settings.cs | 3 + src/GitMan/Context.cs | 81 ++++++++++--- src/GitMan/Providers/RemoteProvider.cs | 2 +- src/GitMan/Repository.cs | 36 +----- src/GitMan/RepositoryDirectory.cs | 152 +++++++++++++++++++++++++ src/GitMan/RepositoryList.cs | 52 --------- 6 files changed, 224 insertions(+), 102 deletions(-) create mode 100644 src/GitMan/RepositoryDirectory.cs delete mode 100644 src/GitMan/RepositoryList.cs diff --git a/src/GitMan/Config/Settings.cs b/src/GitMan/Config/Settings.cs index 7d4694c..80d9528 100644 --- a/src/GitMan/Config/Settings.cs +++ b/src/GitMan/Config/Settings.cs @@ -48,6 +48,9 @@ public static Settings Load() settings.Save(); } + settings.AzureProviders ??= Array.Empty(); + settings.GitHubProviders ??= Array.Empty(); + return settings; } diff --git a/src/GitMan/Context.cs b/src/GitMan/Context.cs index 89f0a21..2f7dcd9 100644 --- a/src/GitMan/Context.cs +++ b/src/GitMan/Context.cs @@ -18,17 +18,12 @@ public class Context : ApplicationContext private readonly Settings _settings; private readonly RepositoryAction[] _repositoryActions; private readonly RemoteProvider[] _remoteProviders; + private readonly RepositoryDirectory _repositoryDirectory; private static void EmptyHandler(object sender, EventArgs eventArgs) { } public Context() { - _icon = new NotifyIcon(); - _icon.DoubleClick += Icon_DoubleClick; - _icon.MouseDown += Icon_MouseDown; - _icon.Visible = true; - _icon.Icon = LoadIcon(); - _main = new Main(); _settings = Settings.Load(); @@ -40,6 +35,51 @@ public Context() var gitHubProviders = _settings.GitHubProviders .Select(provider => (RemoteProvider)new GitHubProvider(provider)); _remoteProviders = azureProviders.Concat(gitHubProviders).ToArray(); + + var directoryInfo = new DirectoryInfo(_settings.RepositoryFolder); + _repositoryDirectory = new RepositoryDirectory(directoryInfo); + _repositoryDirectory.Added += RepositoryAdded; + _repositoryDirectory.Removed += RepositoryRemoved; + _repositoryDirectory.Renamed += RepositoryRenamed; + + _icon = new NotifyIcon(); + _icon.DoubleClick += Icon_DoubleClick; + _icon.Visible = true; + _icon.Icon = LoadIcon(); + _icon.ContextMenu = MakeContextMenu(); + } + + private void InsertMenuItem(MenuItem menuItem) + { + var menuItems = _icon.ContextMenu.MenuItems; + + var currentItems = menuItems.Cast(); + var newItems = Enumerable.Repeat(menuItem, 1); + var allitems = currentItems.Concat(newItems); + var orderedItems = allitems.OrderBy(item => item.Name).ToArray(); + + menuItems.Clear(); + menuItems.AddRange(orderedItems); + } + + private void RepositoryAdded(Repository repository) + { + var menuItem = MakeMenuItem(repository); + InsertMenuItem(menuItem); + } + + private void RepositoryRemoved(Repository repository) + { + var currentItems = _icon.ContextMenu.MenuItems.Cast(); + var name = GetRepositoryItemName(repository); + var menuItem = currentItems.Single(item => item.Name == name); + _icon.ContextMenu.MenuItems.Remove(menuItem); + } + + private void RepositoryRenamed(Repository oldRepository, Repository newRepository) + { + RepositoryRemoved(oldRepository); + RepositoryAdded(newRepository); } private void Icon_DoubleClick(object sender, EventArgs e) @@ -62,25 +102,21 @@ private Icon LoadIcon() return new Icon(resourceStream); } - private void Icon_MouseDown(object sender, MouseEventArgs e) + private ContextMenu MakeContextMenu() { - var directory = new DirectoryInfo(_settings.RepositoryFolder); - var repositoryList = RepositoryList.Load(directory); - var items = new List(); - var cloneItem = MakeCloneItem(repositoryList); + var cloneItem = MakeCloneItem(_repositoryDirectory); items.Add(cloneItem); - var repositoryItems = repositoryList.Select(MakeMenuItem).ToArray(); + var repositoryItems = _repositoryDirectory.Select(MakeMenuItem).ToArray(); items.AddRange(repositoryItems); var exitItem = MakeExitItem(); items.Add(exitItem); var menu = new ContextMenu(items.ToArray()); - - _icon.ContextMenu = menu; + return menu; } protected override void Dispose(bool disposing) @@ -90,6 +126,11 @@ protected override void Dispose(bool disposing) _icon.Dispose(); } + private string GetRepositoryItemName(Repository repository) + { + return $"2_REPO_{repository.Name}"; + } + private MenuItem MakeMenuItem(Repository repository) { var name = repository.Name; @@ -102,7 +143,11 @@ private MenuItem MakeMenuItem(Repository repository) subItems.AddRange(repositoryAction.GetMenuItems(directoryInfo)); } - var menuItem = new MenuItem(name, subItems.ToArray()); + var menuItem = new MenuItem(name, subItems.ToArray()) + { + Name = GetRepositoryItemName(repository) + }; + return menuItem; } @@ -114,10 +159,10 @@ void onClick(object sender, EventArgs eventArgs) } const string name = "Exit"; - return new MenuItem(name, onClick); + return new MenuItem(name, onClick) { Name = "3_EXIT" }; } - private MenuItem MakeCloneItem(RepositoryList existingRepositories) + private MenuItem MakeCloneItem(RepositoryDirectory existingRepositories) { var items = new List(); @@ -133,7 +178,7 @@ private MenuItem MakeCloneItem(RepositoryList existingRepositories) const string name = "Clone"; var itemArray = items.ToArray(); - return new MenuItem(name, itemArray); + return new MenuItem(name, itemArray) { Name = "1_CLONE" }; } } } diff --git a/src/GitMan/Providers/RemoteProvider.cs b/src/GitMan/Providers/RemoteProvider.cs index 5c82776..8440965 100644 --- a/src/GitMan/Providers/RemoteProvider.cs +++ b/src/GitMan/Providers/RemoteProvider.cs @@ -20,7 +20,7 @@ public RemoteProvider(string name, Dictionary defaultConfig) public MenuItem MakeRemoteProviderItem( string repositoryFolder, - RepositoryList existingRepositories) + RepositoryDirectory existingRepositories) { var loadItem = new MenuItem("Loading..."); var dummyItems = new[] { loadItem }; diff --git a/src/GitMan/Repository.cs b/src/GitMan/Repository.cs index 6e6b614..ea2c2a6 100644 --- a/src/GitMan/Repository.cs +++ b/src/GitMan/Repository.cs @@ -1,47 +1,21 @@ -using System; -using System.Collections.Generic; -using System.IO; -using System.Linq; +using System.IO; namespace GitMan { internal class Repository { - private readonly DirectoryInfo _directoryInfo; + public string Name { get; } + public string FullName { get; } private Repository(DirectoryInfo directoryInfo) { - _directoryInfo = directoryInfo; + Name = directoryInfo.Name; + FullName = directoryInfo.FullName; } public static Repository Load(DirectoryInfo directoryInfo) { return new Repository(directoryInfo); } - - public string Name => _directoryInfo.Name; - - public string FullName => _directoryInfo.FullName; - - public IEnumerable SolutionFiles => _directoryInfo.EnumerateFiles("*.sln", SearchOption.AllDirectories); - - public bool IsVsCodeProject() - { - var subDirectories = _directoryInfo.EnumerateDirectories(); - var hasVsCodeDirectory = subDirectories.Any(IsVsCodeDirectory); - return hasVsCodeDirectory; - } - - private static bool IsVsCodeDirectory(DirectoryInfo directoryInfo) - { - const string vsCodeDirectoryName = ".vscode"; - - var isVsCodeDirectory = string.Equals( - directoryInfo.Name, - vsCodeDirectoryName, - StringComparison.OrdinalIgnoreCase); - - return isVsCodeDirectory; - } } } diff --git a/src/GitMan/RepositoryDirectory.cs b/src/GitMan/RepositoryDirectory.cs new file mode 100644 index 0000000..3fa9a3f --- /dev/null +++ b/src/GitMan/RepositoryDirectory.cs @@ -0,0 +1,152 @@ +using System; +using System.Collections; +using System.Collections.Generic; +using System.IO; +using System.Linq; + +namespace GitMan +{ + internal class RepositoryDirectory : IDisposable, IEnumerable + { + private readonly DirectoryInfo _directoryInfo; + private readonly Dictionary _repositories; + private readonly FileSystemWatcher _watcher; + + public RepositoryDirectory(DirectoryInfo directoryInfo) + { + _directoryInfo = directoryInfo; + + _watcher = new FileSystemWatcher(directoryInfo.FullName); + _watcher.Changed += HandleChange; + _watcher.Created += HandleChange; + _watcher.Deleted += HandleChange; + _watcher.Renamed += HandleChange; + _watcher.EnableRaisingEvents = true; + + _repositories = BuildIndex(); + } + + public delegate void RepositoryAddedHandler(Repository repository); + public delegate void RepositoryRenamedHandler(Repository oldRepository, Repository newRepository); + public delegate void RepositoryRemovedHandler(Repository repository); + + public event RepositoryAddedHandler Added; + public event RepositoryRenamedHandler Renamed; + public event RepositoryRemovedHandler Removed; + + private Dictionary BuildIndex() + { + var index = new Dictionary(); + + foreach (var subDirectoryInfo in _directoryInfo.EnumerateDirectories()) + { + if (IsGitRepository(subDirectoryInfo)) + { + var repository = Repository.Load(subDirectoryInfo); + index[subDirectoryInfo.Name] = repository; + } + } + + return index; + } + + private void HandleChange(object sender, FileSystemEventArgs eventArgs) + { + var oldName = eventArgs is RenamedEventArgs rename ? rename.OldName : eventArgs.Name; + var newName = eventArgs.Name; + HandleChange(oldName, newName); + } + + private bool TryGetDirectory(string name, out DirectoryInfo directory) + { + directory = _directoryInfo.EnumerateDirectories().SingleOrDefault(dir => dir.Name == name); + return directory != default; + } + + private bool WasRepository(string directoryName, out Repository repository) + { + if (directoryName == null) + { + repository = null; + return false; + } + + return _repositories.TryGetValue(directoryName, out repository); + } + + private bool IsRepository(string directoryName, out Repository repository) + { + if (!TryGetDirectory(directoryName, out var directory)) + { + repository = null; + return false; + } + + if (IsGitRepository(directory)) + { + repository = Repository.Load(directory); + return true; + } + + repository = null; + return false; + } + + private void HandleChange(string oldDirectoryName, string newDirectoryName) + { + var wasRepository = WasRepository(oldDirectoryName, out var oldRepository); + var isRepository = IsRepository(newDirectoryName, out var newRepository); + var isNameChanged = !string.Equals(oldDirectoryName, newDirectoryName, StringComparison.OrdinalIgnoreCase); + + if (wasRepository && isRepository) + { + if (isNameChanged) + { + _repositories.Remove(oldDirectoryName); + _repositories[newRepository.Name] = newRepository; + Renamed?.Invoke(oldRepository, newRepository); + } + } + else if (wasRepository) + { + _repositories.Remove(oldDirectoryName); + Removed?.Invoke(oldRepository); + } + else if (isRepository) + { + _repositories[newRepository.Name] = newRepository; + Added?.Invoke(newRepository); + } + } + + public void Dispose() + { + _watcher.Dispose(); + } + + private static bool IsGitRepository(DirectoryInfo directoryInfo) + { + var subDirectories = directoryInfo.EnumerateDirectories(); + var hasGitDirectory = subDirectories.Any(IsGitDirectory); + return hasGitDirectory; + } + + private static bool IsGitDirectory(DirectoryInfo directoryInfo) + { + const string gitDirectoryName = ".git"; + + var isGitDirectory = string.Equals( + directoryInfo.Name, + gitDirectoryName, + StringComparison.OrdinalIgnoreCase); + + return isGitDirectory; + } + + public int Count => _repositories.Count; + + public IEnumerator GetEnumerator() => _repositories.Values.GetEnumerator(); + + IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); + } +} diff --git a/src/GitMan/RepositoryList.cs b/src/GitMan/RepositoryList.cs deleted file mode 100644 index 972c0db..0000000 --- a/src/GitMan/RepositoryList.cs +++ /dev/null @@ -1,52 +0,0 @@ -using System; -using System.Collections; -using System.Collections.Generic; -using System.IO; -using System.Linq; - -namespace GitMan -{ - internal class RepositoryList : IReadOnlyList - { - private readonly Repository[] _repositories; - - private RepositoryList(IEnumerable repositories) - { - _repositories = repositories.ToArray(); - } - - public static RepositoryList Load(DirectoryInfo directoryInfo) - { - var subDirectories = directoryInfo.EnumerateDirectories(); - var gitDirectories = subDirectories.Where(IsGitRepository); - var gitRepositories = gitDirectories.Select(Repository.Load); - return new RepositoryList(gitRepositories); - } - - private static bool IsGitRepository(DirectoryInfo directoryInfo) - { - var subDirectories = directoryInfo.EnumerateDirectories(); - var hasGitDirectory = subDirectories.Any(IsGitDirectory); - return hasGitDirectory; - } - - private static bool IsGitDirectory(DirectoryInfo directoryInfo) - { - const string gitDirectoryName = ".git"; - - var isGitDirectory = string.Equals( - directoryInfo.Name, - gitDirectoryName, - StringComparison.OrdinalIgnoreCase); - - return isGitDirectory; - } - - public int Count => _repositories.Length; - - public Repository this[int index] => _repositories[index]; - - public IEnumerator GetEnumerator() => ((IEnumerable)_repositories).GetEnumerator(); - IEnumerator IEnumerable.GetEnumerator() => _repositories.GetEnumerator(); - } -}