Compare commits
No commits in common. "master" and "canadian_dev" have entirely different histories.
master
...
canadian_d
@ -1,40 +0,0 @@
|
||||
using ExtensiblePortfolioSite.SDK.Resources;
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace ExtensiblePortfolioSite.SDK.Caching
|
||||
{
|
||||
/// <summary>
|
||||
/// Global Item Cache
|
||||
/// </summary>
|
||||
public static class Cache
|
||||
{
|
||||
internal static TimeSpan GitUserLifetime = TimeSpan.FromMinutes(1440);
|
||||
internal static TimeSpan GitRepositoryLifetime = TimeSpan.FromMinutes(120);
|
||||
|
||||
private static readonly SortedDictionary<String, GitServiceCache> CacheLookup = new();
|
||||
|
||||
/// <summary>
|
||||
/// Retrives the specified <see cref="GitServiceCache"/> for the given provider
|
||||
/// </summary>
|
||||
/// <param name="ServiceProvider">Git Service Provider Name</param>
|
||||
/// <returns>Git Service Cache</returns>
|
||||
public static GitServiceCache GetGitServiceCache(String ServiceProvider)
|
||||
{
|
||||
if (CacheLookup.TryGetValue(ServiceProvider, out GitServiceCache? cache))
|
||||
return cache;
|
||||
lock (CacheLookup)
|
||||
{
|
||||
if (CacheLookup.TryGetValue(ServiceProvider, out cache))
|
||||
return cache;
|
||||
|
||||
ServiceProvider = string.Intern(ServiceProvider);
|
||||
|
||||
cache = new GitServiceCache(ServiceProvider);
|
||||
CacheLookup.Add(ServiceProvider, cache);
|
||||
return cache;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1,16 +0,0 @@
|
||||
using ExtensiblePortfolioSite.SDK.Git;
|
||||
|
||||
using System;
|
||||
|
||||
namespace ExtensiblePortfolioSite.SDK.Caching
|
||||
{
|
||||
internal sealed class CacheRepo : GitCacheItemBase<IRepository>
|
||||
{
|
||||
private readonly Uri Repository;
|
||||
|
||||
internal CacheRepo(GitServiceCache Parent, Uri Repository) : base(Parent) => this.Repository = Repository;
|
||||
|
||||
protected override TimeSpan Lifetime => Cache.GitRepositoryLifetime;
|
||||
protected override IRepository RetriveValue() => this.Parent.ServiceProvider.GetRepository(this.Repository);
|
||||
}
|
||||
}
|
||||
@ -1,16 +0,0 @@
|
||||
using ExtensiblePortfolioSite.SDK.Git;
|
||||
|
||||
using System;
|
||||
|
||||
namespace ExtensiblePortfolioSite.SDK.Caching
|
||||
{
|
||||
internal sealed class CacheUser : GitCacheItemBase<IUser>
|
||||
{
|
||||
private readonly Uri User;
|
||||
|
||||
internal CacheUser(GitServiceCache Parent, Uri User) : base(Parent) => this.User = User;
|
||||
|
||||
protected override TimeSpan Lifetime => Cache.GitRepositoryLifetime;
|
||||
protected override IUser RetriveValue() => this.Parent.ServiceProvider.GetUser(this.User);
|
||||
}
|
||||
}
|
||||
@ -1,86 +0,0 @@
|
||||
using System;
|
||||
|
||||
namespace ExtensiblePortfolioSite.SDK.Caching
|
||||
{
|
||||
/// <summary>
|
||||
/// Base Git Cache Item Class
|
||||
/// </summary>
|
||||
public abstract class GitCacheItemBase : IDisposable, ICacheItem
|
||||
{
|
||||
/// <summary>
|
||||
/// the <see cref="GitServiceCache"/> this Item is bound to
|
||||
/// </summary>
|
||||
protected readonly GitServiceCache Parent;
|
||||
|
||||
|
||||
/// <inheritdoc cref="GitCacheItemBase"/>
|
||||
/// <param name="Parent"><see cref="GitServiceCache"/> that this Item is boud to</param>
|
||||
public GitCacheItemBase(GitServiceCache Parent)
|
||||
{
|
||||
this.Parent = Parent;
|
||||
Parent.Register(this);
|
||||
}
|
||||
///
|
||||
~GitCacheItemBase() => this.Disposing();
|
||||
|
||||
/// <inheritdoc cref="ICacheItem.ExpiresOn"/>
|
||||
public abstract DateTime ExpiresOn { get; }
|
||||
/// <inheritdoc cref="ICacheItem.Invalidate"/>
|
||||
public abstract void Invalidate();
|
||||
|
||||
/// <inheritdoc cref="IDisposable.Dispose"/>
|
||||
public void Dispose()
|
||||
{
|
||||
GC.SuppressFinalize(this);
|
||||
this.Disposing();
|
||||
}
|
||||
private void Disposing() => this.Parent.Unregister(this);
|
||||
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Base Git Cache Item Class
|
||||
/// </summary>
|
||||
/// <typeparam name="T">Cache </typeparam>
|
||||
public abstract class GitCacheItemBase<T> : GitCacheItemBase, ICacheItem<T>
|
||||
{
|
||||
private T CacheValue;
|
||||
private DateTime RetrivedTime;
|
||||
|
||||
internal GitCacheItemBase(GitServiceCache Parent) : base(Parent)
|
||||
{
|
||||
this.RetrivedTime = DateTime.MinValue;
|
||||
this.CacheValue = default!;
|
||||
}
|
||||
|
||||
/// <inheritdoc cref="GitCacheItemBase.Invalidate"/>
|
||||
public override void Invalidate()
|
||||
{
|
||||
this.CacheValue = default!;
|
||||
this.RetrivedTime = DateTime.MinValue;
|
||||
}
|
||||
/// <inheritdoc cref="ICacheItem{T}.GetValue"/>
|
||||
public T GetValue()
|
||||
{
|
||||
if (DateTime.Now - this.RetrivedTime > this.Lifetime)
|
||||
{
|
||||
this.CacheValue = RetriveValue();
|
||||
this.RetrivedTime = DateTime.Now;
|
||||
}
|
||||
return this.CacheValue!;
|
||||
}
|
||||
|
||||
/// <inheritdoc cref="GitCacheItemBase.ExpiresOn"/>
|
||||
public override DateTime ExpiresOn => RetrivedTime.Add(Lifetime);
|
||||
|
||||
/// <summary>
|
||||
/// Get Expected Cache Item Lifetime
|
||||
/// </summary>
|
||||
protected abstract TimeSpan Lifetime { get; }
|
||||
/// <summary>
|
||||
/// Retrive the Cache Item
|
||||
/// </summary>
|
||||
/// <returns>Cache Item</returns>
|
||||
protected abstract T RetriveValue();
|
||||
}
|
||||
}
|
||||
@ -1,107 +0,0 @@
|
||||
using ExtensiblePortfolioSite.SDK.Git;
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace ExtensiblePortfolioSite.SDK.Caching
|
||||
{
|
||||
/// <summary>
|
||||
/// Git Service Cache Provider
|
||||
/// </summary>
|
||||
public sealed class GitServiceCache : IGitCacheProvider, IDisposable
|
||||
{
|
||||
/// <inheritdoc cref="IGitCacheProvider.ServiceProvider"/>
|
||||
public GitService ServiceProvider { get; }
|
||||
private readonly LinkedList<GitCacheItemBase> CacheItems = new();
|
||||
private Boolean _disposing = false;
|
||||
|
||||
internal GitServiceCache(String Provider) : this(GitManager.GetService(Provider)) { }
|
||||
internal GitServiceCache(GitService ServiceProvider)
|
||||
{
|
||||
this.ServiceProvider = ServiceProvider;
|
||||
this.ServiceProvider.OnChange += ServiceProvider_OnChange;
|
||||
}
|
||||
|
||||
private void ServiceProvider_OnChange(GitService Git)
|
||||
{
|
||||
lock(this.CacheItems)
|
||||
{
|
||||
foreach (GitCacheItemBase Item in this.CacheItems)
|
||||
Item.Invalidate();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private readonly SortedDictionary<String, GitCacheItemBase<IUser>> UserCaches = new();
|
||||
private readonly SortedDictionary<String, GitCacheItemBase<IRepository>> RepositoryCaches = new();
|
||||
|
||||
|
||||
/// <inheritdoc cref="IGitCacheProvider.GetUser(Uri)"/>
|
||||
public GitCacheItemBase<IUser> GetUser(Uri UserProfile)
|
||||
{
|
||||
String url = UserProfile.ToString();
|
||||
if (UserCaches.TryGetValue(url, out GitCacheItemBase<IUser>? cache))
|
||||
return cache;
|
||||
|
||||
lock (UserCaches)
|
||||
{
|
||||
if (UserCaches.TryGetValue(url, out cache))
|
||||
return cache;
|
||||
|
||||
cache = new CacheUser(this, UserProfile);
|
||||
UserCaches.Add(url, cache);
|
||||
return cache;
|
||||
}
|
||||
}
|
||||
/// <inheritdoc cref="IGitCacheProvider.GetRepository(Uri)"/>
|
||||
public GitCacheItemBase<IRepository> GetRepository(Uri RepositoryURL)
|
||||
{
|
||||
String url = RepositoryURL.ToString();
|
||||
if (RepositoryCaches.TryGetValue(url, out GitCacheItemBase<IRepository>? cache))
|
||||
return cache;
|
||||
|
||||
lock (RepositoryCaches)
|
||||
{
|
||||
if (RepositoryCaches.TryGetValue(url, out cache))
|
||||
return cache;
|
||||
|
||||
cache = new CacheRepo(this, RepositoryURL);
|
||||
RepositoryCaches.Add(url, cache);
|
||||
return cache;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/// <inheritdoc cref="ICacheProvider{GitCacheItemBase}.Register(GitCacheItemBase)"/>
|
||||
public void Register(GitCacheItemBase CacheItem)
|
||||
{
|
||||
if (!_disposing)
|
||||
lock (CacheItems)
|
||||
{
|
||||
this.CacheItems.AddFirst(CacheItem);
|
||||
}
|
||||
}
|
||||
/// <inheritdoc cref="ICacheProvider{GitCacheItemBase}.Unregister(GitCacheItemBase)"/>
|
||||
public void Unregister(GitCacheItemBase CacheItem)
|
||||
{
|
||||
if (!_disposing)
|
||||
lock (CacheItems)
|
||||
if (this.CacheItems.Remove(CacheItem)) ;
|
||||
}
|
||||
|
||||
|
||||
/// <inheritdoc cref="IDisposable"/>
|
||||
public void Dispose()
|
||||
{
|
||||
_disposing = true;
|
||||
lock (CacheItems)
|
||||
{
|
||||
this.ServiceProvider.OnChange -= ServiceProvider_OnChange;
|
||||
this.RepositoryCaches.Clear();
|
||||
this.UserCaches.Clear();
|
||||
foreach (GitCacheItemBase Item in CacheItems)
|
||||
Item.Dispose();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1,84 +0,0 @@
|
||||
using ExtensiblePortfolioSite.SDK.Git;
|
||||
|
||||
using System;
|
||||
|
||||
namespace ExtensiblePortfolioSite.SDK.Caching
|
||||
{
|
||||
/// <summary>
|
||||
/// Represents a Cache Item Provider
|
||||
/// </summary>
|
||||
/// <typeparam name="TCacheItemType">Cache Item Base Type</typeparam>
|
||||
public interface ICacheProvider<TCacheItemType> where TCacheItemType : ICacheItem
|
||||
{
|
||||
/// <summary>
|
||||
/// Registers a Cache Item
|
||||
/// </summary>
|
||||
/// <param name="Item"></param>
|
||||
void Register(TCacheItemType Item);
|
||||
|
||||
/// <summary>
|
||||
/// Unregisters a Cache Item
|
||||
/// </summary>
|
||||
/// <param name="Item"></param>
|
||||
void Unregister(TCacheItemType Item);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Represents a Cache Item
|
||||
/// </summary>
|
||||
public interface ICacheItem
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets the DateTime that this item expires on
|
||||
/// </summary>
|
||||
DateTime ExpiresOn { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Invalidates the Cache Item
|
||||
/// </summary>
|
||||
void Invalidate();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Represents a Cache Item of Type <typeparamref name="T"/>
|
||||
/// </summary>
|
||||
/// <typeparam name="T">Cache Item Type</typeparam>
|
||||
public interface ICacheItem<T> : ICacheItem
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets the Requested Cache Item
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
T GetValue();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Git Cache Invalidate or Changed Delegate Callback
|
||||
/// </summary>
|
||||
public delegate void GitCacheInvalidateDelegate();
|
||||
|
||||
/// <summary>
|
||||
/// A Git Cache Provider
|
||||
/// </summary>
|
||||
public interface IGitCacheProvider : ICacheProvider<GitCacheItemBase>
|
||||
{
|
||||
/// <summary>
|
||||
/// The Service Provider tied to this Cache
|
||||
/// </summary>
|
||||
public GitService ServiceProvider { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Get a User by its URL
|
||||
/// </summary>
|
||||
/// <param name="UserProfile">User Profile URL</param>
|
||||
/// <returns></returns>
|
||||
public GitCacheItemBase<IUser> GetUser(Uri UserProfile);
|
||||
/// <summary>
|
||||
/// Get a Repositroy by its URL
|
||||
/// </summary>
|
||||
/// <param name="RepositoryURL">Repository URL</param>
|
||||
/// <returns></returns>
|
||||
public GitCacheItemBase<IRepository> GetRepository(Uri RepositoryURL);
|
||||
}
|
||||
|
||||
}
|
||||
@ -1,4 +1,4 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net6.0</TargetFramework>
|
||||
@ -9,8 +9,4 @@
|
||||
<GenerateDocumentationFile>True</GenerateDocumentationFile>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.AspNetCore.Mvc.RazorPages" Version="2.2.5" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
|
||||
@ -1,39 +1,39 @@
|
||||
using ExtensiblePortfolioSite.SDK.Plugins;
|
||||
|
||||
using System;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace ExtensiblePortfolioSite.SDK.Git
|
||||
{
|
||||
/// <summary>
|
||||
/// Git Manager for Getting Different Git Services
|
||||
/// </summary>
|
||||
public static class GitManager
|
||||
internal static class GitManager
|
||||
{
|
||||
private static readonly SortedDictionary<String, GitService> Providers = new();
|
||||
|
||||
internal static GitService GetOrCreate(String Service)
|
||||
public static void Register(String Service, IGitProvider Provider)
|
||||
{
|
||||
if (Providers.TryGetValue(Service, out GitService? Git))
|
||||
return Git;
|
||||
|
||||
lock(Providers)
|
||||
if (Providers.TryGetValue(Service, out GitService? GitService))
|
||||
{
|
||||
if (Providers.TryGetValue(Service, out Git))
|
||||
return Git;
|
||||
GitService.addProvider(Provider);
|
||||
return;
|
||||
}
|
||||
lock (Providers)
|
||||
{
|
||||
if (Providers.TryGetValue(Service, out GitService))
|
||||
{
|
||||
GitService.addProvider(Provider);
|
||||
return;
|
||||
}
|
||||
|
||||
Git = new GitService(Service);
|
||||
Providers.Add(Service, Git);
|
||||
return Git;
|
||||
GitService = new GitService(Service);
|
||||
GitService.addProvider(Provider);
|
||||
Providers.Add(Service, GitService);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get a Git Service Provider
|
||||
/// </summary>
|
||||
/// <param name="Service"></param>
|
||||
/// <exception cref="KeyNotFoundException"/>
|
||||
/// <returns></returns>
|
||||
public static GitService GetService(String Service) => Providers[Service];
|
||||
public static void Unregister(String Service, IGitProvider Provider)
|
||||
{
|
||||
if (Providers.TryGetValue(Service, out GitService? GitService))
|
||||
GitService.removeProvider(Provider);
|
||||
}
|
||||
|
||||
public static GitService GetService(String Website) => Providers[Website];
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,99 +1,54 @@
|
||||
using ExtensiblePortfolioSite.SDK.Caching;
|
||||
using ExtensiblePortfolioSite.SDK.Plugins;
|
||||
|
||||
using System;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace ExtensiblePortfolioSite.SDK.Git
|
||||
{
|
||||
/// <summary>
|
||||
/// Git Service Changed Delegate/Callback
|
||||
/// </summary>
|
||||
/// <param name="Git">Sender</param>
|
||||
public delegate void GitServiceChangedDelegate(GitService Git);
|
||||
|
||||
/// <summary>
|
||||
/// Git Service
|
||||
/// </summary>
|
||||
public sealed class GitService
|
||||
internal class GitService
|
||||
{
|
||||
/// <summary>
|
||||
/// Git Service Name
|
||||
/// </summary>
|
||||
public String Service { get; }
|
||||
|
||||
/// <summary>
|
||||
/// OnChanged Event Callback
|
||||
/// </summary>
|
||||
public event GitServiceChangedDelegate? OnChange;
|
||||
|
||||
internal GitService(String Service) => this.Service = Service;
|
||||
|
||||
private readonly SortedDictionary<String, IGitProvider> Providers = new();
|
||||
|
||||
internal void I_RemoveProvider(Plugin FromPlugin, IGitProvider provider)
|
||||
public GitService(String Service)
|
||||
{
|
||||
lock (this.Providers)
|
||||
this.Providers.Add(FromPlugin.Info.Name, provider);
|
||||
|
||||
OnChange?.Invoke(this);
|
||||
}
|
||||
internal void I_AddProvider(Plugin FromPlugin, IGitProvider provider)
|
||||
{
|
||||
lock (this.Providers)
|
||||
this.Providers.Add(FromPlugin.Info.Name, provider);
|
||||
|
||||
OnChange?.Invoke(this);
|
||||
this.Service = Service;
|
||||
}
|
||||
|
||||
private GitServiceCache? ServiceCache;
|
||||
private readonly List<IGitProvider> Providers = new();
|
||||
|
||||
/// <summary>
|
||||
/// Gets the Service Cache
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public GitServiceCache GetCache() => this.ServiceCache ??= Cache.GetGitServiceCache(this.Service);
|
||||
|
||||
/// <summary>
|
||||
/// Attempt to Retrive a GitProvider by its PluginName
|
||||
/// </summary>
|
||||
/// <param name="PluginName"></param>
|
||||
/// <param name="Provider"></param>
|
||||
/// <returns></returns>
|
||||
public void TryGetProviderByPlugin(String PluginName, out IGitProvider? Provider) => this.Providers.TryGetValue(PluginName, out Provider);
|
||||
|
||||
/// <summary>
|
||||
/// Retrives a Specified User by URL
|
||||
/// </summary>
|
||||
/// <param name="URL"></param>
|
||||
/// <returns></returns>
|
||||
/// <exception cref="GitObjectNotFoundException"></exception>
|
||||
public IUser GetUser(Uri URL)
|
||||
internal void removeProvider(IGitProvider provider)
|
||||
{
|
||||
lock (this.Providers)
|
||||
foreach (IGitProvider Provider in this.Providers.Values)
|
||||
if (Provider.TryGetUserByURL(URL, out IUser? User))
|
||||
this.Providers.Remove(provider);
|
||||
}
|
||||
internal void addProvider(IGitProvider provider)
|
||||
{
|
||||
lock (this.Providers)
|
||||
this.Providers.Add(provider);
|
||||
}
|
||||
|
||||
public IUser GetUser(Uri Path)
|
||||
{
|
||||
lock (this.Providers)
|
||||
{
|
||||
foreach(IGitProvider Provider in this.Providers)
|
||||
if (Provider.TryGetUserByURL(Path, out IUser? User))
|
||||
return User!;
|
||||
|
||||
throw new GitObjectNotFoundException(GitReferenceKind.User, URL);
|
||||
|
||||
|
||||
throw new GitObjectNotFoundException(GitReferenceKind.User, Path);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Retrives a Specified Repository by URL
|
||||
/// </summary>
|
||||
/// <param name="URL"></param>
|
||||
/// <returns></returns>
|
||||
/// <exception cref="GitObjectNotFoundException"></exception>
|
||||
public IRepository GetRepository(Uri URL)
|
||||
public IRepository GetRepository(Uri Path)
|
||||
{
|
||||
lock (this.Providers)
|
||||
foreach (IGitProvider Provider in this.Providers.Values)
|
||||
if (Provider.TryGetRepositoryByURL(URL, out IRepository? Repository))
|
||||
{
|
||||
foreach (IGitProvider Provider in this.Providers)
|
||||
if (Provider.TryGetRepositoryByURL(Path, out IRepository? Repository))
|
||||
return Repository!;
|
||||
|
||||
throw new GitObjectNotFoundException(GitReferenceKind.Repository, URL);
|
||||
|
||||
throw new GitObjectNotFoundException(GitReferenceKind.User, Path);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -20,10 +20,5 @@ namespace ExtensiblePortfolioSite.SDK.Git
|
||||
/// List of files modified in the commit
|
||||
/// </summary>
|
||||
public IReadOnlyCollection<String> ModifiedFiles { get; }
|
||||
|
||||
/// <summary>
|
||||
/// URL to the commit author's avatar
|
||||
/// </summary>
|
||||
public string AuthorAvatarUrl { get; }
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,7 +1,30 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace ExtensiblePortfolioSite.SDK.Git
|
||||
{
|
||||
/// <summary>
|
||||
/// Git Providers marked withi this attribute will be auto-imported at startup!
|
||||
/// </summary>
|
||||
[AttributeUsage(AttributeTargets.Class, AllowMultiple = true, Inherited = false)]
|
||||
public sealed class GitProviderAttribute : Attribute
|
||||
{
|
||||
/// <summary>
|
||||
/// The Service which this GitProvider is for
|
||||
/// </summary>
|
||||
public String WebSite { get; }
|
||||
|
||||
/// <inheritdoc cref="GitProviderAttribute"/>
|
||||
/// <param name="WebSite">The Service which this GitProvider is for</param>
|
||||
public GitProviderAttribute(String WebSite)
|
||||
{
|
||||
this.WebSite = WebSite;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// a Git Provider
|
||||
/// </summary>
|
||||
@ -26,19 +49,19 @@ namespace ExtensiblePortfolioSite.SDK.Git
|
||||
/// <summary>
|
||||
/// Attempts to Get a User Profile by its Website URL
|
||||
/// </summary>
|
||||
public Boolean TryGetRepositoryByURL(Uri URL, out IRepository? Repository);
|
||||
public Boolean TryGetRepositoryByURL(Uri UserProfile, out IRepository? Repository);
|
||||
|
||||
/// <summary>
|
||||
/// Get a User Profile by its Website URL
|
||||
/// </summary>
|
||||
public IUser GetUserByURL(Uri URL) =>
|
||||
TryGetUserByURL(URL, out IUser? user)
|
||||
public IUser GetUserByURL(Uri UserProfile) =>
|
||||
TryGetUserByURL(UserProfile, out IUser? user)
|
||||
? user!
|
||||
: throw new GitObjectNotFoundException(GitReferenceKind.User, URL);
|
||||
: throw new GitObjectNotFoundException(GitReferenceKind.User, UserProfile);
|
||||
|
||||
/// <summary>
|
||||
/// Attempts to Get a User Profile by its Website URL
|
||||
/// </summary>
|
||||
public Boolean TryGetUserByURL(Uri URL, out IUser? User);
|
||||
public Boolean TryGetUserByURL(Uri UserProfile, out IUser? User);
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,70 +0,0 @@
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Net.Http;
|
||||
using System.Net.Http.Json;
|
||||
using System.Text.Json;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace ExtensiblePortfolioSite.SDK
|
||||
{
|
||||
/// <summary>
|
||||
/// Provides a simple Async HTTP Client
|
||||
/// </summary>
|
||||
public static class HTTP
|
||||
{
|
||||
private static readonly HttpClient Client = new();
|
||||
|
||||
|
||||
///
|
||||
public static Stream GetStream(Uri URL) => Client.GetStreamAsync(URL).ConfigureAwait(false).GetAwaiter().GetResult();
|
||||
///
|
||||
public static async Task<Stream> GetStreamAsync(Uri URL) => await Client.GetStreamAsync(URL);
|
||||
///
|
||||
public static async Task<Stream> GetStreamAsync(Uri URL, CancellationToken cancellationToken = default) => await Client.GetStreamAsync(URL, cancellationToken);
|
||||
|
||||
|
||||
///
|
||||
public static HttpResponseMessage Post(Uri URL, HttpContent content) => Client.PostAsync(URL, content).ConfigureAwait(false).GetAwaiter().GetResult();
|
||||
///
|
||||
public static async Task<HttpResponseMessage> PostAsync(Uri URL, HttpContent content) => await Client.PostAsync(URL, content);
|
||||
///
|
||||
public static async Task<HttpResponseMessage> PostAsync(Uri URL, HttpContent content, CancellationToken cancellationToken = default) => await Client.PostAsync(URL, content, cancellationToken);
|
||||
|
||||
|
||||
///
|
||||
public static String GetString(Uri URL) => Client.GetStringAsync(URL).ConfigureAwait(false).GetAwaiter().GetResult();
|
||||
///
|
||||
public static async Task<String> GetStringAsync(Uri URL) => await Client.GetStringAsync(URL);
|
||||
///
|
||||
public static async Task<String> GetStringAsync(Uri URL, CancellationToken cancellationToken = default) => await Client.GetStringAsync(URL, cancellationToken);
|
||||
|
||||
|
||||
///
|
||||
public static T? GetArtifact<T>(Uri URL)
|
||||
{
|
||||
using Stream stm = GetStream(URL);
|
||||
return JsonSerializer.Deserialize<T>(stm);
|
||||
}
|
||||
///
|
||||
public static async ValueTask<T?> GetArtifactAsync<T>(Uri URL)
|
||||
{
|
||||
using Stream stm = await GetStreamAsync(URL);
|
||||
return await JsonSerializer.DeserializeAsync<T>(stm);
|
||||
}
|
||||
///
|
||||
public static async ValueTask<T?> GetArtifactAsync<T>(Uri URL, CancellationToken cancellationToken = default)
|
||||
{
|
||||
using Stream stm = await GetStreamAsync(URL, cancellationToken);
|
||||
return await JsonSerializer.DeserializeAsync<T>(stm, cancellationToken: cancellationToken);
|
||||
}
|
||||
|
||||
|
||||
///
|
||||
public static HttpResponseMessage PostArtifact<T>(Uri URL, T Obj) => Client.PostAsJsonAsync<T>(URL, Obj).ConfigureAwait(false).GetAwaiter().GetResult();
|
||||
///
|
||||
public static async Task<HttpResponseMessage> PostArtifactAsync<T>(Uri URL, T Obj) => await Client.PostAsJsonAsync<T>(URL, Obj);
|
||||
///
|
||||
public static async Task<HttpResponseMessage> PostArtifactAsync<T>(Uri URL, T Obj, CancellationToken cancellationToken = default) => await Client.PostAsJsonAsync<T>(URL, Obj, cancellationToken);
|
||||
}
|
||||
}
|
||||
@ -1,75 +0,0 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Text.Json;
|
||||
using System.Text.Json.Serialization;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace ExtensiblePortfolioSite.SDK
|
||||
{
|
||||
public static class Json
|
||||
{
|
||||
private static class DefaultConverters
|
||||
{
|
||||
public sealed class UriConverter : JsonConverter<Uri>
|
||||
{
|
||||
public override Uri? Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
|
||||
{
|
||||
String? s = reader.GetString();
|
||||
return s == null ? null : new Uri(s);
|
||||
}
|
||||
|
||||
public override void Write(Utf8JsonWriter writer, Uri value, JsonSerializerOptions options)
|
||||
{
|
||||
writer.WriteStringValue(value.ToString());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static readonly JsonSerializerOptions Options = new()
|
||||
{
|
||||
AllowTrailingCommas = true,
|
||||
DefaultBufferSize = 4098,
|
||||
DefaultIgnoreCondition = JsonIgnoreCondition.Never,
|
||||
DictionaryKeyPolicy = JsonNamingPolicy.CamelCase,
|
||||
IgnoreReadOnlyFields = true,
|
||||
IgnoreReadOnlyProperties = true,
|
||||
IncludeFields = true,
|
||||
MaxDepth = 64,
|
||||
NumberHandling = JsonNumberHandling.AllowNamedFloatingPointLiterals | JsonNumberHandling.AllowReadingFromString,
|
||||
PropertyNameCaseInsensitive = true,
|
||||
PropertyNamingPolicy = JsonNamingPolicy.CamelCase,
|
||||
ReadCommentHandling = JsonCommentHandling.Skip,
|
||||
UnknownTypeHandling = JsonUnknownTypeHandling.JsonElement,
|
||||
WriteIndented = false,
|
||||
};
|
||||
private static readonly IList<JsonConverter> JsonConverters = Options.Converters;
|
||||
static Json()
|
||||
{
|
||||
JsonConverters.Add(new DefaultConverters.UriConverter());
|
||||
}
|
||||
|
||||
internal static void Register(JsonConverter Converter) => JsonConverters.Add(Converter);
|
||||
internal static void Unregister(JsonConverter Converter) => JsonConverters.Remove(Converter);
|
||||
|
||||
|
||||
|
||||
|
||||
public static T? Deserialize<T>(String String) => JsonSerializer.Deserialize<T>(String, Options);
|
||||
public static T? Deserialize<T>(Stream STM) => JsonSerializer.Deserialize<T>(STM, Options);
|
||||
public static T? Deserialize<T>(ref Utf8JsonReader Reader) => JsonSerializer.Deserialize<T>(ref Reader, Options);
|
||||
public static T? Deserialize<T>(JsonElement Element) => JsonSerializer.Deserialize<T>(Element, Options);
|
||||
public static ValueTask<T?> DeserializeAsync<T>(Stream STM) => JsonSerializer.DeserializeAsync<T>(STM, Options);
|
||||
public static ValueTask<T?> DeserializeAsync<T>(Stream STM, CancellationToken cancellationToken) => JsonSerializer.DeserializeAsync<T>(STM, Options, cancellationToken);
|
||||
|
||||
|
||||
public static String Serialize<T>(T Obj) => JsonSerializer.Serialize<T>(Obj, Options);
|
||||
public static void Serialize<T>(T Obj, Stream Outp) => JsonSerializer.Serialize<T>(Outp, Obj, Options);
|
||||
public static void Serialize<T>(T Obj, Utf8JsonWriter Writer) => JsonSerializer.Serialize<T>(Writer, Obj, Options);
|
||||
|
||||
public static Task SerializeAsync<T>(T Obj, Stream Outp) => JsonSerializer.SerializeAsync<T>(Outp, Obj, Options);
|
||||
public static Task SerializeAsync<T>(T Obj, Stream Outp, CancellationToken cancellationToken) => JsonSerializer.SerializeAsync<T>(Outp, Obj, Options, cancellationToken);
|
||||
|
||||
}
|
||||
}
|
||||
@ -1,14 +0,0 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace ExtensiblePortfolioSite.SDK
|
||||
{
|
||||
/// <summary>
|
||||
/// Marks a JsonConverter to be registered at Plugin Init
|
||||
/// </summary>
|
||||
[AttributeUsage(AttributeTargets.Class, AllowMultiple = false, Inherited = true)]
|
||||
public sealed class JsonConverterAttribute : Attribute { }
|
||||
}
|
||||
@ -1,105 +0,0 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
|
||||
namespace ExtensiblePortfolioSite.SDK.Plugins
|
||||
{
|
||||
/// <summary>
|
||||
/// Internal Marker Attribute
|
||||
/// </summary>
|
||||
[AttributeUsage(AttributeTargets.Class, AllowMultiple = false, Inherited = true)]
|
||||
public abstract class EPSMarkerAttribute : Attribute { }
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Describes the ExtensiblePortfolioSite Plugin
|
||||
/// </summary>
|
||||
[AttributeUsage(AttributeTargets.Assembly, AllowMultiple = false, Inherited = false)]
|
||||
public class EPSPluginAttribute : Attribute
|
||||
{
|
||||
/// <summary>
|
||||
/// Plugin Name
|
||||
/// </summary>
|
||||
public String Name;
|
||||
|
||||
/// <summary>
|
||||
/// Plugin Version
|
||||
/// </summary>
|
||||
public PluginVersion Version;
|
||||
|
||||
/// <summary>
|
||||
/// Weather or not this Plugin Supports Runtime Upgrades
|
||||
/// </summary>
|
||||
public Boolean SupportsRuntimeUpgrades;
|
||||
|
||||
/// <summary>
|
||||
/// Plugin Auther
|
||||
/// </summary>
|
||||
public String? Auther;
|
||||
|
||||
/// <summary>
|
||||
/// Plugin Website
|
||||
/// </summary>
|
||||
public String? Website;
|
||||
|
||||
/// <inheritdoc cref="EPSPluginAttribute"/>
|
||||
/// <param name="Name">Plugin Name</param>
|
||||
/// <param name="Major">Plugin Version, Major Number</param>
|
||||
/// <param name="Minor">Plugin Version, Major Number</param>
|
||||
/// <param name="Build">Plugin Version, Build Number</param>
|
||||
/// <param name="Revision">Plugin Version, Revision Number</param>
|
||||
/// <param name="SupportsRuntimeUpgrades">Supports Runtime Upgrading of the Plugin</param>
|
||||
/// <param name="Auther">Plugin Auther</param>
|
||||
/// <param name="Website">Plugin Website/Repository</param>
|
||||
public EPSPluginAttribute(
|
||||
String Name,
|
||||
Boolean SupportsRuntimeUpgrades,
|
||||
UInt32 Major,
|
||||
UInt32 Minor,
|
||||
UInt32 Build = 0u,
|
||||
UInt32 Revision = 0u,
|
||||
String? Auther = null,
|
||||
String? Website = null
|
||||
)
|
||||
{
|
||||
this.Name = Name;
|
||||
this.Version = new PluginVersion(Major, Minor, Build, Revision);
|
||||
this.SupportsRuntimeUpgrades = SupportsRuntimeUpgrades;
|
||||
this.Auther = Auther;
|
||||
this.Website = Website;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Defineds a Resource Container
|
||||
/// </summary>
|
||||
[AttributeUsage(AttributeTargets.Class, AllowMultiple = true, Inherited = false)]
|
||||
public sealed class ResourceContainerAttribute : EPSMarkerAttribute
|
||||
{
|
||||
/// <summary>
|
||||
/// Supported Schemas
|
||||
/// </summary>
|
||||
public String[] Schemes { get; }
|
||||
|
||||
/// <inheritdoc cref="ResourceContainerAttribute"/>
|
||||
/// <param name="Schemes">Supported Schemas</param>
|
||||
public ResourceContainerAttribute(params String[] Schemes) => this.Schemes = Schemes;
|
||||
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Git Providers marked withi this attribute will be auto-imported at startup!
|
||||
/// </summary>
|
||||
[AttributeUsage(AttributeTargets.Class, AllowMultiple = true, Inherited = false)]
|
||||
public sealed class GitProviderAttribute : EPSMarkerAttribute
|
||||
{
|
||||
/// <summary>
|
||||
/// The Service which this GitProvider is for
|
||||
/// </summary>
|
||||
public String ServiceName { get; }
|
||||
|
||||
/// <inheritdoc cref="GitProviderAttribute"/>
|
||||
/// <param name="ServiceName">The Service which this GitProvider is for</param>
|
||||
public GitProviderAttribute(String ServiceName) => this.ServiceName = ServiceName;
|
||||
}
|
||||
}
|
||||
@ -1,13 +1,8 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace ExtensiblePortfolioSite.SDK.Plugins
|
||||
{
|
||||
|
||||
/// <summary>
|
||||
/// Represents a Plugin Version
|
||||
/// </summary>
|
||||
@ -66,6 +61,11 @@ namespace ExtensiblePortfolioSite.SDK.Plugins
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
/// <inheritdoc cref="IComparable{PluginVersion}.CompareTo(PluginVersion)"/>
|
||||
public int CompareTo(PluginVersion other)
|
||||
{
|
||||
@ -105,4 +105,63 @@ namespace ExtensiblePortfolioSite.SDK.Plugins
|
||||
///
|
||||
public static Boolean operator <(PluginVersion l, PluginVersion r) => l.CompareTo(r) < 0;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Describes the ExtensiblePortfolioSite Plugin
|
||||
/// </summary>
|
||||
[AttributeUsage(AttributeTargets.Assembly, AllowMultiple = false, Inherited = false)]
|
||||
public class EPSPluginAttribute : Attribute
|
||||
{
|
||||
/// <summary>
|
||||
/// Plugin Name
|
||||
/// </summary>
|
||||
public String Name;
|
||||
|
||||
/// <summary>
|
||||
/// Plugin Version
|
||||
/// </summary>
|
||||
public PluginVersion Version;
|
||||
|
||||
/// <summary>
|
||||
/// Weather or not this Plugin Supports Runtime Upgrades
|
||||
/// </summary>
|
||||
public Boolean SupportsRuntimeUpgrades;
|
||||
|
||||
/// <summary>
|
||||
/// Plugin Auther
|
||||
/// </summary>
|
||||
public String? Auther;
|
||||
|
||||
/// <summary>
|
||||
/// Plugin Website
|
||||
/// </summary>
|
||||
public String? Website;
|
||||
|
||||
/// <inheritdoc cref="EPSPluginAttribute"/>
|
||||
/// <param name="Name">Plugin Name</param>
|
||||
/// <param name="Major">Plugin Version, Major Number</param>
|
||||
/// <param name="Minor">Plugin Version, Major Number</param>
|
||||
/// <param name="Build">Plugin Version, Build Number</param>
|
||||
/// <param name="Revision">Plugin Version, Revision Number</param>
|
||||
/// <param name="SupportsRuntimeUpgrades">Supports Runtime Upgrading of the Plugin</param>
|
||||
/// <param name="Auther">Plugin Auther</param>
|
||||
/// <param name="Website">Plugin Website/Repository</param>
|
||||
public EPSPluginAttribute(
|
||||
String Name,
|
||||
Boolean SupportsRuntimeUpgrades,
|
||||
UInt32 Major,
|
||||
UInt32 Minor,
|
||||
UInt32 Build = 0u,
|
||||
UInt32 Revision = 0u,
|
||||
String? Auther = null,
|
||||
String? Website = null
|
||||
)
|
||||
{
|
||||
this.Name = Name;
|
||||
this.Version = new PluginVersion(Major, Minor, Build, Revision);
|
||||
this.SupportsRuntimeUpgrades = SupportsRuntimeUpgrades;
|
||||
this.Auther = Auther;
|
||||
this.Website = Website;
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1,15 +1,17 @@
|
||||
using ExtensiblePortfolioSite.SDK.Git;
|
||||
using ExtensiblePortfolioSite.SDK.Resources;
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Runtime.Loader;
|
||||
using System.Text.Json.Serialization;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace ExtensiblePortfolioSite.SDK.Plugins
|
||||
{
|
||||
internal delegate void PluginUnloadDelegate(Plugin plugin);
|
||||
internal class Plugin : IPluginManagedLibrary
|
||||
{
|
||||
private class PluginLoadContext : AssemblyLoadContext
|
||||
@ -43,8 +45,6 @@ namespace ExtensiblePortfolioSite.SDK.Plugins
|
||||
public Assembly Assembly { get; private set; }
|
||||
public EPSPluginAttribute Info { get; private set; }
|
||||
|
||||
public event PluginUnloadDelegate? OnUnloading;
|
||||
|
||||
public IList<IPluginLibrary> Dependents { get; } = new List<IPluginLibrary>();
|
||||
IReadOnlyList<IPluginLibrary> IPluginLibrary.References => References.AsReadOnly();
|
||||
public List<IPluginLibrary> References = new();
|
||||
@ -60,9 +60,8 @@ namespace ExtensiblePortfolioSite.SDK.Plugins
|
||||
this.Info = this.Assembly.GetCustomAttribute<EPSPluginAttribute>() ?? throw new InvalidOperationException("Target Assembly is not a Plugin Assembly!");
|
||||
}
|
||||
|
||||
private readonly List<JsonConverter> JsonConverters = new();
|
||||
private readonly Dictionary<Type, String[]> ResourceContainers = new();
|
||||
private readonly Dictionary<GitService, IGitProvider> GitProviders = new();
|
||||
|
||||
private readonly List<KeyValuePair<String, IGitProvider>> GitProviders = new();
|
||||
public void Init()
|
||||
{
|
||||
if (Initialized)
|
||||
@ -70,78 +69,39 @@ namespace ExtensiblePortfolioSite.SDK.Plugins
|
||||
|
||||
Initialized = true;
|
||||
foreach (Type t in this.Assembly.ExportedTypes)
|
||||
foreach (Attribute Attr in t.GetCustomAttributes())
|
||||
if (Attr is EPSMarkerAttribute)
|
||||
{
|
||||
try
|
||||
{
|
||||
// Load Git Providers
|
||||
if (t.GetInterface(nameof(IGitProvider)) != null)
|
||||
{
|
||||
if (Attr is GitProviderAttribute GitProviderAttr)
|
||||
GitProviderAttribute? Attr = t.GetCustomAttribute<GitProviderAttribute>();
|
||||
if (Attr != null)
|
||||
{
|
||||
// Load Git Provider
|
||||
if (t.IsAssignableTo(typeof(IGitProvider)) && t.GetConstructor(Type.EmptyTypes) != null)
|
||||
if (Activator.CreateInstance(t, true) is IGitProvider Provider)
|
||||
{
|
||||
GitService Service = GitManager.GetOrCreate(GitProviderAttr.ServiceName);
|
||||
GitProviders.Add(Service, Provider);
|
||||
Service.I_AddProvider(this, Provider);
|
||||
}
|
||||
else
|
||||
PluginManager.LogMessage(PluginErrorSeverity.Error, this, "Failed to Instantiate GitProvider", t, null);
|
||||
else
|
||||
PluginManager.LogMessage(PluginErrorSeverity.Error, this, "Marked GitProvider does not have an empty Constructor or does not expose an IGitProvider interface", t, null);
|
||||
}
|
||||
else if (Attr is JsonConverterAttribute)
|
||||
{
|
||||
// Load Json Converter
|
||||
if (t.IsAssignableTo(typeof(JsonConverter)) && t.GetConstructor(Type.EmptyTypes) != null)
|
||||
if (Activator.CreateInstance(t, true) is JsonConverter Converter)
|
||||
{
|
||||
JsonConverters.Add(Converter);
|
||||
Json.Register(Converter);
|
||||
}
|
||||
else
|
||||
PluginManager.LogMessage(PluginErrorSeverity.Error, this, "Failed to Instantiate JsonConverter", t, null);
|
||||
else
|
||||
PluginManager.LogMessage(PluginErrorSeverity.Error, this, "Marked JsonConverter does not have an empty Constructor or does not derive from JsonConverter", t, null);
|
||||
}
|
||||
else if (Attr is ResourceContainerAttribute ResourceContainerAttr)
|
||||
{
|
||||
// Load Resource Container
|
||||
if (t.IsAssignableTo(typeof(IResource)) && t.GetConstructor(new Type[1] { typeof(Uri) }) != null)
|
||||
{
|
||||
try
|
||||
{
|
||||
ResourceContainers.Add(t, ResourceContainerAttr.Schemes);
|
||||
ResourceManager.Register(t, ResourceContainerAttr.Schemes);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
PluginManager.LogMessage(PluginErrorSeverity.Fatal, this, "Failed to Create ResoruceContainer Factory", t, ex);
|
||||
Console.Error.WriteLine("Runtime Unstable, Exitting!");
|
||||
Environment.Exit(5);
|
||||
}
|
||||
}
|
||||
else
|
||||
PluginManager.LogMessage(PluginErrorSeverity.Error, this, "Marked ResourceContainer does not have a valid Constructor or does not expose an IResource interface", t, null);
|
||||
IGitProvider Provider = (IGitProvider)Activator.CreateInstance(t)!;
|
||||
GitManager.Register(Attr.WebSite, Provider);
|
||||
GitProviders.Add(new(Attr.WebSite, Provider));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
catch
|
||||
{
|
||||
// TODO: Error Logging
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
public void Disposing()
|
||||
{
|
||||
OnUnloading?.Invoke(this);
|
||||
|
||||
foreach (KeyValuePair<GitService, IGitProvider> provider in GitProviders)
|
||||
provider.Key.I_RemoveProvider(this, provider.Value);
|
||||
foreach (KeyValuePair<String, IGitProvider> provider in GitProviders)
|
||||
GitManager.Unregister(provider.Key, provider.Value);
|
||||
this.GitProviders.Clear();
|
||||
|
||||
foreach (KeyValuePair<Type, String[]> resourceContainer in ResourceContainers)
|
||||
ResourceManager.Unregister(resourceContainer.Key);
|
||||
this.ResourceContainers.Clear();
|
||||
|
||||
foreach (JsonConverter Converter in JsonConverters)
|
||||
Json.Unregister(Converter);
|
||||
this.JsonConverters.Clear();
|
||||
|
||||
this.Assembly = null!;
|
||||
this.LoadContext.Unload();
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,27 +0,0 @@
|
||||
using System;
|
||||
|
||||
namespace ExtensiblePortfolioSite.SDK.Plugins
|
||||
{
|
||||
internal enum PluginErrorSeverity
|
||||
{
|
||||
/// <summary>
|
||||
/// Critically Breaking Error, Wholly unable to load Plugin
|
||||
/// </summary>
|
||||
Fatal,
|
||||
|
||||
/// <summary>
|
||||
/// Small Info, non critical
|
||||
/// </summary>
|
||||
Info,
|
||||
/// <summary>
|
||||
/// Moderate Warning
|
||||
/// </summary>
|
||||
Warning,
|
||||
/// <summary>
|
||||
/// any error that will cause a degraded runtime.
|
||||
/// </summary>
|
||||
Error,
|
||||
}
|
||||
|
||||
internal delegate void PluginLogMessageDelegate(PluginErrorSeverity Severity, Plugin Plugin, String Message, Type? Target, Exception? ex);
|
||||
}
|
||||
@ -26,20 +26,12 @@ namespace ExtensiblePortfolioSite.SDK.Plugins
|
||||
NativePath.Append(Path.DirectorySeparatorChar);
|
||||
NativeLibraryPath = String.Intern(NativePath.ToString());
|
||||
|
||||
//b03f5f7f11d50a3a
|
||||
|
||||
List<Byte[]> PublicKeyTokens = new();
|
||||
PublicKeyTokens.Add(typeof(String).Assembly.GetName().GetPublicKeyToken()!); // Framework Libraries
|
||||
PublicKeyTokens.Add(new byte[8] { 0xb0, 0x3f, 0x5f, 0x7f, 0x11, 0xd5, 0x0a, 0x3a }); // System.Runtime
|
||||
PublicKeyTokens.Add(new byte[8] { 0xcc, 0x7b, 0x13, 0xff, 0xcd, 0x2d, 0xdd, 0x51 }); // System.Text.Json
|
||||
PublicKeyTokens.Add(typeof(System.Runtime.GCSettings).Assembly.GetName().GetPublicKeyToken()!);
|
||||
KnownPublicKeyTokens = PublicKeyTokens.ToArray();
|
||||
}
|
||||
|
||||
|
||||
internal static event PluginLogMessageDelegate? OnLogMessage;
|
||||
internal static void LogMessage(PluginErrorSeverity Severity, Plugin Plugin, String Message, Type? Target, Exception? ex) => OnLogMessage?.Invoke(Severity, Plugin, Message, Target, ex);
|
||||
|
||||
|
||||
public static readonly HashSet<String> LibraryPaths = new();
|
||||
public static IReadOnlyList<Plugin> Plugins => _plugins.AsReadOnly();
|
||||
private static readonly List<Plugin> _plugins = new();
|
||||
@ -55,7 +47,9 @@ namespace ExtensiblePortfolioSite.SDK.Plugins
|
||||
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)
|
||||
{
|
||||
@ -378,15 +372,5 @@ namespace ExtensiblePortfolioSite.SDK.Plugins
|
||||
// we want to throw when its trying to load something we don't explicitly allow!
|
||||
throw new FileNotFoundException($"Unable to Load Assembly '{Name}'");
|
||||
}
|
||||
|
||||
internal static Plugin? ResolveTypeToPlugin(Type type)
|
||||
{
|
||||
String fname = type.Assembly.GetName().FullName;
|
||||
lock (_plugins)
|
||||
foreach (Plugin plugin in _plugins)
|
||||
if (plugin.Assembly.GetName().FullName == fname)
|
||||
return plugin;
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1,52 +0,0 @@
|
||||
using ExtensiblePortfolioSite.SDK.Plugins;
|
||||
|
||||
using System;
|
||||
using System.IO;
|
||||
|
||||
namespace ExtensiblePortfolioSite.SDK.Resources
|
||||
{
|
||||
/// <summary>
|
||||
/// A Remote File retrived from a HTTP Server
|
||||
/// </summary>
|
||||
[ResourceContainer("http", "https", "lhttp", "lhttps")]
|
||||
public class HTTPFile : IResource
|
||||
{
|
||||
/// <inheritdoc cref="IResource.PublicallyAccessible"/>
|
||||
public bool PublicallyAccessible { get; }
|
||||
/// <inheritdoc cref="IResource.WebCompatible"/>
|
||||
public bool WebCompatible => true;
|
||||
/// <inheritdoc cref="IResource.URI"/>
|
||||
public Uri URI { get; }
|
||||
|
||||
/// <inheritdoc cref="HTTPFile"/>
|
||||
/// <param name="URI"></param>
|
||||
/// <exception cref="ArgumentException"></exception>
|
||||
public HTTPFile(Uri URI)
|
||||
{
|
||||
switch (URI.Scheme)
|
||||
{
|
||||
// remote "public" HTTP FILE
|
||||
case "http":
|
||||
case "https":
|
||||
this.PublicallyAccessible = true;
|
||||
this.URI = URI;
|
||||
break;
|
||||
|
||||
// local "non-public" HTTP FILE
|
||||
case "lhttp":
|
||||
case "lhttps":
|
||||
this.PublicallyAccessible = false;
|
||||
UriBuilder Builder = new UriBuilder(URI);
|
||||
Builder.Scheme = URI.Scheme == "lhttp" ? "http" : "https";
|
||||
this.URI = Builder.Uri;
|
||||
break;
|
||||
|
||||
default:
|
||||
throw new ArgumentException("Provided URI is not a HTTPS URL", nameof(URI));
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc cref="IResource.OpenStream"/>
|
||||
public Stream OpenStream() => HTTP.GetStream(URI);
|
||||
}
|
||||
}
|
||||
@ -1,31 +0,0 @@
|
||||
using System;
|
||||
using System.IO;
|
||||
|
||||
namespace ExtensiblePortfolioSite.SDK.Resources
|
||||
{
|
||||
/// <summary>
|
||||
/// Represents a Resource
|
||||
/// </summary>
|
||||
public interface IResource
|
||||
{
|
||||
/// <summary>
|
||||
/// Weather or not this Resource is Publically Accessible via its URI
|
||||
/// </summary>
|
||||
public Boolean PublicallyAccessible { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Weather or not this Resource is directly usable by the Browser
|
||||
/// </summary>
|
||||
public Boolean WebCompatible { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Resource URI
|
||||
/// </summary>
|
||||
public Uri URI { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Opens a Stream to the specified resource
|
||||
/// </summary>
|
||||
public Stream OpenStream();
|
||||
}
|
||||
}
|
||||
@ -1,43 +0,0 @@
|
||||
using ExtensiblePortfolioSite.SDK.Plugins;
|
||||
|
||||
using System;
|
||||
using System.IO;
|
||||
|
||||
namespace ExtensiblePortfolioSite.SDK.Resources
|
||||
{
|
||||
/// <summary>
|
||||
/// Local File Resource
|
||||
/// </summary>
|
||||
[ResourceContainer("file")]
|
||||
public sealed class LocalFile : IResource
|
||||
{
|
||||
/// <inheritdoc cref="IResource.PublicallyAccessible"/>
|
||||
public bool PublicallyAccessible => false;
|
||||
/// <inheritdoc cref="IResource.WebCompatible"/>
|
||||
public bool WebCompatible => false;
|
||||
/// <inheritdoc cref="IResource.URI"/>
|
||||
public Uri URI { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Local On System FilePath
|
||||
/// </summary>
|
||||
public string LocalFilePath { get; }
|
||||
|
||||
/// <inheritdoc cref="LocalFile"/>
|
||||
/// <param name="FilePath"></param>
|
||||
public LocalFile(String FilePath) : this(new Uri($"file://{FilePath}")) { }
|
||||
|
||||
/// <inheritdoc cref="LocalFile"/>
|
||||
/// <param name="FileUri"></param>
|
||||
public LocalFile(Uri FileUri)
|
||||
{
|
||||
this.LocalFilePath = FileUri.AbsolutePath;
|
||||
this.URI = FileUri;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/// <inheritdoc cref="IResource.OpenStream"/>
|
||||
public Stream OpenStream() => File.OpenRead(this.LocalFilePath);
|
||||
}
|
||||
}
|
||||
@ -1,191 +0,0 @@
|
||||
using ExtensiblePortfolioSite.SDK.Plugins;
|
||||
using ExtensiblePortfolioSite.SDK.Resources.VFS;
|
||||
|
||||
using Microsoft.Extensions.FileProviders;
|
||||
using Microsoft.Extensions.Primitives;
|
||||
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using System.Reflection.Emit;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace ExtensiblePortfolioSite.SDK.Resources
|
||||
{
|
||||
/// <summary>
|
||||
/// Generic Resource Manager
|
||||
/// </summary>
|
||||
public static class ResourceManager
|
||||
{
|
||||
static ResourceManager()
|
||||
{
|
||||
Register(typeof(HTTPFile), typeof(HTTPFile).GetCustomAttribute<ResourceContainerAttribute>()!.Schemes);
|
||||
Register(typeof(LocalFile), typeof(LocalFile).GetCustomAttribute<ResourceContainerAttribute>()!.Schemes);
|
||||
}
|
||||
|
||||
private delegate IResource ResourceContainerFactory(Uri URI);
|
||||
private class ResourceContainer
|
||||
{
|
||||
public ResourceContainer(ResourceContainerFactory Factory, Type ResourceContainerType)
|
||||
{
|
||||
this.Schemes = new();
|
||||
this.Factory = Factory;
|
||||
this.ResourceContainerType = ResourceContainerType;
|
||||
}
|
||||
|
||||
public readonly List<String> Schemes;
|
||||
public readonly ResourceContainerFactory Factory;
|
||||
public readonly Type ResourceContainerType;
|
||||
}
|
||||
private static readonly SortedDictionary<String, List<ResourceContainer>> SchemeLookup = new();
|
||||
private static readonly Dictionary<Type, ResourceContainer> ResourceLookup = new();
|
||||
|
||||
/// <summary>
|
||||
/// Resolves a URI to its Resource Handler
|
||||
/// </summary>
|
||||
/// <param name="URI">Resource URI</param>
|
||||
/// <returns></returns>
|
||||
/// <exception cref="Exception"></exception>
|
||||
public static IResource ResolveResource(Uri URI)
|
||||
{
|
||||
List<ResourceContainer>? Containers;
|
||||
Boolean B;
|
||||
|
||||
lock (SchemeLookup)
|
||||
B = SchemeLookup.TryGetValue(URI.Scheme, out Containers);
|
||||
|
||||
if (B)
|
||||
{
|
||||
lock (Containers!)
|
||||
foreach (ResourceContainer Container in Containers)
|
||||
try { return Container.Factory(URI); } catch { }
|
||||
|
||||
throw new Exception($"Failed to Build Resouce Container! for Scheme {URI.Scheme}");
|
||||
}
|
||||
|
||||
throw new Exception($"Unknown Scheme! {URI.Scheme}");
|
||||
}
|
||||
|
||||
internal static void Register(Type T, params String[] Scheme)
|
||||
{
|
||||
lock (ResourceLookup)
|
||||
{
|
||||
if (ResourceLookup.TryGetValue(T, out ResourceContainer? Container))
|
||||
return;
|
||||
|
||||
ConstructorInfo? Ctor = T.GetConstructor(new Type[1] { typeof(Uri) });
|
||||
if (Ctor != null && T.GetInterface(nameof(IResource)) != null)
|
||||
{
|
||||
DynamicMethod Factory = new("ResourceManager_Factory_dynamic", MethodAttributes.Public | MethodAttributes.Static, CallingConventions.Standard, typeof(IResource), new Type[1] { typeof(Uri) }, T, true);
|
||||
|
||||
ILGenerator IL = Factory.GetILGenerator(7);
|
||||
IL.Emit(OpCodes.Ldarg_0);
|
||||
IL.Emit(OpCodes.Newobj, Ctor);
|
||||
IL.Emit(OpCodes.Ret);
|
||||
|
||||
Container = new(Factory.CreateDelegate<ResourceContainerFactory>(), T);
|
||||
|
||||
ResourceLookup.Add(T, Container);
|
||||
|
||||
lock (SchemeLookup)
|
||||
foreach (String s in Scheme)
|
||||
{
|
||||
Container.Schemes.Add(s);
|
||||
|
||||
if (SchemeLookup.TryGetValue(s, out List<ResourceContainer>? Containers))
|
||||
lock (Containers)
|
||||
Containers.Add(Container);
|
||||
else
|
||||
SchemeLookup.Add(s, new List<ResourceContainer>() { Container });
|
||||
}
|
||||
}
|
||||
else
|
||||
throw new ArgumentException("Provided Type does not have the required constructor/interface", nameof(T));
|
||||
}
|
||||
}
|
||||
internal static void Unregister(Type T)
|
||||
{
|
||||
lock (ResourceLookup)
|
||||
{
|
||||
if (ResourceLookup.TryGetValue(T, out ResourceContainer? Container))
|
||||
{
|
||||
lock (SchemeLookup)
|
||||
foreach (String s in Container.Schemes)
|
||||
if (SchemeLookup.TryGetValue(s, out List<ResourceContainer>? Containers))
|
||||
lock (Containers)
|
||||
{
|
||||
Containers.Remove(Container);
|
||||
if (Containers.Count == 0)
|
||||
SchemeLookup.Remove(s);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static readonly RootVFSFolder RootVFS = new();
|
||||
|
||||
/// <summary>
|
||||
/// Gets or Creates a Virtual Folder
|
||||
/// </summary>
|
||||
/// <param name="FolderPath">Folder Path</param>
|
||||
public static IVirtualFolder GetOrCreateVirtualFolder(String FolderPath)
|
||||
{
|
||||
IVirtualFolder Folder = RootVFS;
|
||||
foreach (String Seg in FolderPath.Split(Path.DirectorySeparatorChar, StringSplitOptions.RemoveEmptyEntries))
|
||||
Folder = Folder.GetFolder(Seg) ?? Folder.CreateFolder(Seg);
|
||||
return Folder;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a Virtual File
|
||||
/// </summary>
|
||||
/// <param name="FilePath">File Path</param>
|
||||
/// <param name="File">File Content</param>
|
||||
public static IVirtualFile CreateVirtualFile(String FilePath, IResource File) =>
|
||||
GetOrCreateVirtualFolder(Path.GetDirectoryName(FilePath)!).CreateFile(Path.GetFileName(FilePath)!, File);
|
||||
|
||||
/// <summary>
|
||||
/// Gets a Virtual Folder
|
||||
/// </summary>
|
||||
/// <param name="FolderPath">Folder Path</param>
|
||||
/// <returns>Virtual Folder or Null</returns>
|
||||
public static IVirtualFolder? GetVirtualFolder(String FolderPath)
|
||||
{
|
||||
IVirtualFolder Folder = RootVFS;
|
||||
foreach (String Seg in FolderPath.Split(Path.DirectorySeparatorChar, StringSplitOptions.RemoveEmptyEntries))
|
||||
{
|
||||
IVirtualFolder? entry = Folder.GetFolder(Seg);
|
||||
if (entry == null) return null;
|
||||
Folder = entry;
|
||||
}
|
||||
return Folder;
|
||||
}
|
||||
/// <summary>
|
||||
/// Gets a Virtual File
|
||||
/// </summary>
|
||||
/// <param name="FilePath">File Path</param>
|
||||
/// <returns>Virtual File or Null</returns>
|
||||
public static IVirtualFile? GetVirtualFile(String FilePath) => GetVirtualFolder(Path.GetDirectoryName(FilePath) ?? "/")?.GetFile(Path.GetFileName(FilePath));
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Gets a Virtual Entry
|
||||
/// </summary>
|
||||
/// <param name="EntryPath">Entry Path</param>
|
||||
/// <returns>Virtual Entry or Null</returns>
|
||||
public static IVirtualEntry? GetVirtualEntry(String EntryPath)
|
||||
{
|
||||
String? Filename = Path.GetFileName(EntryPath);
|
||||
|
||||
if (Filename == null)
|
||||
return GetVirtualFolder(EntryPath);
|
||||
else
|
||||
return GetVirtualFolder(Path.GetDirectoryName(EntryPath) ?? "/")?.GetFile(Filename);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1,152 +0,0 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.IO.Enumeration;
|
||||
|
||||
namespace ExtensiblePortfolioSite.SDK.Resources.VFS
|
||||
{
|
||||
/// <summary>
|
||||
/// Represents a VFS Entry Type/Kind
|
||||
/// </summary>
|
||||
public enum VirtualEntryKind
|
||||
{
|
||||
/// <summary>
|
||||
/// <see cref="IVirtualFile"/>
|
||||
/// </summary>
|
||||
File,
|
||||
/// <summary>
|
||||
/// <see cref="IVirtualFolder"/>
|
||||
/// </summary>
|
||||
Folder
|
||||
}
|
||||
/// <summary>
|
||||
/// Represents a VFS Entry
|
||||
/// </summary>
|
||||
public interface IVirtualEntry
|
||||
{
|
||||
/// <summary>
|
||||
/// VFS Entry Kind
|
||||
/// </summary>
|
||||
public VirtualEntryKind Kind { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Entry Name
|
||||
/// </summary>
|
||||
public String Name { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Delete the VFS Entry
|
||||
/// </summary>
|
||||
public void Delete();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Represents a VFS File
|
||||
/// </summary>
|
||||
public interface IVirtualFile : IVirtualEntry
|
||||
{
|
||||
VirtualEntryKind IVirtualEntry.Kind => VirtualEntryKind.File;
|
||||
|
||||
String IVirtualEntry.Name => Filename;
|
||||
|
||||
/// <summary>
|
||||
/// Folder Containing This File
|
||||
/// </summary>
|
||||
public IVirtualFolder Folder { get; }
|
||||
|
||||
/// <summary>
|
||||
/// File Name
|
||||
/// </summary>
|
||||
public String Filename { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Open the File Stream
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public Stream OpenStream();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Represents a VFS Folder
|
||||
/// </summary>
|
||||
public interface IVirtualFolder : IVirtualEntry
|
||||
{
|
||||
VirtualEntryKind IVirtualEntry.Kind => VirtualEntryKind.Folder;
|
||||
String IVirtualEntry.Name => Foldername;
|
||||
|
||||
/// <summary>
|
||||
/// The Parent Folder
|
||||
/// </summary>
|
||||
IVirtualFolder? Parent { get; }
|
||||
|
||||
/// <summary>
|
||||
/// File Name
|
||||
/// </summary>
|
||||
public String Foldername { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Create a File with the Given <paramref name="Filename"/>
|
||||
/// </summary>
|
||||
/// <param name="Filename">File Name</param>
|
||||
/// <param name="File">Underlying Resource File</param>
|
||||
/// <returns>new VFS File Entry</returns>
|
||||
IVirtualFile CreateFile(String Filename, IResource File);
|
||||
|
||||
/// <summary>
|
||||
/// Create a SubFolder with the Given <paramref name="Foldername"/>
|
||||
/// </summary>
|
||||
/// <param name="Foldername">Folder Name</param>
|
||||
/// <returns>new VFS Folder Entry</returns>
|
||||
IVirtualFolder CreateFolder(String Foldername);
|
||||
|
||||
/// <summary>
|
||||
/// Attempts to get the specified File
|
||||
/// </summary>
|
||||
/// <param name="Filename">File Name</param>
|
||||
/// <returns>Virtual File or Null</returns>
|
||||
/// <exception cref="InvalidOperationException"/>
|
||||
IVirtualFile? GetFile(String Filename)
|
||||
{
|
||||
IVirtualEntry? entry = GetEntry(Filename);
|
||||
return entry is IVirtualFile file
|
||||
? file
|
||||
: entry == null
|
||||
? null
|
||||
: throw new InvalidOperationException($"{Filename} is not a file!");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Attempts to get the specified Folder
|
||||
/// </summary>
|
||||
/// <param name="Foldername">Folder Name</param>
|
||||
/// <returns>Virtual Folder or Null</returns>
|
||||
/// <exception cref="InvalidOperationException"/>
|
||||
IVirtualFolder? GetFolder(String Foldername)
|
||||
{
|
||||
IVirtualEntry? entry = GetEntry(Foldername);
|
||||
return entry is IVirtualFolder folder
|
||||
? folder
|
||||
: entry == null
|
||||
? null
|
||||
: throw new InvalidOperationException($"{Foldername} is not a folder!");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get a Specified Entry by Name
|
||||
/// </summary>
|
||||
/// <param name="Entry">Entry Name</param>
|
||||
/// <returns>Entry or Null</returns>
|
||||
IVirtualEntry? GetEntry(String Entry);
|
||||
|
||||
/// <summary>
|
||||
/// Delete the Specified Entry
|
||||
/// </summary>
|
||||
/// <param name="Entry">Entry name</param>
|
||||
void Delete(String Entry);
|
||||
|
||||
/// <summary>
|
||||
/// Enumerate all Entries within this Folder
|
||||
/// </summary>
|
||||
IEnumerable<IVirtualEntry> EnumerateEntries();
|
||||
}
|
||||
}
|
||||
@ -1,49 +0,0 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.Immutable;
|
||||
|
||||
namespace ExtensiblePortfolioSite.SDK.Resources.VFS
|
||||
{
|
||||
internal class RootVFSFolder : IVirtualFolder
|
||||
{
|
||||
public IVirtualFolder? Parent => null;
|
||||
public string Foldername => String.Empty;
|
||||
|
||||
private readonly SortedDictionary<String, IVirtualEntry> Entries = new();
|
||||
|
||||
public IVirtualFile CreateFile(string Filename, IResource File)
|
||||
{
|
||||
IVirtualFile entry = new VirtualFile(this, Filename, File);
|
||||
lock (Entries)
|
||||
Entries.Add(Filename, entry);
|
||||
return entry;
|
||||
}
|
||||
|
||||
public IVirtualFolder CreateFolder(string Foldername)
|
||||
{
|
||||
IVirtualFolder entry = new VirtualFolder(this, Foldername);
|
||||
lock (Entries)
|
||||
Entries.Add(Foldername, entry);
|
||||
return entry;
|
||||
}
|
||||
public void Delete(string Entry)
|
||||
{
|
||||
lock (Entries)
|
||||
Entries.Remove(Entry);
|
||||
}
|
||||
|
||||
public void Delete() => throw new NotSupportedException("you can't remove the root entry");
|
||||
|
||||
public IEnumerable<IVirtualEntry> EnumerateEntries()
|
||||
{
|
||||
foreach (IVirtualEntry Entry in Entries.Values.ToImmutableArray())
|
||||
yield return Entry;
|
||||
}
|
||||
|
||||
public IVirtualEntry? GetEntry(string Entry)
|
||||
{
|
||||
Entries.TryGetValue(Entry, out IVirtualEntry? entry);
|
||||
return entry;
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1,23 +0,0 @@
|
||||
using System;
|
||||
using System.IO;
|
||||
|
||||
namespace ExtensiblePortfolioSite.SDK.Resources.VFS
|
||||
{
|
||||
internal class VirtualFile : IVirtualFile
|
||||
{
|
||||
public IVirtualFolder Folder { get; }
|
||||
public string Filename { get; }
|
||||
|
||||
public IResource ResourceContainer { get; }
|
||||
|
||||
internal VirtualFile(IVirtualFolder Folder, string Filename, IResource ResourceContainer)
|
||||
{
|
||||
this.Folder = Folder;
|
||||
this.Filename = Filename;
|
||||
this.ResourceContainer = ResourceContainer;
|
||||
}
|
||||
|
||||
public void Delete() => Folder.Delete(Filename);
|
||||
public Stream OpenStream() => ResourceContainer.OpenStream();
|
||||
}
|
||||
}
|
||||
@ -1,54 +0,0 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.Immutable;
|
||||
|
||||
namespace ExtensiblePortfolioSite.SDK.Resources.VFS
|
||||
{
|
||||
internal class VirtualFolder : IVirtualFolder
|
||||
{
|
||||
public IVirtualFolder Parent { get; }
|
||||
public string Foldername { get; }
|
||||
|
||||
internal VirtualFolder(IVirtualFolder Parent, String Foldername)
|
||||
{
|
||||
this.Foldername = Foldername;
|
||||
this.Parent = Parent;
|
||||
}
|
||||
|
||||
private readonly SortedDictionary<String, IVirtualEntry> Entries = new();
|
||||
|
||||
public IVirtualFile CreateFile(string Filename, IResource File)
|
||||
{
|
||||
IVirtualFile entry = new VirtualFile(this, Filename, File);
|
||||
lock (Entries)
|
||||
Entries.Add(Filename, entry);
|
||||
return entry;
|
||||
}
|
||||
|
||||
public IVirtualFolder CreateFolder(string Foldername)
|
||||
{
|
||||
IVirtualFolder entry = new VirtualFolder(this, Foldername);
|
||||
lock (Entries)
|
||||
Entries.Add(Foldername, entry);
|
||||
return entry;
|
||||
}
|
||||
|
||||
public void Delete(string Entry)
|
||||
{
|
||||
lock(Entries)
|
||||
Entries.Remove(Entry);
|
||||
}
|
||||
public void Delete() => this.Parent.Delete(this.Foldername);
|
||||
|
||||
public IEnumerable<IVirtualEntry> EnumerateEntries()
|
||||
{
|
||||
foreach (IVirtualEntry Entry in Entries.Values.ToImmutableArray())
|
||||
yield return Entry;
|
||||
}
|
||||
public IVirtualEntry? GetEntry(string Entry)
|
||||
{
|
||||
Entries.TryGetValue(Entry, out IVirtualEntry? entry);
|
||||
return entry;
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1,180 +1,9 @@
|
||||
using ExtensiblePortfolioSite.SDK;
|
||||
using ExtensiblePortfolioSite.SDK.Caching;
|
||||
using ExtensiblePortfolioSite.SDK.Git;
|
||||
|
||||
using System.Text.Json.Serialization;
|
||||
using System.Text.Json;
|
||||
|
||||
namespace ExtensiblePortfolioSite
|
||||
{
|
||||
public static class Config
|
||||
{
|
||||
[Serializable]
|
||||
public class ConfigObject : IJsonOnDeserialized
|
||||
{
|
||||
[Serializable]
|
||||
public class Profile : IJsonOnDeserialized
|
||||
{
|
||||
[Serializable]
|
||||
public class LanguageObject : IJsonOnDeserialized
|
||||
{
|
||||
[JsonPropertyName("Name")]
|
||||
[JsonIgnore(Condition = JsonIgnoreCondition.Never)]
|
||||
[JsonInclude]
|
||||
public String Name = null!;
|
||||
[JsonPropertyName("Icon")]
|
||||
[JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
|
||||
public Uri? Icon = null;
|
||||
|
||||
public void OnDeserialized()
|
||||
{
|
||||
if (Name == null) throw new Exception("Language Name can't be null!");
|
||||
}
|
||||
}
|
||||
|
||||
[Serializable]
|
||||
public class LinkObject : IJsonOnDeserialized
|
||||
{
|
||||
[JsonPropertyName("Name")]
|
||||
[JsonIgnore(Condition = JsonIgnoreCondition.Never)]
|
||||
public string Name { get; set; } = null!;
|
||||
|
||||
[JsonPropertyName("Link")]
|
||||
[JsonIgnore(Condition = JsonIgnoreCondition.Never)]
|
||||
public Uri Link { get; set; } = null!;
|
||||
|
||||
[JsonPropertyName("Icon")]
|
||||
[JsonIgnore(Condition = JsonIgnoreCondition.Never)]
|
||||
public Uri? Icon { get; set; } = null!;
|
||||
|
||||
public void OnDeserialized()
|
||||
{
|
||||
if (Name == null) throw new Exception("Link Name can't be null!");
|
||||
if (Link == null) throw new Exception("Link Link can't be null!");
|
||||
}
|
||||
}
|
||||
|
||||
[JsonPropertyName("Name")]
|
||||
[JsonIgnore(Condition = JsonIgnoreCondition.Never)]
|
||||
public string Name { get; set; } = null!;
|
||||
|
||||
[JsonPropertyName("Phone")]
|
||||
[JsonIgnore(Condition = JsonIgnoreCondition.Never)]
|
||||
public string? Phone { get; set; } = null!;
|
||||
|
||||
[JsonPropertyName("Email")]
|
||||
[JsonIgnore(Condition = JsonIgnoreCondition.Never)]
|
||||
public string? Email { get; set; } = null!;
|
||||
|
||||
[JsonPropertyName("Description")]
|
||||
[JsonIgnore(Condition = JsonIgnoreCondition.Never)]
|
||||
public string[] Description { get; set; } = Array.Empty<String>();
|
||||
|
||||
[JsonPropertyName("Biography")]
|
||||
[JsonIgnore(Condition = JsonIgnoreCondition.Never)]
|
||||
public string[] Biography { get; set; } = Array.Empty<String>();
|
||||
|
||||
[JsonPropertyName("Languages")]
|
||||
[JsonIgnore(Condition = JsonIgnoreCondition.Never)]
|
||||
public LanguageObject[] Languages { get; set; } = Array.Empty<LanguageObject>();
|
||||
|
||||
[JsonPropertyName("Links")]
|
||||
[JsonIgnore(Condition = JsonIgnoreCondition.Never)]
|
||||
public LinkObject[] Links { get; set; } = Array.Empty<LinkObject>();
|
||||
|
||||
public void OnDeserialized()
|
||||
{
|
||||
if (Name == null) throw new Exception("Profile Name can't be null");
|
||||
|
||||
if (Description == null) throw new Exception("Profile Description missing!");
|
||||
if (Languages == null) throw new Exception("Profile Languages missing!");
|
||||
if (Links == null) throw new Exception("Profile Links missing!");
|
||||
}
|
||||
}
|
||||
|
||||
[Serializable]
|
||||
public class GitRepoObject : IJsonOnDeserialized
|
||||
{
|
||||
[JsonPropertyName("ServiceProvider")]
|
||||
[JsonIgnore(Condition = JsonIgnoreCondition.Never)]
|
||||
public String ServiceProvider { get; set; } = null!;
|
||||
|
||||
[JsonPropertyName("Repository")]
|
||||
[JsonIgnore(Condition = JsonIgnoreCondition.Never)]
|
||||
public Uri Repository { get; set; } = null!;
|
||||
|
||||
[JsonPropertyName("Description")]
|
||||
[JsonIgnore(Condition = JsonIgnoreCondition.Never)]
|
||||
public String[] Description { get; set; } = Array.Empty<String>();
|
||||
|
||||
public void OnDeserialized()
|
||||
{
|
||||
if (ServiceProvider == null) throw new Exception("GitRepo ServiceProvider missing!");
|
||||
if (Repository == null) throw new Exception("GitRepo Repository missing!");
|
||||
if (Description == null) throw new Exception("GitRepo Description missing!");
|
||||
}
|
||||
|
||||
public IRepository Resolve() => GitManager.GetService(this.ServiceProvider).GetRepository(this.Repository);
|
||||
}
|
||||
|
||||
[Serializable]
|
||||
public class Logging : IJsonOnDeserialized
|
||||
{
|
||||
[JsonPropertyName("ConsoleLogging")]
|
||||
[JsonIgnore(Condition = JsonIgnoreCondition.Never)]
|
||||
public Boolean ConsoleLogging { get; set; } = true;
|
||||
|
||||
[JsonPropertyName("FileLogging")]
|
||||
[JsonIgnore(Condition = JsonIgnoreCondition.Never)]
|
||||
public Boolean FileLogging { get; set; } = true;
|
||||
|
||||
[JsonPropertyName("FileLogPath")]
|
||||
[JsonIgnore(Condition = JsonIgnoreCondition.Never)]
|
||||
public String? FileLogPath { get; set; } = null!;
|
||||
|
||||
public void OnDeserialized()
|
||||
{
|
||||
if (FileLogging && FileLogPath == null)
|
||||
throw new Exception("FileLogPath null with FileLogging Enabled!");
|
||||
}
|
||||
}
|
||||
|
||||
[Serializable]
|
||||
public class Cache
|
||||
{
|
||||
[JsonPropertyName("GitRepo")]
|
||||
[JsonIgnore(Condition = JsonIgnoreCondition.Never)]
|
||||
public Int32 GitRepo = 120;
|
||||
|
||||
[JsonPropertyName("GitUser")]
|
||||
[JsonIgnore(Condition = JsonIgnoreCondition.Never)]
|
||||
public Int32 GitUser = 1440;
|
||||
}
|
||||
|
||||
[JsonPropertyName("Profile")]
|
||||
[JsonIgnore(Condition = JsonIgnoreCondition.Never)]
|
||||
public Profile ProfileSection { get; set; } = null!;
|
||||
|
||||
[JsonPropertyName("GitRepos")]
|
||||
[JsonIgnore(Condition = JsonIgnoreCondition.Never)]
|
||||
public GitRepoObject[] GitRepos { get; set; } = Array.Empty<GitRepoObject>();
|
||||
|
||||
[JsonPropertyName("Logging")]
|
||||
[JsonIgnore(Condition = JsonIgnoreCondition.Never)]
|
||||
public Logging LoggingSection { get; set; } = null!;
|
||||
|
||||
[JsonPropertyName("Cache")]
|
||||
[JsonIgnore(Condition = JsonIgnoreCondition.Never)]
|
||||
public Cache CacheSection { get; set; } = null!;
|
||||
|
||||
public void OnDeserialized()
|
||||
{
|
||||
if (ProfileSection == null) throw new Exception("Config Missing ProfileSection!");
|
||||
if (GitRepos == null) throw new Exception("Config Missing GitRepos!");
|
||||
if (LoggingSection == null) throw new Exception("Config Missing LoggingSection!");
|
||||
if (CacheSection == null) throw new Exception("Config Missing CacheSection!");
|
||||
}
|
||||
}
|
||||
|
||||
private static String ConfigFile = "eps_host_config.json";
|
||||
|
||||
public static void SetConfigFile(String File)
|
||||
@ -182,26 +11,70 @@ namespace ExtensiblePortfolioSite
|
||||
ConfigFile = File;
|
||||
}
|
||||
|
||||
private static ConfigObject? Conf;
|
||||
public static ConfigObject GetConfig()
|
||||
|
||||
public static String Name { get; private set; } = "invalid";
|
||||
public static String? Phone { get; private set; } = null;
|
||||
public static String? Email { get; private set; } = null;
|
||||
public static String[]? Description { get; private set; } = null;
|
||||
public static Dictionary<string, string> Languages = new Dictionary<string, string>()
|
||||
{
|
||||
if (Conf == null)
|
||||
ReloadConfig();
|
||||
return Conf!;
|
||||
}
|
||||
{ "C#", "https://seeklogo.com/images/C/c-sharp-c-logo-02F17714BA-seeklogo.com.png" },
|
||||
{ "Kotlin", "https://upload.wikimedia.org/wikipedia/commons/7/74/Kotlin_Icon.png" },
|
||||
{ "Lua", "https://upload.wikimedia.org/wikipedia/commons/c/cf/Lua-Logo.svg" },
|
||||
{ "C/C++", "https://upload.wikimedia.org/wikipedia/commons/1/19/C_Logo.png" },
|
||||
};
|
||||
|
||||
public static void ReloadConfig()
|
||||
public static void LoadConfig()
|
||||
{
|
||||
Conf = Json.Deserialize<ConfigObject>(File.ReadAllText(ConfigFile));
|
||||
if (Conf == null)
|
||||
throw new Exception("Failed to Load Config!");
|
||||
|
||||
// Load Config
|
||||
Cache.GitUserLifetime = TimeSpan.FromMinutes(Conf.CacheSection.GitUser);
|
||||
Cache.GitRepositoryLifetime = TimeSpan.FromMinutes(Conf.CacheSection.GitRepo);
|
||||
|
||||
// Invalidate Caches
|
||||
InvalidableCacheTagHelper.TriggerInvalidateEvent("config_update");
|
||||
JsonDocument Doc = JsonDocument.Parse(File.ReadAllText(ConfigFile), new JsonDocumentOptions
|
||||
{
|
||||
AllowTrailingCommas = true,
|
||||
CommentHandling = JsonCommentHandling.Skip,
|
||||
MaxDepth = 16,
|
||||
});
|
||||
foreach (JsonProperty property in Doc.RootElement.EnumerateObject())
|
||||
{
|
||||
switch (property.Value.ValueKind)
|
||||
{
|
||||
case JsonValueKind.String:
|
||||
String value = property.Value.GetString()!;
|
||||
switch (property.Name)
|
||||
{
|
||||
case "Name":
|
||||
Config.Name = value;
|
||||
break;
|
||||
case "Phone":
|
||||
Config.Phone = value;
|
||||
break;
|
||||
case "Email":
|
||||
Config.Email = value;
|
||||
break;
|
||||
}
|
||||
break;
|
||||
case JsonValueKind.Array:
|
||||
switch (property.Name)
|
||||
{
|
||||
case "Description":
|
||||
Config.Description = new string[property.Value.GetArrayLength()];
|
||||
var enumerator = property.Value.EnumerateArray();
|
||||
for (int i = 0; i < property.Value.GetArrayLength(); i++)
|
||||
{
|
||||
if (!enumerator.MoveNext())
|
||||
break;
|
||||
if (enumerator.Current.ValueKind == JsonValueKind.String)
|
||||
Config.Description[i] = enumerator.Current.GetString()!;
|
||||
}
|
||||
break;
|
||||
}
|
||||
break;
|
||||
case JsonValueKind.Object:
|
||||
break;
|
||||
case JsonValueKind.Number:
|
||||
break;
|
||||
case JsonValueKind.True: //Why, why isn't it called boolean
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -6,19 +6,17 @@ EXPOSE 80
|
||||
EXPOSE 443
|
||||
|
||||
FROM mcr.microsoft.com/dotnet/sdk:6.0 AS build
|
||||
|
||||
COPY . /src
|
||||
RUN dotnet restore "/src/EPS.SDK/EPS.SDK.csproj"
|
||||
RUN dotnet restore "/src/ExtensiblePortfolioSite/ExtensiblePortfolioSite.csproj"
|
||||
|
||||
RUN dotnet build "/src/EPS.SDK/EPS.SDK.csproj" -c Release -o /app/build
|
||||
RUN dotnet build "/src/ExtensiblePortfolioSite/ExtensiblePortfolioSite.csproj" -c Release -o /app/build
|
||||
WORKDIR /src
|
||||
COPY ["ExtensiblePortfolioSite/ExtensiblePortfolioSite.csproj", "ExtensiblePortfolioSite/"]
|
||||
RUN dotnet restore "ExtensiblePortfolioSite/ExtensiblePortfolioSite.csproj"
|
||||
COPY . .
|
||||
WORKDIR "/src/ExtensiblePortfolioSite"
|
||||
RUN dotnet build "ExtensiblePortfolioSite.csproj" -c Release -o /app/build
|
||||
|
||||
FROM build AS publish
|
||||
RUN dotnet publish "/src/ExtensiblePortfolioSite/ExtensiblePortfolioSite.csproj" -c Release -o /app/publish /p:UseAppHost=false
|
||||
RUN cp -rf "/src/ExtensiblePortfolioSite/Plugins/" "/app/publish/Plugins/"
|
||||
|
||||
RUN dotnet publish "ExtensiblePortfolioSite.csproj" -c Release -o /app/publish /p:UseAppHost=false
|
||||
|
||||
FROM base AS final
|
||||
WORKDIR /app
|
||||
COPY --from=publish /app/publish .
|
||||
ENTRYPOINT ["dotnet", "/app/ExtensiblePortfolioSite.dll"]
|
||||
ENTRYPOINT ["dotnet", "ExtensiblePortfolioSite.dll"]
|
||||
@ -1,4 +1,4 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk.Web">
|
||||
<Project Sdk="Microsoft.NET.Sdk.Web">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net6.0</TargetFramework>
|
||||
@ -6,23 +6,24 @@
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<UserSecretsId>d2893527-e397-462e-b226-0b13b31981ef</UserSecretsId>
|
||||
<DockerDefaultTargetOS>Linux</DockerDefaultTargetOS>
|
||||
<StartupObject>ExtensiblePortfolioSite.Program</StartupObject>
|
||||
<PreserveCompilationContext>true</PreserveCompilationContext>
|
||||
<RazorCompileOnBuild>true</RazorCompileOnBuild>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.AspNetCore.Mvc.Razor.RuntimeCompilation" Version="6.0.8" />
|
||||
<PackageReference Include="Microsoft.VisualStudio.Azure.Containers.Tools.Targets" Version="1.17.0" />
|
||||
<Content Remove="Pages\Projects.cshtml" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\EPS.SDK\EPS.SDK.csproj" />
|
||||
<PackageReference Include="Microsoft.VisualStudio.Azure.Containers.Tools.Targets" Version="1.17.0" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<None Include="logs\" />
|
||||
<Content Include="Plugins\" />
|
||||
<ProjectReference Include="..\EPS.SDK\EPS.SDK.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
|
||||
<ProjectExtensions><VisualStudio><UserProperties /></VisualStudio></ProjectExtensions>
|
||||
|
||||
<ItemGroup>
|
||||
<None Include="Pages\Projects\Index.cshtml" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
|
||||
@ -1,65 +0,0 @@
|
||||
using Microsoft.AspNetCore.Mvc.ViewFeatures;
|
||||
using Microsoft.AspNetCore.Razor.TagHelpers;
|
||||
|
||||
using System.Text;
|
||||
|
||||
namespace ExtensiblePortfolioSite
|
||||
{
|
||||
[HtmlTargetElement("cache")]
|
||||
public class InvalidableCacheTagHelper : TagHelper
|
||||
{
|
||||
[HtmlAttributeName("expires-on")]
|
||||
public DateTime ExpiresOn { get; set; }
|
||||
|
||||
[HtmlAttributeName("name")]
|
||||
public String? Name { get; set; } = null;
|
||||
|
||||
[HtmlAttributeName("invalidate-events")]
|
||||
public String? Event { get; set; } = null;
|
||||
|
||||
private static readonly SortedDictionary<String, HashSet<String>> InvalidateTriggers = new();
|
||||
private static readonly SortedDictionary<String, String> CachedContent = new();
|
||||
|
||||
public static void TriggerInvalidateEvent(String EventName)
|
||||
{
|
||||
lock (InvalidateTriggers)
|
||||
if (InvalidateTriggers.TryGetValue(EventName, out HashSet<String>? Caches))
|
||||
foreach (String Cache in Caches)
|
||||
InvalidateCachedTag(Cache);
|
||||
}
|
||||
|
||||
public static void InvalidateCachedTag(String NameOrUniqueId) => CachedContent.Remove(NameOrUniqueId);
|
||||
|
||||
public override async Task ProcessAsync(TagHelperContext context, TagHelperOutput output)
|
||||
{
|
||||
String Key = Name ?? context.UniqueId;
|
||||
|
||||
output.TagName = "div";
|
||||
output.TagMode = TagMode.StartTagAndEndTag;
|
||||
|
||||
if (DateTime.Now < ExpiresOn && CachedContent.TryGetValue(Key, out String? Content))
|
||||
output.Content.SetHtmlContent(Content);
|
||||
else
|
||||
{
|
||||
TagHelperContent content = await output.GetChildContentAsync(true);
|
||||
Content = content.GetContent();
|
||||
CachedContent[Key] = Content;
|
||||
output.Content.SetHtmlContent(Content);
|
||||
|
||||
// ensure Invalidate Triggers
|
||||
if (Event != null)
|
||||
lock (InvalidateTriggers)
|
||||
foreach (String Event in Event.Split(';', StringSplitOptions.TrimEntries | StringSplitOptions.RemoveEmptyEntries))
|
||||
{
|
||||
if (!InvalidateTriggers.TryGetValue(Event, out HashSet<String>? Caches))
|
||||
{
|
||||
Caches = new HashSet<string>();
|
||||
InvalidateTriggers.Add(Event, Caches);
|
||||
}
|
||||
Caches.Add(Key);
|
||||
}
|
||||
}
|
||||
}
|
||||
public override void Process(TagHelperContext context, TagHelperOutput output) => this.ProcessAsync(context, output).ConfigureAwait(false).GetAwaiter().GetResult();
|
||||
}
|
||||
}
|
||||
@ -1,4 +1,4 @@
|
||||
using ExtensiblePortfolioSite.SDK.Plugins;
|
||||
using Microsoft.Extensions.FileSystemGlobbing.Internal.PathSegments;
|
||||
|
||||
using System.Collections.Concurrent;
|
||||
using System.Net.Sockets;
|
||||
@ -25,15 +25,10 @@ namespace ExtensiblePortfolioSite
|
||||
{
|
||||
this.Parent = Parent;
|
||||
this.Identifier = Identifier;
|
||||
lock (this.Parent.ScopeStack)
|
||||
this.Parent.ScopeStack.AddLast(this);
|
||||
this.Parent.ScopeStack.AddLast(this);
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
lock (this.Parent.ScopeStack)
|
||||
this.Parent.ScopeStack.Remove(this);
|
||||
}
|
||||
public void Dispose() => this.Parent.ScopeStack.Remove(this);
|
||||
}
|
||||
|
||||
private readonly LinkedList<Scope> ScopeStack = new();
|
||||
@ -58,7 +53,7 @@ namespace ExtensiblePortfolioSite
|
||||
|
||||
while (Curr != null)
|
||||
{
|
||||
MessageBuilder.Append('>');
|
||||
MessageBuilder.Append(">");
|
||||
MessageBuilder.Append(Curr.Value.Identifier);
|
||||
Curr = Curr.Next;
|
||||
}
|
||||
@ -118,18 +113,16 @@ namespace ExtensiblePortfolioSite
|
||||
#endregion Static
|
||||
|
||||
|
||||
private readonly Lazy<Logger> PluginLogger;
|
||||
|
||||
private Boolean Running;
|
||||
private readonly Thread LogWriter;
|
||||
private readonly TextWriter? COut;
|
||||
private readonly TextWriter COut;
|
||||
private readonly StreamWriter? FOut;
|
||||
public LoggerProvider(Boolean ConsoleLogging, String FileOutput)
|
||||
public LoggerProvider(String FileOutput)
|
||||
{
|
||||
this.COut = ConsoleLogging ? Console.Out : null;
|
||||
this.COut = Console.Out;
|
||||
|
||||
String Dir = Path.GetDirectoryName(FileOutput)!;
|
||||
if (!Directory.Exists(Dir)) Directory.CreateDirectory(Dir);
|
||||
if(!Directory.Exists(Dir)) Directory.CreateDirectory(Dir);
|
||||
this.FOut = new StreamWriter(new FileStream(FileOutput, FileMode.Append, FileAccess.Write, FileShare.Read, 1024, false), ENCODING, 1024, false);
|
||||
|
||||
this.LogWriter = new Thread(LogWriter_thread)
|
||||
@ -138,16 +131,13 @@ namespace ExtensiblePortfolioSite
|
||||
Priority = ThreadPriority.BelowNormal,
|
||||
IsBackground = true,
|
||||
};
|
||||
|
||||
this.PluginLogger = new Lazy<Logger>(() => (Logger)CreateLogger("PluginManager"), true);
|
||||
|
||||
this.Running = true;
|
||||
this.LogWriter.Start();
|
||||
}
|
||||
public LoggerProvider(Boolean ConsoleLogging)
|
||||
public LoggerProvider()
|
||||
{
|
||||
this.FOut = null;
|
||||
this.COut = ConsoleLogging ? Console.Out : null;
|
||||
this.COut = Console.Out;
|
||||
|
||||
this.LogWriter = new Thread(LogWriter_thread)
|
||||
{
|
||||
@ -155,61 +145,11 @@ namespace ExtensiblePortfolioSite
|
||||
Priority = ThreadPriority.BelowNormal,
|
||||
IsBackground = true,
|
||||
};
|
||||
|
||||
this.PluginLogger = new Lazy<Logger>(() => (Logger)CreateLogger("PluginManager"), true);
|
||||
|
||||
this.Running = true;
|
||||
this.LogWriter.Start();
|
||||
}
|
||||
|
||||
|
||||
internal void LogPluginManagerError(PluginErrorSeverity Severity, Plugin Plugin, String Message, Type? Target, Exception? ex)
|
||||
{
|
||||
StringBuilder MessageBuilder = GetStringBuilder();
|
||||
{
|
||||
WritePrefix(MessageBuilder, this.PluginLogger.Value, Severity switch
|
||||
{
|
||||
PluginErrorSeverity.Fatal => LogLevel.Critical,
|
||||
PluginErrorSeverity.Info => LogLevel.Information,
|
||||
PluginErrorSeverity.Warning => LogLevel.Warning,
|
||||
PluginErrorSeverity.Error => LogLevel.Error,
|
||||
_ => throw new ArgumentException("Unknown PluginErrorSeverity Value", nameof(Severity))
|
||||
});
|
||||
MessageBuilder.Append('{');
|
||||
MessageBuilder.Append(Plugin.Info.Name);
|
||||
MessageBuilder.Append(':');
|
||||
MessageBuilder.Append(Plugin.Info.Version.Major);
|
||||
MessageBuilder.Append('.');
|
||||
MessageBuilder.Append(Plugin.Info.Version.Minor);
|
||||
MessageBuilder.Append('.');
|
||||
MessageBuilder.Append(Plugin.Info.Version.Build);
|
||||
MessageBuilder.Append('.');
|
||||
MessageBuilder.Append(Plugin.Info.Version.Revision);
|
||||
MessageBuilder.Append('}');
|
||||
if (Target != null)
|
||||
{
|
||||
MessageBuilder.Append('@');
|
||||
MessageBuilder.Append(Target.FullName);
|
||||
}
|
||||
MessageBuilder.Append("::");
|
||||
MessageBuilder.Append(Message);
|
||||
if (ex != null)
|
||||
{
|
||||
MessageBuilder.Append("\n ");
|
||||
MessageBuilder.Append(ex.Message);
|
||||
if (ex.StackTrace != null)
|
||||
{
|
||||
MessageBuilder.Append("\n ");
|
||||
MessageBuilder.Append(ex.StackTrace.Replace("\n", "\n "));
|
||||
}
|
||||
}
|
||||
lock (LogQueue)
|
||||
LogQueue.Enqueue(MessageBuilder.ToString());
|
||||
}
|
||||
ReclaimStringBuilder(MessageBuilder);
|
||||
}
|
||||
|
||||
|
||||
private readonly SortedDictionary<String, Logger> LoggerLookup = new();
|
||||
public ILogger CreateLogger(string categoryName)
|
||||
{
|
||||
@ -239,11 +179,20 @@ namespace ExtensiblePortfolioSite
|
||||
this.LogWriter.Join();
|
||||
}
|
||||
|
||||
|
||||
|
||||
private readonly ConcurrentQueue<String> LogQueue = new();
|
||||
private void WriteLog(Logger logger, LogLevel logLevel, EventId eventId, String Message)
|
||||
{
|
||||
StringBuilder MessageBuilder = GetStringBuilder();
|
||||
{
|
||||
WritePrefix(MessageBuilder, logger, logLevel);
|
||||
MessageBuilder.Append('[');
|
||||
MessageBuilder.Append(DateTime.Now.ToShortTimeString());
|
||||
MessageBuilder.Append('\\');
|
||||
MessageBuilder.Append(logLevel.ToString());
|
||||
MessageBuilder.Append('\\');
|
||||
MessageBuilder.Append(logger.Category);
|
||||
MessageBuilder.Append(']');
|
||||
MessageBuilder.Append('{');
|
||||
MessageBuilder.Append(eventId.Id);
|
||||
MessageBuilder.Append(':');
|
||||
@ -252,48 +201,25 @@ namespace ExtensiblePortfolioSite
|
||||
MessageBuilder.Append("}::");
|
||||
MessageBuilder.Append(Message);
|
||||
|
||||
lock (LogQueue)
|
||||
LogQueue.Enqueue(MessageBuilder.ToString());
|
||||
LogQueue.Enqueue(MessageBuilder.ToString());
|
||||
}
|
||||
ReclaimStringBuilder(MessageBuilder);
|
||||
}
|
||||
|
||||
private static void WritePrefix(StringBuilder MessageBuilder, Logger logger, LogLevel logLevel)
|
||||
{
|
||||
MessageBuilder.Append('[');
|
||||
MessageBuilder.Append(DateTime.Now.ToShortTimeString());
|
||||
MessageBuilder.Append('\\');
|
||||
MessageBuilder.Append(logLevel.ToString());
|
||||
MessageBuilder.Append('\\');
|
||||
MessageBuilder.Append(logger.Category);
|
||||
MessageBuilder.Append(']');
|
||||
}
|
||||
|
||||
|
||||
private readonly ConcurrentQueue<String> LogQueue = new();
|
||||
private void LogWriter_thread()
|
||||
{
|
||||
[MethodImpl(MethodImplOptions.AggressiveOptimization | MethodImplOptions.AggressiveInlining)]
|
||||
void WriteLog(String Log)
|
||||
async void WriteLog(String Log)
|
||||
{
|
||||
Task[] tasks = new Task[2];
|
||||
int x = 0;
|
||||
|
||||
if (COut != null)
|
||||
tasks[x++] = COut.WriteLineAsync(Log);
|
||||
|
||||
Task COutWrite_task = COut.WriteLineAsync(Log);
|
||||
if (FOut != null)
|
||||
tasks[x++] = FOut.WriteLineAsync(Log);
|
||||
|
||||
Task.WaitAll(tasks[0..x]);
|
||||
await FOut.WriteLineAsync(Log);
|
||||
await COutWrite_task;
|
||||
}
|
||||
|
||||
while (this.Running)
|
||||
{
|
||||
if (LogQueue.Count > 0)
|
||||
lock (LogQueue)
|
||||
while (LogQueue.TryDequeue(out String? Log))
|
||||
WriteLog(Log);
|
||||
|
||||
while (LogQueue.TryDequeue(out String? Log))
|
||||
WriteLog(Log);
|
||||
Thread.Sleep(1000);
|
||||
}
|
||||
lock (LogQueue)
|
||||
|
||||
@ -8,37 +8,32 @@
|
||||
<div class="about-me" style="display: flex; flex-direction: row; width: 100%; flex-grow: 1; justify-content: center; align-items: center;">
|
||||
<div class="languages-container" style="min-width: 20%;">
|
||||
<h3 style="width: 100%; text-align:center;">Known Languages</h3>
|
||||
@foreach(var language in Config.GetConfig().ProfileSection.Languages)
|
||||
@foreach(var language in Config.Languages)
|
||||
{
|
||||
<div style="display: flex; flex-direction: row; align-items: center; border-top: solid 1px var(--bs-gray-800); padding: 0.25em 0; ">
|
||||
@if(language.Icon != null)
|
||||
{
|
||||
<img src="@language.Icon" style="height: 2em; margin-right: 1em;" />
|
||||
}
|
||||
<h3 style="margin: 0 0 0 .25em;">@language.Name</h3>
|
||||
<img src="@language.Value" style="height: 2em; margin-right: 1em;" />
|
||||
<h3 style="margin: 0 0 0 .25em;">@language.Key</h3>
|
||||
<h4 style="margin: 0 0 0 auto;">100%</h4>
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
<div class="about-me-container" style="display: flex; flex-direction: column; flex: 1; align-items: center; min-width: 60%; min-height: 40vh;">
|
||||
<h2>About Me</h2>
|
||||
@foreach(String s in Config.GetConfig().ProfileSection.Biography)
|
||||
{
|
||||
<p>@s</p>
|
||||
}
|
||||
<p>Lorem ipsum dolor sit amet, consectetur adipiscing elit. Fusce commodo imperdiet hendrerit. Vestibulum pretium mauris eu sagittis tristique. Sed tellus nulla, facilisis in molestie id, fermentum facilisis nulla. Nullam vel tristique neque. Nam accumsan arcu vel nulla efficitur, posuere convallis diam rhoncus. Mauris mollis at sapien eu gravida. Nullam viverra velit porttitor ex mattis, in iaculis justo tincidunt. Morbi pulvinar odio nec mauris luctus facilisis. Phasellus aliquam commodo turpis. Quisque in tincidunt tellus, vel facilisis massa.</p>
|
||||
<p>Pellentesque auctor eros vitae pretium ornare. Donec quam erat, tempor id elit eu, aliquet suscipit mi. Morbi facilisis nisi vel dapibus facilisis. Phasellus efficitur ac odio id ultricies. Cras ullamcorper lacinia posuere. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia curae; Vivamus lobortis, arcu a mollis venenatis, nisi libero cursus risus, ut sodales felis erat quis lorem. Praesent nisl turpis, egestas ac auctor et, tempor ac mauris. Donec aliquam lacus nisi, id hendrerit neque aliquam ut. Nullam maximus velit nulla, quis euismod lacus porttitor ac. Nullam lacinia non arcu vitae scelerisque. Nulla nisl nibh, vestibulum eget ex sit amet, viverra vulputate purus. Nullam non arcu ac diam sagittis consectetur.</p>
|
||||
</div>
|
||||
<div class="personals-container" style="min-width: 20%;">
|
||||
<div style="display: flex; flex-direction: row; align-items: center; padding: 0.25em 0; ">
|
||||
<img src="/content/img/user.svg" style="height: 2em; margin-right: 1em;" />
|
||||
<span style="margin: 0 0 0 .25em;">@Config.GetConfig().ProfileSection.Name</span>
|
||||
<img src="img/user.svg" style="height: 2em; margin-right: 1em;" />
|
||||
<span style="margin: 0 0 0 .25em;">@Config.Name</span>
|
||||
</div>
|
||||
<div style="display: flex; flex-direction: row; align-items: center; border-top: solid 1px var(--bs-gray-800); padding: 0.25em 0; ">
|
||||
<img src="/content/img/email.svg" style="height: 2em; margin-right: 1em;" />
|
||||
<span style="margin: 0 0 0 .25em;">@Config.GetConfig().ProfileSection.Email</span>
|
||||
<img src="img/email.svg" style="height: 2em; margin-right: 1em;" />
|
||||
<span style="margin: 0 0 0 .25em;">@Config.Email</span>
|
||||
</div>
|
||||
<div style="display: flex; flex-direction: row; align-items: center; border-top: solid 1px var(--bs-gray-800); padding: 0.25em 0; ">
|
||||
<img src="/content/img/phone.svg" style="height: 2em; margin-right: 1em;" />
|
||||
<span style="margin: 0 0 0 .25em;">@Config.GetConfig().ProfileSection.Phone</span>
|
||||
<img src="img/phone.svg" style="height: 2em; margin-right: 1em;" />
|
||||
<span style="margin: 0 0 0 .25em;">@Config.Phone</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@ -3,7 +3,6 @@ using Microsoft.AspNetCore.Mvc.RazorPages;
|
||||
|
||||
namespace ExtensiblePortfolioSite.Pages.About
|
||||
{
|
||||
[ResponseCache(Duration = 60, Location = ResponseCacheLocation.Any, NoStore = false)]
|
||||
public class AboutModel : PageModel
|
||||
{
|
||||
public void OnGet()
|
||||
|
||||
@ -6,15 +6,15 @@
|
||||
}
|
||||
|
||||
<div class="text-center">
|
||||
<h1 class="display-4">@Config.GetConfig().ProfileSection.Name</h1>
|
||||
@if(Config.GetConfig().ProfileSection.Description == null)
|
||||
<h1 class="display-4">@Config.Name</h1>
|
||||
@if(Config.Description == null)
|
||||
{
|
||||
<p>
|
||||
No description ;(
|
||||
</p>
|
||||
} else
|
||||
{
|
||||
@foreach(String s in Config.GetConfig().ProfileSection.Description)
|
||||
@foreach(String s in Config.Description)
|
||||
{
|
||||
<div class="description">
|
||||
<span>@s</span>
|
||||
|
||||
@ -1,9 +1,7 @@
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.AspNetCore.Mvc.RazorPages;
|
||||
using Microsoft.AspNetCore.Mvc.RazorPages;
|
||||
|
||||
namespace ExtensiblePortfolioSite.Pages
|
||||
{
|
||||
[ResponseCache(Duration = 60, Location = ResponseCacheLocation.Any, NoStore = false)]
|
||||
public class IndexModel : PageModel
|
||||
{
|
||||
private readonly ILogger<IndexModel> _logger;
|
||||
@ -15,7 +13,7 @@ namespace ExtensiblePortfolioSite.Pages
|
||||
|
||||
public void OnGet()
|
||||
{
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1,100 +1,92 @@
|
||||
@page
|
||||
@using System.Text.Json
|
||||
@using ExtensiblePortfolioSite.SDK.Git
|
||||
@model ProjectsModel
|
||||
@{
|
||||
ViewData["Title"] = "Projects";
|
||||
Layout = "_Layout";
|
||||
ViewData["Title"] = "Projects";
|
||||
Layout = "_Layout";
|
||||
}
|
||||
|
||||
<script src="https://cdn.jsdelivr.net/npm/marked/marked.min.js"></script>
|
||||
<div class="container repo-container">
|
||||
@{
|
||||
foreach (Config.ConfigObject.GitRepoObject repo in Config.GetConfig().GitRepos)
|
||||
{
|
||||
try
|
||||
{
|
||||
GitCacheItemBase<IRepository> cache_repository = GitManager.GetService(repo.ServiceProvider).GetCache().GetRepository(repo.Repository);
|
||||
<cache expires-on=@cache_repository.ExpiresOn invalidate-events="config_update">
|
||||
@{
|
||||
IRepository repository = cache_repository.GetValue();
|
||||
@{
|
||||
//var githubService = GitManager.GetService("github.com");
|
||||
//var user = githubService.GetUser(new Uri("https://github.com/KoromaruKoruko"));
|
||||
//Console.WriteLine("FOUND USER");
|
||||
//Console.WriteLine(user.Name);
|
||||
//var repos = user.GetUserRepositories();
|
||||
//Console.WriteLine("REPOSITORIES");
|
||||
//foreach(var repo in repos)
|
||||
// Console.WriteLine(repo.Name);
|
||||
var client = new HttpClient();
|
||||
client.DefaultRequestHeaders.Add("User-Agent", "KoromaruKoruko");
|
||||
client.DefaultRequestHeaders.Add("Authorization", "Bearer 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");
|
||||
HttpResponseMessage response = await client.GetAsync("https://api.github.com/users/KoromaruKoruko/repos");
|
||||
JsonDocument json = await JsonDocument.ParseAsync(await response.Content.ReadAsStreamAsync());
|
||||
|
||||
// TODO: [ProjectPage] Handle no Commit properly
|
||||
Boolean hasCommit = repository.TryGetCommit(0, out ICommit? commit);
|
||||
foreach(JsonElement elem in json.RootElement.EnumerateArray())
|
||||
{
|
||||
int popout_id = Random.Shared.Next();
|
||||
HttpResponseMessage commitResponse = await client.GetAsync(elem.GetProperty("commits_url").GetString().Split('{')[0]);
|
||||
JsonDocument commitInfo = await JsonDocument.ParseAsync(await commitResponse.Content.ReadAsStreamAsync());
|
||||
JsonElement commitJson = commitInfo.RootElement.EnumerateArray().First();
|
||||
String[] lastCommitMessage = commitJson.GetProperty("commit").GetProperty("message")!.GetString()!.Split("\n").Where(s => s != "").ToArray();
|
||||
|
||||
String[] lastCommitMessage = hasCommit
|
||||
? commit!.Description.Split("\n").Where(s => !String.IsNullOrEmpty(s)).ToArray()
|
||||
: Array.Empty<String>();
|
||||
|
||||
String Description = String.Join('\n', repo.Description);
|
||||
}
|
||||
<div class="repo">
|
||||
<div class="repo-title">
|
||||
<span class="eps-lineclamp-1">@repository.Owner.Name/</span>
|
||||
<span class="eps-lineclamp-1">@repository.Name</span>
|
||||
</div>
|
||||
<div class="repo-description eps-lineclamp-2">
|
||||
@Description
|
||||
</div>
|
||||
<div class="popout">
|
||||
<span class="popout-title">@Description</span>
|
||||
</div>
|
||||
<div class="commit">
|
||||
@if (hasCommit)
|
||||
{
|
||||
<img class="commit-author" src="@commit!.AuthorAvatarUrl" />
|
||||
}
|
||||
else
|
||||
{
|
||||
<img class="commit-author" src="<default-image>" />
|
||||
}
|
||||
<div class="commit-info">
|
||||
<div class="commit-title eps-lineclamp-1">
|
||||
@if (lastCommitMessage.Length > 0)
|
||||
{
|
||||
@lastCommitMessage[0]
|
||||
}
|
||||
</div>
|
||||
<div class="commit-description eps-lineclamp-1">
|
||||
@if (lastCommitMessage.Length > 1)
|
||||
{
|
||||
@lastCommitMessage[1]
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
<div class="popout popout-commit">
|
||||
@if (lastCommitMessage.Length > 0)
|
||||
{
|
||||
<span class="popout-title">@lastCommitMessage[0]</span>
|
||||
<br />
|
||||
<div class="popout-content">
|
||||
@if (lastCommitMessage.Length > 1)
|
||||
{
|
||||
@String.Join('\n', lastCommitMessage.Skip(1))
|
||||
}
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
<div class="repo-footer">
|
||||
<a class="repo-link" href="@repo.Repository.OriginalString">
|
||||
<span>visit repo >></span>
|
||||
</a>
|
||||
<img class="repo-icon" src="/content/img/logos/@repo.ServiceProvider/light.png" />
|
||||
</div>
|
||||
</div>
|
||||
</cache>
|
||||
// End repo
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Model.Logger.LogError(ex, $"Failed to Render Repository '{repo.Repository}'");
|
||||
}
|
||||
}
|
||||
}
|
||||
<script>
|
||||
function processMarkdown(element) {
|
||||
var content = element.getElementsByClassName('popout-content')[0]
|
||||
content.innerHTML = marked.parse(content.innerHTML);
|
||||
}
|
||||
Array.from(document.getElementsByClassName('popout-commit')).forEach(processMarkdown);
|
||||
</script>
|
||||
</div>
|
||||
// Begin repo
|
||||
<div class="repo">
|
||||
<div class="repo-title">
|
||||
<span class="eps-lineclamp-1">KoromaruKoruko/</span>
|
||||
<span class="eps-lineclamp-1">@elem.GetProperty("name")</span>
|
||||
</div>
|
||||
<div class="repo-description eps-lineclamp-2">
|
||||
@elem.GetProperty("description")
|
||||
</div>
|
||||
<div class="popout">
|
||||
<span class="popout-title">@elem.GetProperty("description")</span>
|
||||
</div>
|
||||
<div class="commit">
|
||||
<img class="commit-author" src="@commitJson.GetProperty("author").GetProperty("avatar_url").GetString()"/>
|
||||
<div class="commit-info">
|
||||
<div class="commit-title eps-lineclamp-1">
|
||||
@if(lastCommitMessage.Length > 0)
|
||||
{
|
||||
@lastCommitMessage[0]
|
||||
}
|
||||
</div>
|
||||
<div class="commit-description eps-lineclamp-1">
|
||||
@if(lastCommitMessage.Length > 1)
|
||||
{
|
||||
@lastCommitMessage[1]
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
<div class="popout popout-commit">
|
||||
@if (lastCommitMessage.Length > 0)
|
||||
{
|
||||
<span class="popout-title">@lastCommitMessage[0]</span>
|
||||
<br />
|
||||
var commitDescription = lastCommitMessage.Skip(1).ToHashSet();
|
||||
<div class="popout-content">
|
||||
@if(commitDescription.Count > 0)
|
||||
@commitDescription.Aggregate((a, b) => a + "\n" + b)
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
<div class="repo-footer">
|
||||
<a class="repo-link" href="@elem.GetProperty("html_url").GetString()">
|
||||
<span>visit repo >></span>
|
||||
</a>
|
||||
<img class="repo-icon" src="/img/logos/github/Light-64px.png"/>
|
||||
</div>
|
||||
</div>
|
||||
// End repo
|
||||
}
|
||||
<script>
|
||||
function processMarkdown(element) {
|
||||
element.getElementsByClassName('popout-content')[0].innerHTML = marked.parse(element.getElementsByClassName('popout-content')[0].innerHTML);
|
||||
}
|
||||
Array.from(document.getElementsByClassName('popout-commit')).forEach(processMarkdown);
|
||||
</script>
|
||||
}
|
||||
</div>
|
||||
@ -1,16 +1,14 @@
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.AspNetCore.Mvc.RazorPages;
|
||||
|
||||
namespace ExtensiblePortfolioSite.Pages
|
||||
{
|
||||
[ResponseCache(Duration = 60, Location = ResponseCacheLocation.Any, NoStore = false)]
|
||||
public class ProjectsModel : PageModel
|
||||
{
|
||||
public readonly ILogger<IndexModel> Logger;
|
||||
private readonly ILogger<IndexModel> _logger;
|
||||
|
||||
public ProjectsModel(ILogger<IndexModel> logger)
|
||||
{
|
||||
Logger = logger;
|
||||
_logger = logger;
|
||||
}
|
||||
|
||||
public void OnGet()
|
||||
|
||||
@ -5,13 +5,14 @@
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>@ViewData["Title"] - ExtensiblePortfolioSite</title>
|
||||
<link rel="stylesheet" href="https://bootswatch.com/5/superhero/bootstrap.min.css" />
|
||||
<link rel="stylesheet" href="/content/css/site.css" asp-append-version="true" />
|
||||
<link rel="stylesheet" href="~/css/site.css" asp-append-version="true" />
|
||||
<link rel="stylesheet" href="~/ExtensiblePortfolioSite.styles.css" asp-append-version="true" />
|
||||
</head>
|
||||
<body>
|
||||
<header>
|
||||
<nav class="navbar navbar-expand-sm navbar-toggleable-sm navbar-dark border-bottom box-shadow mb-3">
|
||||
<div class="container">
|
||||
<a class="navbar-brand" asp-area="" asp-page="/Index">@Config.GetConfig().ProfileSection.Name</a>
|
||||
<a class="navbar-brand" asp-area="" asp-page="/Index">@Config.Name</a>
|
||||
<button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target=".navbar-collapse" aria-controls="navbarSupportedContent" aria-expanded="false" aria-label="Toggle navigation">
|
||||
<span class="navbar-toggler-icon"></span>
|
||||
</button>
|
||||
@ -47,15 +48,15 @@
|
||||
})
|
||||
</script>
|
||||
<footer class="border-top footer text-muted container">
|
||||
<span id="copyright-disclaimer">© 2022 - @Config.GetConfig().ProfileSection.Name</span>
|
||||
<span id="copyright-disclaimer">© 2022 - @Config.Name</span>
|
||||
<span>Photo by <a href="https://unsplash.com/photos/iqrdLI5p87I">Serge Bauwens</a> on Unsplash</span>
|
||||
</footer>
|
||||
|
||||
|
||||
|
||||
<script src="/content/lib/jquery/dist/jquery.min.js"></script>
|
||||
<script src="/content/lib/bootstrap/dist/js/bootstrap.bundle.min.js"></script>
|
||||
<script src="/content/js/site.js" asp-append-version="true"></script>
|
||||
<script src="~/lib/jquery/dist/jquery.min.js"></script>
|
||||
<script src="~/lib/bootstrap/dist/js/bootstrap.bundle.min.js"></script>
|
||||
<script src="~/js/site.js" asp-append-version="true"></script>
|
||||
|
||||
@await RenderSectionAsync("Scripts", required: false)
|
||||
</body>
|
||||
|
||||
@ -1,2 +1,2 @@
|
||||
<script src="/content/lib/jquery-validation/dist/jquery.validate.min.js"></script>
|
||||
<script src="/content/lib/jquery-validation-unobtrusive/jquery.validate.unobtrusive.min.js"></script>
|
||||
<script src="~/lib/jquery-validation/dist/jquery.validate.min.js"></script>
|
||||
<script src="~/lib/jquery-validation-unobtrusive/jquery.validate.unobtrusive.min.js"></script>
|
||||
|
||||
@ -3,7 +3,6 @@ using Microsoft.AspNetCore.Mvc.RazorPages;
|
||||
|
||||
namespace ExtensiblePortfolioSite.Pages.Socials
|
||||
{
|
||||
[ResponseCache(Duration = 60, Location = ResponseCacheLocation.Any, NoStore = false)]
|
||||
public class SocialsModel : PageModel
|
||||
{
|
||||
public void OnGet()
|
||||
|
||||
@ -1,14 +1,3 @@
|
||||
@using Microsoft.Extensions.Logging
|
||||
|
||||
@using ExtensiblePortfolioSite
|
||||
@using ExtensiblePortfolioSite.SDK
|
||||
@using ExtensiblePortfolioSite.SDK.Caching
|
||||
@using ExtensiblePortfolioSite.SDK.Git
|
||||
@using ExtensiblePortfolioSite.SDK.Resources
|
||||
|
||||
@using ExtensiblePortfolioSite
|
||||
@namespace ExtensiblePortfolioSite.Pages
|
||||
|
||||
|
||||
@addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers
|
||||
@removeTagHelper Microsoft.AspNetCore.Mvc.TagHelpers.CacheTagHelper, Microsoft.AspNetCore.Mvc.TagHelpers
|
||||
@addTagHelper ExtensiblePortfolioSite.InvalidableCacheTagHelper, ExtensiblePortfolioSite
|
||||
@ -1,8 +1,4 @@
|
||||
using ExtensiblePortfolioSite.SDK.Plugins;
|
||||
using ExtensiblePortfolioSite.SDK.Resources;
|
||||
|
||||
using Microsoft.AspNetCore.Http.Features;
|
||||
using Microsoft.Extensions.FileProviders;
|
||||
|
||||
namespace ExtensiblePortfolioSite
|
||||
{
|
||||
@ -10,9 +6,12 @@ namespace ExtensiblePortfolioSite
|
||||
{
|
||||
private static void ParseArgs(String[] args, WebApplicationBuilder builder)
|
||||
{
|
||||
|
||||
Boolean Error = false;
|
||||
Boolean Help = false;
|
||||
string ConfigPath = "eps_host_config.json";
|
||||
|
||||
Boolean help = false;
|
||||
Boolean log_nofile = false;
|
||||
string log_output = "logs/log_{date}.log";
|
||||
|
||||
for (int x = 0; x < args.Length; x++)
|
||||
{
|
||||
@ -25,8 +24,16 @@ namespace ExtensiblePortfolioSite
|
||||
|
||||
switch(arg[0..s])
|
||||
{
|
||||
case "+config":
|
||||
ConfigPath = value;
|
||||
case "+log_nofile":
|
||||
log_nofile = true;
|
||||
break;
|
||||
|
||||
case "+log_output":
|
||||
log_output = value;
|
||||
break;
|
||||
|
||||
case "+help":
|
||||
help = true;
|
||||
break;
|
||||
|
||||
default:
|
||||
@ -36,15 +43,17 @@ namespace ExtensiblePortfolioSite
|
||||
}
|
||||
}
|
||||
else if(arg == "help")
|
||||
Help = true;
|
||||
help = true;
|
||||
}
|
||||
|
||||
if(Help)
|
||||
if(help)
|
||||
{
|
||||
Console.Error.WriteLine(
|
||||
@"
|
||||
[Help Message]
|
||||
+config={configpath} Override Default Config Path
|
||||
+log_nofile Disable Flle Logging
|
||||
+log_output={filepath} Set Log File Output
|
||||
+help Display Help Message
|
||||
"
|
||||
);
|
||||
Environment.Exit(Error ? 0 : 2);
|
||||
@ -56,23 +65,15 @@ namespace ExtensiblePortfolioSite
|
||||
Environment.Exit(2);
|
||||
}
|
||||
|
||||
Config.SetConfigFile(ConfigPath);
|
||||
LoggerProvider Logger;
|
||||
builder.Logging.ClearProviders();
|
||||
|
||||
Config.ConfigObject Conf = Config.GetConfig();
|
||||
|
||||
if (!Conf.LoggingSection.FileLogging)
|
||||
builder.Logging.AddProvider(Logger = new LoggerProvider(Conf.LoggingSection.ConsoleLogging));
|
||||
if (log_nofile)
|
||||
builder.Logging.AddProvider(new LoggerProvider());
|
||||
else
|
||||
builder.Logging.AddProvider(Logger = new LoggerProvider(Conf.LoggingSection.ConsoleLogging,
|
||||
Conf.LoggingSection.FileLogPath!
|
||||
builder.Logging.AddProvider(new LoggerProvider(
|
||||
log_output
|
||||
.Replace("{date}", DateTime.Now.ToString("yyyy-MM-dd"))
|
||||
.Replace("{time}", DateTime.Now.ToString("HH.mm.ss"))
|
||||
));
|
||||
|
||||
// Log On Error
|
||||
PluginManager.OnLogMessage += Logger.LogPluginManagerError;
|
||||
}
|
||||
|
||||
public static void Main(string[] args)
|
||||
@ -80,29 +81,18 @@ namespace ExtensiblePortfolioSite
|
||||
var builder = WebApplication.CreateBuilder(args);
|
||||
|
||||
// Load Config
|
||||
Config.GetConfig();
|
||||
Config.LoadConfig();
|
||||
|
||||
// Parse input args
|
||||
ParseArgs(args, builder);
|
||||
|
||||
|
||||
// Load Plugins
|
||||
PluginManager.LoadPlugins(Path.GetFullPath("Plugins/"));
|
||||
|
||||
//Initialize Plugins
|
||||
PluginManager.InitializePlugins();
|
||||
|
||||
// TODO: [Plugin] Web Hook Provider
|
||||
// TODO: [Plugin] API Endpoint Provider
|
||||
// TODO: [Plugin] Reloading/Updating/Disabling
|
||||
// Add services to the container.
|
||||
builder.Services.AddRazorPages();
|
||||
|
||||
// Add razor pages from the Project Directory
|
||||
builder.Services.AddRazorPages()
|
||||
// precompile razor pages
|
||||
.AddRazorRuntimeCompilation();
|
||||
|
||||
|
||||
// Add Responce Cache
|
||||
builder.Services.AddResponseCaching();
|
||||
var app = builder.Build();
|
||||
|
||||
// Configure the HTTP request pipeline.
|
||||
@ -113,21 +103,12 @@ namespace ExtensiblePortfolioSite
|
||||
app.UseHsts();
|
||||
}
|
||||
|
||||
app.UseHttpsRedirection(); // not required but, prefered
|
||||
|
||||
app.UseStaticFiles(new StaticFileOptions()
|
||||
{
|
||||
// tie in physical files and virtual files!
|
||||
FileProvider = new CompositeFileProvider(new VFSFileProvider(), new PhysicalFileProvider(Path.GetFullPath("wwwroot/"))),
|
||||
RequestPath = "/content",
|
||||
HttpsCompression = HttpsCompressionMode.Compress,
|
||||
});
|
||||
app.UseHttpsRedirection();
|
||||
app.UseStaticFiles();
|
||||
|
||||
app.UseRouting();
|
||||
|
||||
app.UseResponseCaching();
|
||||
|
||||
//app.UseAuthorization(); // not needed
|
||||
app.UseAuthorization();
|
||||
|
||||
app.MapRazorPages();
|
||||
|
||||
|
||||
@ -3,39 +3,33 @@
|
||||
"ExtensiblePortfolioSite": {
|
||||
"commandName": "Project",
|
||||
"launchBrowser": true,
|
||||
"dotnetRunMessages": true,
|
||||
"environmentVariables": {
|
||||
"ASPNETCORE_ENVIRONMENT": "Development"
|
||||
},
|
||||
"applicationUrl": "https://localhost:58428;http://localhost:58429"
|
||||
},
|
||||
"Release": {
|
||||
"commandName": "Project",
|
||||
"launchBrowser": false,
|
||||
"dotnetRunMessages": true,
|
||||
"applicationUrl": "https://localhost:7128;http://localhost:5128"
|
||||
},
|
||||
"IIS Express": {
|
||||
"commandName": "IISExpress",
|
||||
"launchBrowser": true,
|
||||
"environmentVariables": {
|
||||
"ASPNETCORE_HOSTINGSTARTUPASSEMBLIES": "Microsoft.AspNetCore.Mvc.Razor.RuntimeCompilation"
|
||||
},
|
||||
"applicationUrl": "https://0.0.0.0:443;http://0.0.0.0:80"
|
||||
"ASPNETCORE_ENVIRONMENT": "Development"
|
||||
}
|
||||
},
|
||||
"Docker": {
|
||||
"commandName": "Docker",
|
||||
"launchBrowser": true,
|
||||
"environmentVariables": {
|
||||
"ASPNETCORE_ENVIRONMENT": "Development"
|
||||
},
|
||||
"launchUrl": "{Scheme}://{ServiceHost}:{ServicePort}",
|
||||
"publishAllPorts": true,
|
||||
"useSSL": true
|
||||
},
|
||||
"DockerRelease": {
|
||||
"commandName": "Docker",
|
||||
"environmentVariables": {
|
||||
"ASPNETCORE_HOSTINGSTARTUPASSEMBLIES": "Microsoft.AspNetCore.Mvc.Razor.RuntimeCompilation"
|
||||
},
|
||||
"applicationUrl": "https://0.0.0.0:443;http://0.0.0.0:80",
|
||||
"publishAllPorts": true,
|
||||
"useSSL": true
|
||||
}
|
||||
},
|
||||
"iisSettings": {
|
||||
"windowsAuthentication": false,
|
||||
"anonymousAuthentication": true,
|
||||
"iisExpress": {
|
||||
"applicationUrl": "http://localhost:28648",
|
||||
"sslPort": 44342
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1,177 +0,0 @@
|
||||
using ExtensiblePortfolioSite.SDK.Resources;
|
||||
using ExtensiblePortfolioSite.SDK.Resources.VFS;
|
||||
|
||||
using Microsoft.Extensions.FileProviders;
|
||||
using Microsoft.Extensions.Primitives;
|
||||
|
||||
using System.Collections;
|
||||
using System.Text;
|
||||
|
||||
namespace ExtensiblePortfolioSite
|
||||
{
|
||||
public class VFSFileProvider : IFileProvider
|
||||
{
|
||||
private class VFS_FileInfo_File : IFileInfo
|
||||
{
|
||||
private readonly IVirtualFile File;
|
||||
public VFS_FileInfo_File(IVirtualFile File) => this.File = File;
|
||||
|
||||
public bool Exists => true;
|
||||
public bool IsDirectory => false;
|
||||
|
||||
public DateTimeOffset LastModified => DateTimeOffset.UnixEpoch;
|
||||
public long Length
|
||||
{
|
||||
get
|
||||
{
|
||||
if (this.File is VirtualFile VF)
|
||||
if(VF.ResourceContainer is LocalFile LocalFile)
|
||||
return new FileInfo(LocalFile.LocalFilePath).Length;
|
||||
|
||||
using Stream STM = this.File.OpenStream();
|
||||
return STM.Length;
|
||||
}
|
||||
}
|
||||
public string Name => File.Name;
|
||||
|
||||
public string PhysicalPath
|
||||
{
|
||||
get
|
||||
{
|
||||
StringBuilder Builder = new(256);
|
||||
Stack<IVirtualFolder> Folders = new();
|
||||
|
||||
IVirtualFolder? Current = File.Folder;
|
||||
while(Current != null)
|
||||
{
|
||||
Folders.Push(Current);
|
||||
Current = Current.Parent;
|
||||
}
|
||||
|
||||
while (Folders.TryPop(out Current))
|
||||
{
|
||||
Builder.Append(Current.Foldername);
|
||||
Builder.Append(Path.DirectorySeparatorChar);
|
||||
}
|
||||
|
||||
Builder.Append(File.Filename);
|
||||
return Builder.ToString();
|
||||
}
|
||||
}
|
||||
|
||||
public Stream CreateReadStream() => File.OpenStream();
|
||||
}
|
||||
private class VFS_FileInfo_Folder : IFileInfo
|
||||
{
|
||||
private readonly IVirtualFolder Folder;
|
||||
public VFS_FileInfo_Folder(IVirtualFolder File) => this.Folder = File;
|
||||
|
||||
public bool Exists => true;
|
||||
public bool IsDirectory => true;
|
||||
|
||||
public DateTimeOffset LastModified => DateTimeOffset.UnixEpoch;
|
||||
public long Length => -1;
|
||||
public string Name => Folder.Name;
|
||||
|
||||
public string PhysicalPath
|
||||
{
|
||||
get
|
||||
{
|
||||
StringBuilder Builder = new(256);
|
||||
Stack<IVirtualFolder> Folders = new();
|
||||
|
||||
IVirtualFolder? Current = Folder.Parent;
|
||||
while (Current != null)
|
||||
{
|
||||
Folders.Push(Current);
|
||||
Current = Current.Parent;
|
||||
}
|
||||
|
||||
while (Folders.TryPop(out Current))
|
||||
{
|
||||
Builder.Append(Current);
|
||||
Builder.Append(Path.DirectorySeparatorChar);
|
||||
}
|
||||
|
||||
Builder.Append(Folder.Foldername);
|
||||
return Builder.ToString();
|
||||
}
|
||||
}
|
||||
|
||||
public Stream CreateReadStream() => throw new InvalidOperationException("Can't Open Folder as File");
|
||||
}
|
||||
private class VFS_FileInfo_NULL : IFileInfo
|
||||
{
|
||||
public VFS_FileInfo_NULL(String PhysicalPath) => this.PhysicalPath = PhysicalPath;
|
||||
|
||||
public bool Exists => false;
|
||||
public bool IsDirectory => false;
|
||||
|
||||
public DateTimeOffset LastModified => DateTimeOffset.MinValue;
|
||||
public long Length => -1;
|
||||
public string Name => Path.GetFileName(PhysicalPath)!;
|
||||
|
||||
public string PhysicalPath { get; }
|
||||
|
||||
public Stream CreateReadStream() => throw new InvalidOperationException("Can't Open Non-Existant File!");
|
||||
}
|
||||
|
||||
|
||||
private class VFS_DirectoryContents : IDirectoryContents
|
||||
{
|
||||
private readonly IVirtualFolder? Folder;
|
||||
public VFS_DirectoryContents(IVirtualFolder? Folder) => this.Folder = Folder;
|
||||
|
||||
public bool Exists => this.Folder != null;
|
||||
|
||||
public IEnumerator<IFileInfo> GetEnumerator()
|
||||
{
|
||||
if(this.Folder != null)
|
||||
foreach(IVirtualEntry Entry in this.Folder.EnumerateEntries())
|
||||
{
|
||||
switch(Entry.Kind)
|
||||
{
|
||||
case VirtualEntryKind.File:
|
||||
yield return new VFS_FileInfo_File((IVirtualFile)Entry);
|
||||
break;
|
||||
|
||||
case VirtualEntryKind.Folder:
|
||||
yield return new VFS_FileInfo_Folder((IVirtualFolder)Entry);
|
||||
break;
|
||||
|
||||
default:
|
||||
continue;
|
||||
}
|
||||
}
|
||||
}
|
||||
IEnumerator IEnumerable.GetEnumerator() => this.GetEnumerator();
|
||||
}
|
||||
|
||||
public IDirectoryContents GetDirectoryContents(string subpath) => new VFS_DirectoryContents(ResourceManager.GetVirtualFolder($"/wwwroot{subpath}"));
|
||||
|
||||
public IFileInfo GetFileInfo(string subpath)
|
||||
{
|
||||
subpath = $"/wwwroot{subpath}";
|
||||
|
||||
IVirtualEntry? Entry = ResourceManager.GetVirtualEntry(subpath);
|
||||
|
||||
if (Entry == null)
|
||||
return new VFS_FileInfo_NULL(subpath);
|
||||
|
||||
switch(Entry.Kind)
|
||||
{
|
||||
case VirtualEntryKind.File:
|
||||
return new VFS_FileInfo_File((IVirtualFile)Entry);
|
||||
case VirtualEntryKind.Folder:
|
||||
return new VFS_FileInfo_Folder((IVirtualFolder)Entry);
|
||||
default:
|
||||
return new VFS_FileInfo_NULL(subpath);
|
||||
}
|
||||
}
|
||||
|
||||
public IChangeToken Watch(string filter)
|
||||
{
|
||||
throw new NotSupportedException();
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1,84 +1,59 @@
|
||||
{
|
||||
// Profile Options
|
||||
"Profile": {
|
||||
// Full Name (String)
|
||||
"Name": "Bailey English",
|
||||
// Phone Number (String|Null)
|
||||
"Phone": "+49 176 73236537",
|
||||
// Email (String|Null)
|
||||
"Email": "Bailey.Drahoss@outlook.com",
|
||||
// Full Name (String)
|
||||
"Name": "Bailey English",
|
||||
// Phone Number (String|Null)
|
||||
"Phone": "+49 176 73236537",
|
||||
// Email (String|Null)
|
||||
"Email": "Bailey.Drahoss@outlook.com",
|
||||
|
||||
// Multiple Lines to make up your Profile Description
|
||||
"Description": [
|
||||
"I'm a c# Programmer who prides herself in making things configurable and highly optimized."
|
||||
],
|
||||
// Multiple Lines to make up your Profile Description
|
||||
"Description": [
|
||||
"I'm a c# Programmer who prides herself in making things configurable and highly optimized."
|
||||
],
|
||||
|
||||
// Multi paragraph biography for About Me page
|
||||
"Biography": [
|
||||
"I'm a c# Programmer who prides herself in making things configurable and highly optimized."
|
||||
],
|
||||
|
||||
// Languages you'd like to advertise
|
||||
"Languages": [
|
||||
{
|
||||
"Name": "C#",
|
||||
"Icon": null
|
||||
},
|
||||
{
|
||||
"Name": "Kotlin",
|
||||
"Icon": null
|
||||
},
|
||||
{
|
||||
"Name": "Lua",
|
||||
"Icon": null
|
||||
},
|
||||
{
|
||||
"Name": "C/C++",
|
||||
"Icon": null
|
||||
}
|
||||
],
|
||||
|
||||
// Social Links
|
||||
"Links": [
|
||||
{
|
||||
// Social Name (String)
|
||||
"Name": "Github",
|
||||
// Link to Socal (String)
|
||||
"Link": "https://github.com/KoromaruKoruko",
|
||||
// Icon File/Link (String|Null)
|
||||
"Icon": "https://github.githubassets.com/images/modules/logos_page/GitHub-Logo.png"
|
||||
}
|
||||
]
|
||||
},
|
||||
|
||||
// Featured Git Repositories
|
||||
"GitRepos": [
|
||||
// Languages you'd like to advertise
|
||||
"Languages": [
|
||||
{
|
||||
// Internal Git Provider
|
||||
"ServiceProvider": "Github",
|
||||
// Git Repo (Accessed via the Git Provider)
|
||||
"Repository": "https://github.com/KoromaruKoruko/gmod_csModuleLoader",
|
||||
// Short Description
|
||||
"Description": [
|
||||
"Garry's Mod C# Module Loader"
|
||||
]
|
||||
"Name": "C#",
|
||||
"Icon": null
|
||||
},
|
||||
{
|
||||
"Name": "Kotlin",
|
||||
"Icon": null
|
||||
},
|
||||
{
|
||||
"Name": "Lua",
|
||||
"Icon": null
|
||||
},
|
||||
{
|
||||
"Name": "C/C++",
|
||||
"Icon": null
|
||||
}
|
||||
],
|
||||
|
||||
// Logging Options
|
||||
"Logging": {
|
||||
// Should logs be output to console?
|
||||
"ConsoleLogging": true,
|
||||
// Should logs be output to File
|
||||
"FileLogging": true,
|
||||
// Log File Path ({date} = creation Date, {time} = creation Time)
|
||||
"FileLogPath": "logs/{date}.log"
|
||||
},
|
||||
// Featured Git Repositories
|
||||
// must be Web links to an unauthenticated accessible repository.
|
||||
"GitRepos": [
|
||||
{
|
||||
"ServiceProvider": "Github",
|
||||
"Repository": "<Link to Repo>",
|
||||
"Description": [
|
||||
"line 1",
|
||||
"line 2"
|
||||
]
|
||||
}
|
||||
],
|
||||
|
||||
// Caching Options
|
||||
"Cache": {
|
||||
// Lifetimes given in minutes (0 = disabled)
|
||||
"GitRepo": 120, //2h
|
||||
"GitUser": 1440 //1d
|
||||
}
|
||||
// Social Links
|
||||
// must be a Name and Web link to an unauthenticated accessible websites.
|
||||
"Links": [
|
||||
{
|
||||
// Social Name (String)
|
||||
"Name": "Github",
|
||||
// Link to Socal (String)
|
||||
"Link": "https://github.com/KoromaruKoruko",
|
||||
// Icon File/Link (String|Null)
|
||||
"Icon": "https://github.githubassets.com/images/modules/logos_page/GitHub-Logo.png"
|
||||
}
|
||||
]
|
||||
}
|
||||
@ -1,6 +1,6 @@
|
||||
html {
|
||||
position: relative;
|
||||
background: url(/content/img/background.jpg);
|
||||
background: url(/img/background.jpg);
|
||||
background-size: cover;
|
||||
}
|
||||
|
||||
@ -70,7 +70,7 @@ main .text-center {
|
||||
}
|
||||
|
||||
.repo-container {
|
||||
/*border: solid 1px #444;*/
|
||||
border: solid 1px #444;
|
||||
border-radius: .25em;
|
||||
width: 81.5em;
|
||||
display: flex;
|
||||
|
||||
|
Before Width: | Height: | Size: 2.6 KiB After Width: | Height: | Size: 2.6 KiB |
|
Before Width: | Height: | Size: 2.3 KiB After Width: | Height: | Size: 2.3 KiB |
@ -8,7 +8,6 @@ using System.Threading.Tasks;
|
||||
|
||||
namespace GithubPlugin
|
||||
{
|
||||
[Serializable]
|
||||
public class GithubCommit : ICommit
|
||||
{
|
||||
[JsonIgnore]
|
||||
@ -20,16 +19,11 @@ namespace GithubPlugin
|
||||
[JsonIgnore]
|
||||
public IGitProvider? Provider { get; internal set; }
|
||||
|
||||
[JsonIgnore]
|
||||
public string? AuthorAvatarUrl { get; internal set; }
|
||||
|
||||
[JsonInclude]
|
||||
[JsonPropertyName("sha")]
|
||||
public string? Hash { get; init; }
|
||||
public string? Hash { get; private set; }
|
||||
|
||||
[JsonInclude]
|
||||
[JsonPropertyName("message")]
|
||||
public string? Description { get; init; }
|
||||
public string? Description { get; private set; }
|
||||
|
||||
public GitReference GetReference()
|
||||
{
|
||||
|
||||
@ -1,14 +1,10 @@
|
||||
using ExtensiblePortfolioSite.SDK;
|
||||
using ExtensiblePortfolioSite.SDK.Git;
|
||||
using ExtensiblePortfolioSite.SDK.Plugins;
|
||||
|
||||
using ExtensiblePortfolioSite.SDK.Git;
|
||||
using System.Text.Json;
|
||||
using System.Text.RegularExpressions;
|
||||
|
||||
namespace GithubPlugin
|
||||
{
|
||||
[GitProvider("Github")]
|
||||
[Serializable]
|
||||
[GitProvider("github.com")]
|
||||
public class GithubProvider : IGitProvider
|
||||
{
|
||||
private HttpClient httpClient = new HttpClient();
|
||||
@ -70,7 +66,7 @@ namespace GithubPlugin
|
||||
|
||||
public bool TryGetRepositoryByName(IUser User, string RepositoryName, out IRepository? Repository)
|
||||
{
|
||||
if(User is GithubUser _user)
|
||||
if(User.GetType() == typeof(GithubUser))
|
||||
{
|
||||
if (Repositories.TryGetValue(RepositoryName, out GithubRepo? _repo))
|
||||
{
|
||||
@ -78,15 +74,15 @@ namespace GithubPlugin
|
||||
return true;
|
||||
} else
|
||||
{
|
||||
var response = httpClient.GetAsync($"repos/{_user.Name}/{RepositoryName}").Result;
|
||||
var response = httpClient.GetAsync($"repos/{User.Name}/{RepositoryName}").Result;
|
||||
if (!response.IsSuccessStatusCode)
|
||||
{
|
||||
Repository = null;
|
||||
return false;
|
||||
}
|
||||
GithubRepo tempRepo = Json.Deserialize<GithubRepo>(response.Content.ReadAsStream())!;
|
||||
GithubRepo tempRepo = JsonSerializer.Deserialize<GithubRepo>(response.Content.ReadAsStream())!;
|
||||
tempRepo.Provider = this;
|
||||
tempRepo.Owner = _user;
|
||||
tempRepo.Owner = User;
|
||||
Repository = tempRepo;
|
||||
return true;
|
||||
}
|
||||
@ -109,7 +105,7 @@ namespace GithubPlugin
|
||||
User = null;
|
||||
return false;
|
||||
}
|
||||
GithubUser tempUser = Json.Deserialize<GithubUser>(response.Content.ReadAsStringAsync().Result)!;
|
||||
GithubUser tempUser = JsonSerializer.Deserialize<GithubUser>(response.Content.ReadAsStream())!;
|
||||
tempUser.Provider = this;
|
||||
User = tempUser;
|
||||
return true;
|
||||
@ -147,6 +143,5 @@ namespace GithubPlugin
|
||||
{
|
||||
return httpClient.GetAsync(resourceLocation).Result;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@ -1,41 +1,50 @@
|
||||
using ExtensiblePortfolioSite.SDK;
|
||||
using ExtensiblePortfolioSite.SDK.Git;
|
||||
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
|
||||
{
|
||||
[Serializable]
|
||||
public class GithubRepo : IRepository
|
||||
{
|
||||
[JsonInclude]
|
||||
[JsonPropertyName("name")]
|
||||
public string Name { get; init; }
|
||||
public string Name { get; private set; }
|
||||
|
||||
[JsonInclude]
|
||||
[JsonPropertyName("description")]
|
||||
public string Description { get; init; }
|
||||
|
||||
[JsonInclude]
|
||||
[JsonPropertyName("owner")]
|
||||
public GithubUser Owner { get; internal set; }
|
||||
public string Description { get; private set; }
|
||||
|
||||
[JsonIgnore]
|
||||
public GithubProvider Provider { get; internal set; }
|
||||
public IUser Owner { get; internal set; }
|
||||
|
||||
[JsonIgnore]
|
||||
IGitProvider IGitObject.Provider => this.Provider;
|
||||
|
||||
[JsonIgnore]
|
||||
IUser IRepository.Owner => this.Owner;
|
||||
public IGitProvider Provider { get; internal set; }
|
||||
|
||||
public ICommit? GetCommit(uint HeadOffset = 0)
|
||||
{
|
||||
var response = Provider.GetAPIResource($"repos/{Owner.Name}/{Name}/commits");
|
||||
if (response.IsSuccessStatusCode)
|
||||
return GetCommitByRef(JsonDocument.Parse(response.Content.ReadAsStream()).RootElement.EnumerateArray().Skip((int)HeadOffset).First().GetProperty("sha").GetString());
|
||||
else
|
||||
if(response.IsSuccessStatusCode)
|
||||
{
|
||||
JsonDocument json = JsonDocument.Parse(response.Content.ReadAsStream());
|
||||
JsonElement commitObject = json.RootElement.EnumerateArray().Skip((int)HeadOffset).First();
|
||||
GithubCommit commit = JsonSerializer.Deserialize<GithubCommit>(commitObject.GetProperty("commit"))!;
|
||||
commit.Provider = Provider;
|
||||
commit.Repository = this;
|
||||
List<string> tempFiles = new List<string>();
|
||||
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)
|
||||
@ -45,11 +54,10 @@ namespace GithubPlugin
|
||||
{
|
||||
JsonDocument json = JsonDocument.Parse(response.Content.ReadAsStream());
|
||||
JsonElement commitObject = json.RootElement;
|
||||
GithubCommit commit = Json.Deserialize<GithubCommit>(commitObject.GetProperty("commit"))!;
|
||||
commit.AuthorAvatarUrl = commitObject.GetProperty("author").GetProperty("avatar_url").GetString();
|
||||
GithubCommit commit = JsonSerializer.Deserialize<GithubCommit>(commitObject.GetProperty("commit"))!;
|
||||
commit.Provider = Provider;
|
||||
commit.Repository = this;
|
||||
List<string> tempFiles = new();
|
||||
List<string> tempFiles = new List<string>();
|
||||
JsonElement files = commitObject.GetProperty("files");
|
||||
foreach (JsonElement file in files.EnumerateArray())
|
||||
{
|
||||
@ -64,8 +72,10 @@ namespace GithubPlugin
|
||||
}
|
||||
}
|
||||
|
||||
public GitReference GetReference() => new(GitReferenceKind.Repository, $"{Owner}/{Name}");
|
||||
|
||||
public GitReference GetReference()
|
||||
{
|
||||
return new GitReference(GitReferenceKind.Repository, $"{Owner}/{Name}");
|
||||
}
|
||||
|
||||
public bool TryGetCommit(uint HeadOffset, out ICommit? Commit)
|
||||
{
|
||||
|
||||
@ -1,30 +1,27 @@
|
||||
using ExtensiblePortfolioSite.SDK;
|
||||
using ExtensiblePortfolioSite.SDK.Git;
|
||||
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
|
||||
{
|
||||
[Serializable]
|
||||
public class GithubUser : IUser
|
||||
{
|
||||
[JsonInclude]
|
||||
[JsonPropertyName("login")]
|
||||
public string Name { get; init; }
|
||||
public string Name { get; private set; }
|
||||
|
||||
[JsonInclude]
|
||||
[JsonPropertyName("id")]
|
||||
public int Identifier { get; init; }
|
||||
public int Identifier { get; private set; }
|
||||
|
||||
[JsonInclude]
|
||||
[JsonPropertyName("avatar_url")]
|
||||
public string AvatarURL { get; init; }
|
||||
public string AvatarURL { get; private set; }
|
||||
|
||||
[JsonIgnore]
|
||||
public GithubProvider Provider { get; internal set; }
|
||||
|
||||
[JsonIgnore]
|
||||
IGitProvider IGitObject.Provider => this.Provider;
|
||||
public IGitProvider Provider { get; internal set; }
|
||||
|
||||
public GitReference GetReference()
|
||||
{
|
||||
@ -36,13 +33,15 @@ namespace GithubPlugin
|
||||
var response = Provider.GetAPIResource($"users/{Name}/repos");
|
||||
if(response.IsSuccessStatusCode)
|
||||
{
|
||||
using Stream STM = response.Content.ReadAsStream();
|
||||
foreach (GithubRepo repo in Json.Deserialize<GithubRepo[]>(STM) ?? Array.Empty<GithubRepo>())
|
||||
JsonDocument json = JsonDocument.Parse(response.Content.ReadAsStream());
|
||||
List<IRepository> ret = new List<IRepository>(json.RootElement.GetArrayLength());
|
||||
foreach(JsonElement repo in json.RootElement.EnumerateArray())
|
||||
{
|
||||
repo.Provider = this.Provider;
|
||||
yield return repo;
|
||||
ret.Add(JsonSerializer.Deserialize<GithubRepo>(repo)!);
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
return new List<IRepository>();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Loading…
Reference in New Issue
Block a user