diff --git a/EPS.SDK/Git/GitManager.cs b/EPS.SDK/Git/GitManager.cs index 701be2f..3167675 100644 --- a/EPS.SDK/Git/GitManager.cs +++ b/EPS.SDK/Git/GitManager.cs @@ -5,11 +5,11 @@ namespace ExtensiblePortfolioSite.SDK.Git { internal static class GitManager { - private static readonly SortedDictionary Providers = new (); + private static readonly SortedDictionary Providers = new(); public static void Register(String Service, IGitProvider Provider) { - if(Providers.TryGetValue(Service, out GitService? GitService)) + if (Providers.TryGetValue(Service, out GitService? GitService)) { GitService.addProvider(Provider); return; diff --git a/EPS.SDK/Git/GitReference.cs b/EPS.SDK/Git/GitReference.cs index 6479441..0ae1414 100644 --- a/EPS.SDK/Git/GitReference.cs +++ b/EPS.SDK/Git/GitReference.cs @@ -1,8 +1,4 @@ using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; namespace ExtensiblePortfolioSite.SDK.Git { @@ -30,6 +26,11 @@ namespace ExtensiblePortfolioSite.SDK.Git /// public readonly struct GitReference { + public GitReference(GitReferenceKind Kind, String ReferenceString) + { + this.Kind = Kind; + this.ReferenceString = ReferenceString; + } /// /// Reference Kind /// diff --git a/EPS.SDK/Git/IGitObject.cs b/EPS.SDK/Git/IGitObject.cs index d30c1f4..7e34d0f 100644 --- a/EPS.SDK/Git/IGitObject.cs +++ b/EPS.SDK/Git/IGitObject.cs @@ -1,10 +1,4 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; - -namespace ExtensiblePortfolioSite.SDK.Git +namespace ExtensiblePortfolioSite.SDK.Git { /// /// Represents a Git Object Interface diff --git a/EPS.SDK/Git/IRepository.cs b/EPS.SDK/Git/IRepository.cs index 65e0b94..876c820 100644 --- a/EPS.SDK/Git/IRepository.cs +++ b/EPS.SDK/Git/IRepository.cs @@ -1,9 +1,4 @@ using System; -using System.Collections.Generic; -using System.IO; -using System.Linq; -using System.Text; -using System.Threading.Tasks; namespace ExtensiblePortfolioSite.SDK.Git { @@ -26,6 +21,29 @@ namespace ExtensiblePortfolioSite.SDK.Git /// /// How far to go back in the commit tree (default 0) /// A commit - public ICommit GetCommit(uint HeadOffset = 0); + public ICommit? GetCommit(uint HeadOffset = 0); + + /// + /// Get a commit from the repository, defaults to latest + /// + /// How far to go back in the commit tree (default 0) + /// Reference to the commit + /// If the commit could be found + public bool TryGetCommit(uint HeadOffset, out ICommit? Commit); + + /// + /// Get a commit from the repository by it's hash + /// + /// Which commit to get from the repo + /// A commit + public ICommit? GetCommitByRef(string Reference); + + /// + /// Get a commit from the repository, defaults to latest + /// + /// Which commit to get from the repo + /// Reference to the commit + /// If the commit could be found + public bool TryGetCommitByRef(string Reference, out ICommit? Commit); } } diff --git a/EPS.SDK/Plugins/PluginManager.cs b/EPS.SDK/Plugins/PluginManager.cs index ec368d1..6f409b2 100644 --- a/EPS.SDK/Plugins/PluginManager.cs +++ b/EPS.SDK/Plugins/PluginManager.cs @@ -28,6 +28,7 @@ namespace ExtensiblePortfolioSite.SDK.Plugins List PublicKeyTokens = new(); PublicKeyTokens.Add(typeof(String).Assembly.GetName().GetPublicKeyToken()!); // Framework Libraries + PublicKeyTokens.Add(typeof(System.Runtime.GCSettings).Assembly.GetName().GetPublicKeyToken()!); KnownPublicKeyTokens = PublicKeyTokens.ToArray(); } @@ -42,11 +43,13 @@ namespace ExtensiblePortfolioSite.SDK.Plugins public static void LoadPlugins(string RootPath) { RootPath = Path.GetFullPath(RootPath); - if(!Directory.Exists(RootPath)) + if (!Directory.Exists(RootPath)) Directory.CreateDirectory(RootPath); LibraryPaths.Add(RootPath); foreach (String Dll in Directory.EnumerateFiles(RootPath, "*.dll", SearchOption.TopDirectoryOnly)) + { TryLoadPlugin(Path.GetFullPath(Dll, RootPath), out Plugin? _); + } } public static Boolean TryLoadPlugin(string AsmPath, out Plugin? plugin) { @@ -104,17 +107,17 @@ namespace ExtensiblePortfolioSite.SDK.Plugins TypeReference AttrType = MD.GetTypeReference((TypeReferenceHandle)Ctor.Parent); String AttrTypeName = MD.GetString(AttrType.Name); String AttrTypeNamespace = MD.GetString(AttrType.Namespace); - if ( AttrType.ResolutionScope.Kind == HandleKind.AssemblyReference && AttrTypeName == EPSPluginAttribute_type.Name && AttrTypeNamespace == EPSPluginAttribute_type.Namespace ) { + AssemblyReference AsmRef = MD.GetAssemblyReference((AssemblyReferenceHandle)AttrType.ResolutionScope); String AsmName = MD.GetString(AsmRef.Name); - if (AsmName == EPSPluginAttribute_type.Assembly.FullName) + if (AsmName == EPSPluginAttribute_type.Assembly.GetName().Name) return true; } } @@ -185,7 +188,7 @@ namespace ExtensiblePortfolioSite.SDK.Plugins } } } - throw new FileNotFoundException($"Unable to load Assembly '{AsmName}'"); + return null; } internal static IPluginUnmanagedLibrary? LoadUnmanagedLibrary(String Name) { diff --git a/GithubPlugin/GithubCommit.cs b/GithubPlugin/GithubCommit.cs new file mode 100644 index 0000000..134564f --- /dev/null +++ b/GithubPlugin/GithubCommit.cs @@ -0,0 +1,33 @@ +using ExtensiblePortfolioSite.SDK.Git; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Text.Json.Serialization; +using System.Threading.Tasks; + +namespace GithubPlugin +{ + public class GithubCommit : ICommit + { + [JsonIgnore] + public IReadOnlyCollection ModifiedFiles { get; internal set; } + + [JsonIgnore] + public IRepository? Repository { get; internal set; } + + [JsonIgnore] + public IGitProvider? Provider { get; internal set; } + + [JsonPropertyName("sha")] + public string? Hash { get; private set; } + + [JsonPropertyName("message")] + public string? Description { get; private set; } + + public GitReference GetReference() + { + return new GitReference(GitReferenceKind.Commit, $"{Repository.Owner}/{Repository.Name}/{Hash}"); + } + } +} diff --git a/GithubPlugin/GithubPlugin.csproj b/GithubPlugin/GithubPlugin.csproj new file mode 100644 index 0000000..edd9f26 --- /dev/null +++ b/GithubPlugin/GithubPlugin.csproj @@ -0,0 +1,13 @@ + + + + net6.0 + enable + enable + + + + + + + diff --git a/GithubPlugin/GithubProvider.cs b/GithubPlugin/GithubProvider.cs new file mode 100644 index 0000000..c3f3d48 --- /dev/null +++ b/GithubPlugin/GithubProvider.cs @@ -0,0 +1,147 @@ +using ExtensiblePortfolioSite.SDK.Git; +using System.Text.Json; +using System.Text.RegularExpressions; + +namespace GithubPlugin +{ + [GitProvider("github.com")] + public class GithubProvider : IGitProvider + { + private HttpClient httpClient = new HttpClient(); + + private Regex userPattern = new Regex("^(http(s)*:\\/\\/)(www\\.)*(github\\.com\\/[A-z]+)$", RegexOptions.Compiled | RegexOptions.IgnoreCase); + private Regex repoPattern = new Regex("^(http(s)*:\\/\\/)(www\\.)*(github\\.com\\/[A-z]+\\/[A-z]+)$", RegexOptions.Compiled | RegexOptions.IgnoreCase); + + public Dictionary Users = new(); + public Dictionary Repositories = new(); + + public GithubProvider() + { + this.httpClient.BaseAddress = new("https://api.github.com/"); + this.httpClient.DefaultRequestHeaders.Add("User-Agent", "KoromaruKoruko"); + this.httpClient.DefaultRequestHeaders.Add("Authorization", "Bearer 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"); + } + + public IGitObject GetByReference(GitReference Reference) + { + switch(Reference.Kind) + { + case GitReferenceKind.User: + if(TryGetUserByName(Reference.ReferenceString, out IUser? user)) + return user!; + else + return null; + case GitReferenceKind.Repository: + { + if (TryGetRepositoryByName( + Reference.ReferenceString.Split('/')[0], Reference.ReferenceString.Split('/')[1], out IRepository? repo)) + return repo!; + else + return null; + } + case GitReferenceKind.Commit: + { + if (TryGetRepositoryByName( + Reference.ReferenceString.Split('/')[0], Reference.ReferenceString.Split('/')[1], out IRepository? repo)) + { + if (repo!.TryGetCommitByRef(Reference.ReferenceString.Split('/')[2], out ICommit? commit)) + return commit!; + else + return null; + } + else + return null; + } + } + return null; + } + + public bool TryGetRepositoryByName(string Username, string RepositoryName, out IRepository? Repository) + { + if (TryGetUserByName(Username, out IUser? User)) + return TryGetRepositoryByName(User!, RepositoryName, out Repository); + Repository = null; + return false; + } + + public bool TryGetRepositoryByName(IUser User, string RepositoryName, out IRepository? Repository) + { + if(User.GetType() == typeof(GithubUser)) + { + if (Repositories.TryGetValue(RepositoryName, out GithubRepo? _repo)) + { + Repository = _repo; + return true; + } else + { + var response = httpClient.GetAsync($"repos/{User.Name}/{RepositoryName}").Result; + if (!response.IsSuccessStatusCode) + { + Repository = null; + return false; + } + GithubRepo tempRepo = JsonSerializer.Deserialize(response.Content.ReadAsStream())!; + tempRepo.Provider = this; + tempRepo.Owner = User; + Repository = tempRepo; + return true; + } + } + Repository = null; + return false; + } + + public bool TryGetUserByName(string Username, out IUser? User) + { + if(Users.TryGetValue(Username, out GithubUser? _user)) + { + User = _user; + return true; + } else + { + var response = httpClient.GetAsync($"users/{Username}").Result; + if(!response.IsSuccessStatusCode) + { + User = null; + return false; + } + GithubUser tempUser = JsonSerializer.Deserialize(response.Content.ReadAsStream())!; + tempUser.Provider = this; + User = tempUser; + return true; + } + } + + public bool TryGetRepositoryByURL(Uri RepoUrl, out IRepository? Repository) + { + if (repoPattern.IsMatch(RepoUrl.OriginalString)) + { + var split = RepoUrl.OriginalString.Split("://")[1].Split('/'); + return TryGetRepositoryByName(split[1], split[2], out Repository); + } else + { + Repository = null; + return false; + } + } + + public bool TryGetUserByURL(Uri UserProfile, out IUser? User) + { + if (userPattern.IsMatch(UserProfile.OriginalString)) + { + var split = UserProfile.OriginalString.Split("://")[1].Split('/'); + return TryGetUserByName(split[1], out User); + } + else + { + User = null; + return false; + } + } + + public HttpResponseMessage GetAPIResource(string resourceLocation) + { + return httpClient.GetAsync(resourceLocation).Result; + } + } +} \ No newline at end of file diff --git a/GithubPlugin/GithubRepo.cs b/GithubPlugin/GithubRepo.cs new file mode 100644 index 0000000..26e6e6a --- /dev/null +++ b/GithubPlugin/GithubRepo.cs @@ -0,0 +1,106 @@ +using ExtensiblePortfolioSite.SDK.Git; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Text.Json; +using System.Text.Json.Serialization; +using System.Threading.Tasks; + +namespace GithubPlugin +{ + public class GithubRepo : IRepository + { + [JsonPropertyName("name")] + public string Name { get; private set; } + + [JsonPropertyName("description")] + public string Description { get; private set; } + + [JsonIgnore] + public IUser Owner { get; internal set; } + + [JsonIgnore] + public IGitProvider Provider { get; internal set; } + + public ICommit? GetCommit(uint HeadOffset = 0) + { + var response = Provider.GetAPIResource($"repos/{Owner.Name}/{Name}/commits"); + if(response.IsSuccessStatusCode) + { + JsonDocument json = JsonDocument.Parse(response.Content.ReadAsStream()); + JsonElement commitObject = json.RootElement.EnumerateArray().Skip((int)HeadOffset).First(); + GithubCommit commit = JsonSerializer.Deserialize(commitObject.GetProperty("commit"))!; + commit.Provider = Provider; + commit.Repository = this; + List tempFiles = new List(); + JsonElement files = commitObject.GetProperty("files"); + foreach(JsonElement file in files.EnumerateArray()) + { + tempFiles.Add(file.GetProperty("filename").GetString()!); + } + commit.ModifiedFiles = tempFiles; + return commit; + } else + { + return null; + } + } + + public ICommit? GetCommitByRef(string reference) + { + var response = Provider.GetAPIResource($"repos/{Owner.Name}/{Name}/commits/{reference}"); + if (response.IsSuccessStatusCode) + { + JsonDocument json = JsonDocument.Parse(response.Content.ReadAsStream()); + JsonElement commitObject = json.RootElement; + GithubCommit commit = JsonSerializer.Deserialize(commitObject.GetProperty("commit"))!; + commit.Provider = Provider; + commit.Repository = this; + List tempFiles = new List(); + JsonElement files = commitObject.GetProperty("files"); + foreach (JsonElement file in files.EnumerateArray()) + { + tempFiles.Add(file.GetProperty("filename").GetString()!); + } + commit.ModifiedFiles = tempFiles; + return commit; + } + else + { + return null; + } + } + + public GitReference GetReference() + { + return new GitReference(GitReferenceKind.Repository, $"{Owner}/{Name}"); + } + + public bool TryGetCommit(uint HeadOffset, out ICommit? Commit) + { + var commit = GetCommit(HeadOffset); + if (commit != null) + { + Commit = commit; + return true; + } + + Commit = null; + return false; + } + + public bool TryGetCommitByRef(string Reference, out ICommit? Commit) + { + var commit = GetCommitByRef(Reference); + if (commit != null) + { + Commit = commit; + return true; + } + + Commit = null; + return false; + } + } +} diff --git a/GithubPlugin/GithubUser.cs b/GithubPlugin/GithubUser.cs new file mode 100644 index 0000000..9739e58 --- /dev/null +++ b/GithubPlugin/GithubUser.cs @@ -0,0 +1,47 @@ +using ExtensiblePortfolioSite.SDK.Git; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Text.Json; +using System.Text.Json.Serialization; +using System.Threading.Tasks; + +namespace GithubPlugin +{ + public class GithubUser : IUser + { + [JsonPropertyName("login")] + public string Name { get; private set; } + + [JsonPropertyName("id")] + public int Identifier { get; private set; } + + [JsonPropertyName("avatar_url")] + public string AvatarURL { get; private set; } + + [JsonIgnore] + public IGitProvider Provider { get; internal set; } + + public GitReference GetReference() + { + return new GitReference(GitReferenceKind.User, $"{Name}"); + } + + public IEnumerable GetUserRepositories() + { + var response = Provider.GetAPIResource($"users/{Name}/repos"); + if(response.IsSuccessStatusCode) + { + JsonDocument json = JsonDocument.Parse(response.Content.ReadAsStream()); + List ret = new List(json.RootElement.GetArrayLength()); + foreach(JsonElement repo in json.RootElement.EnumerateArray()) + { + ret.Add(JsonSerializer.Deserialize(repo)!); + } + return ret; + } + return new List(); + } + } +} diff --git a/GithubPlugin/Properties/AssemblyInfo.cs b/GithubPlugin/Properties/AssemblyInfo.cs new file mode 100644 index 0000000..390bfa9 --- /dev/null +++ b/GithubPlugin/Properties/AssemblyInfo.cs @@ -0,0 +1,3 @@ +using ExtensiblePortfolioSite.SDK.Plugins; + +[assembly: EPSPlugin("Github", false, 1, 0)] \ No newline at end of file