From 5eb89a953c6650db383229aca638ac968046a23a Mon Sep 17 00:00:00 2001 From: Mikayla Dobson Date: Sat, 27 Jan 2024 13:19:50 -0600 Subject: [PATCH] improved program direction and error handling --- git-anchor/Actions/Base.cs | 1 + git-anchor/Actions/Create.cs | 11 +++----- git-anchor/Actions/Pull.cs | 11 ++++++-- git-anchor/Actions/Status.cs | 44 +++++++++++++++++++++++++++++ git-anchor/Lib/CommandLine.cs | 6 ++++ git-anchor/Lib/FileSystem.cs | 34 +++++++++++++++++++++- git-anchor/Lib/GitHub.cs | 53 ++++++++++++++++++++++++++--------- git-anchor/Program.cs | 41 +++++++++++++++++++-------- 8 files changed, 164 insertions(+), 37 deletions(-) create mode 100644 git-anchor/Actions/Status.cs diff --git a/git-anchor/Actions/Base.cs b/git-anchor/Actions/Base.cs index f07e3c4..7f28081 100644 --- a/git-anchor/Actions/Base.cs +++ b/git-anchor/Actions/Base.cs @@ -9,6 +9,7 @@ public abstract class BaseAction { public virtual async Task RunAsync() { + await Task.Delay(0); throw new NotImplementedException(); } } diff --git a/git-anchor/Actions/Create.cs b/git-anchor/Actions/Create.cs index 1ce6de9..2da0edc 100644 --- a/git-anchor/Actions/Create.cs +++ b/git-anchor/Actions/Create.cs @@ -69,7 +69,7 @@ public class Create : BaseAction foreach (Repository newProject in newProjects) { - GitHub.CloneProjectOptions options = new() + GitHub.GithubRepoOptions options = new() { BackupDir = anchorDir, Repo = newProject, @@ -90,12 +90,9 @@ public class Create : BaseAction Task.WaitAll([.. taskPool]); reporter.Stop(); - - if (verbose) - { - var report = reporter.ToString(); - Console.WriteLine(report); - } + + var report = reporter.ToString(); + Console.WriteLine(report); return ActivityStatusCode.Ok; } diff --git a/git-anchor/Actions/Pull.cs b/git-anchor/Actions/Pull.cs index 6dfb7bf..c703f32 100644 --- a/git-anchor/Actions/Pull.cs +++ b/git-anchor/Actions/Pull.cs @@ -21,8 +21,6 @@ namespace GitAnchor.Actions string anchorName = args.FirstOrDefault(arg => arg.StartsWith("--name") || arg.StartsWith("-n"))?.Split("=")[1].Trim() ?? throw new Exception("No anchor name provided"); - anchorName = anchorName.Split("=")[1].Trim(); - // find (or create) the anchor in question var anchor = Volumes.GetAnchor(mainBackupDir, anchorName) ?? FileSystemTools.SetUpAnchor(mainBackupDir, anchorName); var projectDirectories = anchor?.GetDirectories(); @@ -50,7 +48,14 @@ namespace GitAnchor.Actions if (verbose) Console.WriteLine($"Preparing pull task for {repo.Name}"); - Task pullTask = new(() => GitHub.PullExistingRepo(repo, backupDir, verbose)); + var options = new GitHub.GithubRepoOptions + { + Repo = repo, + BackupDir = backupDir, + Verbose = verbose, + }; + + Task pullTask = new(() => GitHub.PullExistingRepo(options)); taskPool.Add(pullTask); } diff --git a/git-anchor/Actions/Status.cs b/git-anchor/Actions/Status.cs new file mode 100644 index 0000000..ecc0f1f --- /dev/null +++ b/git-anchor/Actions/Status.cs @@ -0,0 +1,44 @@ +using System.Diagnostics; +using GitAnchor.Lib; + +namespace GitAnchor.Actions; + +public class Status : BaseAction +{ + public new static async Task RunAsync() + { + string[] args = Environment.GetCommandLineArgs(); + + // get main backup directory + var backupLocation = FileSystemTools.MainBackupFile; + var anchorName = CommandLineTools.ReadAnchorName(); + var backupDir = new DirectoryInfo(Path.Join(backupLocation, anchorName)); + + // get all directories in main backup directory + var directories = backupDir.GetDirectories(); + + List taskPool = []; + + foreach (var dir in directories) + { + GitHub.GithubRepoOptions options = new() + { + AnchorPath = Path.Join(backupDir.FullName, dir.Name), + BackupDir = backupDir, + ProjectPath = dir.Name, + }; + + taskPool.Add(Task.Run(() => GitHub.QueryStatus(options))); + } + + using var reporter = new ProgressReporter(); + reporter.Start(); + + await Parallel.ForEachAsync(taskPool, async(task, cancellationToken) => await task); + + reporter.Stop(); + Console.WriteLine(reporter.ToString()); + + return ActivityStatusCode.Ok; + } +} diff --git a/git-anchor/Lib/CommandLine.cs b/git-anchor/Lib/CommandLine.cs index 7d4a150..8f89c7c 100644 --- a/git-anchor/Lib/CommandLine.cs +++ b/git-anchor/Lib/CommandLine.cs @@ -3,6 +3,12 @@ using Octokit; namespace GitAnchor.Lib; public static class CommandLineTools { + public static string? ReadAnchorName() + { + return Environment.GetCommandLineArgs().FirstOrDefault(arg => arg.StartsWith("--name") || arg.StartsWith("-n"))? + .Split("=")[1].Trim(); + } + public static int ReadAsInt() { string? input = Console.ReadLine(); diff --git a/git-anchor/Lib/FileSystem.cs b/git-anchor/Lib/FileSystem.cs index 8652ac3..1057702 100644 --- a/git-anchor/Lib/FileSystem.cs +++ b/git-anchor/Lib/FileSystem.cs @@ -1,3 +1,4 @@ +using System.Runtime.InteropServices; namespace GitAnchor.Lib; public static class FileSystemTools { @@ -77,7 +78,38 @@ public static class FileSystemTools { private static DirectoryInfo? CreateMainWindowsDirectory(DriveInfo volume, string? location = ".git-anchor") { - bool isSystemDrive = volume.Name == "C:\\"; + OSPlatform platform; + + if (OperatingSystem.IsWindows()) + { + platform = OSPlatform.Windows; + } + else if (OperatingSystem.IsMacOS()) + { + platform = OSPlatform.OSX; + } + else if (OperatingSystem.IsLinux()) + { + platform = OSPlatform.Linux; + } + else + { + throw new Exception("Unsupported operating system"); + } + + bool isSystemDrive; + bool isExternalDrive; + + if (platform == OSPlatform.Windows) + { + isSystemDrive = volume.Name == "C:\\"; + isExternalDrive = volume.DriveType == DriveType.Removable && !isSystemDrive; + } + else + { + isSystemDrive = volume.Name == "/"; + isExternalDrive = volume.DriveType == DriveType.Removable && !isSystemDrive; + } try { diff --git a/git-anchor/Lib/GitHub.cs b/git-anchor/Lib/GitHub.cs index 37cfd97..44184d0 100644 --- a/git-anchor/Lib/GitHub.cs +++ b/git-anchor/Lib/GitHub.cs @@ -1,5 +1,6 @@ using Octokit; using System.Diagnostics; +using System.Runtime.InteropServices; namespace GitAnchor.Lib { @@ -80,23 +81,27 @@ namespace GitAnchor.Lib return projectsToUpdate; } - public record CloneProjectOptions + public record GithubRepoOptions { - public User User { get; init; } = default!; - public Repository Repo { get; init; } = default!; public DirectoryInfo BackupDir { get; init; } = default!; - public string Token { get; init; } = default!; + public User? User { get; init; } = default!; + public Repository? Repo { get; init; } = default!; + public string? ProjectPath { get; init; } + public string? Token { get; init; } public string? AnchorPath { get; init; } - public bool Verbose { get; init; } = false; + public bool? Verbose { get; init; } = false; } - public static void CloneProject(CloneProjectOptions options) + public static void CloneProject(GithubRepoOptions options) { + if (options.User == null) throw new Exception("User not found"); + if (options.Repo == null) throw new Exception("Repository not found"); + string cloneString = ""; cloneString += $"clone https://{options.Token}@github.com/"; cloneString += $"{options.User.Login}/{options.Repo.Name} ."; - Console.WriteLine($"Cloning {options.Repo.Name}..."); + if (options.Verbose ?? false) Console.WriteLine($"Cloning {options.Repo.Name}..."); using (Process process = new()) { @@ -104,21 +109,22 @@ namespace GitAnchor.Lib { FileName = "git", Arguments = cloneString, - WorkingDirectory = $"{options.BackupDir}/{options.Repo.Name}", + WorkingDirectory = Path.Join(options.BackupDir.FullName, options.Repo.Name), RedirectStandardOutput = true, RedirectStandardError = true }; process.Start(); - if (options.Verbose) Console.WriteLine(process.StandardOutput.ReadToEnd()); + if (options.Verbose ?? false) Console.WriteLine(process.StandardOutput.ReadToEnd()); process.WaitForExit(); Console.WriteLine($"Clone complete for {options.Repo.Name}"); }; } - public static void PullExistingRepo(Repository repo, DirectoryInfo backupDir, bool verbose = false) + public static void PullExistingRepo(GithubRepoOptions options) { - Console.WriteLine($"Pulling {repo.Name}..."); + if (options.Repo == null) throw new Exception("Repository not found"); + Console.WriteLine($"Pulling {options.Repo.Name}..."); using (Process process = new()) { @@ -126,15 +132,34 @@ namespace GitAnchor.Lib { FileName = "git", Arguments = "pull", - WorkingDirectory = $"{backupDir}/{repo.Name}", + WorkingDirectory = Path.Join(options.BackupDir.FullName, options.Repo.Name), RedirectStandardOutput = true, RedirectStandardError = true }; process.Start(); - if (verbose) Console.WriteLine(process.StandardOutput.ReadToEnd()); + if (options.Verbose ?? false) Console.WriteLine(process.StandardOutput.ReadToEnd()); + process.WaitForExit(); + Console.WriteLine($"Pull complete for {options.Repo.Name}"); + }; + } + + public static void QueryStatus(GithubRepoOptions options) + { + using (Process process = new()) + { + process.StartInfo = new() + { + FileName = "git", + Arguments = "status", + WorkingDirectory = Path.Join(options.BackupDir.FullName, options.Repo?.Name ?? options.ProjectPath ?? throw new Exception("Unable to determine working directory")), + RedirectStandardOutput = true, + RedirectStandardError = true + }; + + process.Start(); + Console.WriteLine(process.StandardOutput.ReadToEnd()); process.WaitForExit(); - Console.WriteLine($"Pull complete for {repo.Name}"); }; } } diff --git a/git-anchor/Program.cs b/git-anchor/Program.cs index f85224b..e180b68 100644 --- a/git-anchor/Program.cs +++ b/git-anchor/Program.cs @@ -1,25 +1,42 @@ -using System.Diagnostics; -using System.Runtime.CompilerServices; +using System.Collections; +using System.Diagnostics; +using System.Reflection; using GitAnchor.Actions; -using GitAnchor.Lib; string[] inputArgs = Environment.GetCommandLineArgs(); + +// create pointers for entrypoints for each action +var createFn = typeof(Create).GetMethod("RunAsync", BindingFlags.Static | BindingFlags.Public); +var pullFn = typeof(Pull).GetMethod("RunAsync", BindingFlags.Static | BindingFlags.Public); +var statusFn = typeof(Status).GetMethod("RunAsync", BindingFlags.Static | BindingFlags.Public); +var listFn = typeof(List).GetMethod("Run", BindingFlags.Static | BindingFlags.Public); +var helpFn = typeof(Help).GetMethod("Run", BindingFlags.Static | BindingFlags.Public); + +var AllOptions = new Hashtable() +{ + { "create", createFn }, + { "pull", pullFn }, + { "status", statusFn }, + { "list", listFn }, + { "help", helpFn }, +}; + try { foreach (string a in inputArgs) { - string[] options = ["create", "pull", "list", "help"]; - if (options.Contains(a)) + var titleCase = a.ToUpperInvariant(); + + if (AllOptions.ContainsKey(a)) { - return a switch + var func = (MethodInfo)AllOptions[a]; + var result = func?.Invoke(null, null); + + if (result is Task task) { - "create" => (int)await Create.RunAsync(), - "pull" => (int)await Pull.RunAsync(), - "list" => (int)List.Run(), - "help" => (int)Help.Run(), - _ => (int)Help.Run(), - }; + return (int)task.Result; + } }; }