fixup! fixup! fixup! fixup! fixup! fixup! fixup! fixup! fixup! fixup! Add initial Blazor Server project

recyclarr
Robert Dailey 3 years ago
parent 085d641e7e
commit f794e5f652

@ -0,0 +1,32 @@
using System.Collections.Generic;
using System.Linq;
using Microsoft.EntityFrameworkCore;
using TrashLib.Cache;
using TrashLib.Config;
using TrashLib.Radarr.CustomFormat.Cache;
namespace Recyclarr.Code.Database
{
internal class DatabaseServiceCache : IServiceCache
{
private readonly IDbContextFactory<DatabaseContext> _contextFactory;
public DatabaseServiceCache(IDbContextFactory<DatabaseContext> contextFactory)
{
_contextFactory = contextFactory;
}
public IEnumerable<T> Load<T>(IServiceConfiguration config) where T : ServiceCacheObject
{
var context = _contextFactory.CreateDbContext();
return context.Set<T>()
.Where(r => r.ServiceBaseUrl == config.BaseUrl);
}
public void Save<T>(IEnumerable<T> objList, IServiceConfiguration config) where T : ServiceCacheObject
{
var context = _contextFactory.CreateDbContext();
context.SaveChanges();
}
}
}

@ -1,6 +1,12 @@
using System.IO.Abstractions;
using Autofac;
using Blazored.LocalStorage;
using BlazorPro.BlazorSize;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using MudBlazor.Services;
using Recyclarr.Code.Database;
using Recyclarr.Code.Radarr;
using Recyclarr.Code.Settings;
using Recyclarr.Code.Settings.Persisters;
@ -13,8 +19,27 @@ namespace Recyclarr
{
internal static class CompositionRoot
{
public static void Build(IServiceCollection services, IConfiguration configuration)
{
services.AddRazorPages();
services.AddServerSideBlazor();
services.AddMediaQueryService();
services.AddMudServices();
services.AddBlazoredLocalStorage();
services.AddDatabaseDeveloperPageExceptionFilter();
// EFCore DB Context Factory Registrations
services.AddDbContextFactory<DatabaseContext>(options =>
options.UseSqlite(configuration.GetConnectionString("DefaultConnection")));
}
public static void Build(ContainerBuilder builder)
{
// EF Core
// builder.RegisterGeneric(typeof(DbContextFactory<>))
// .As(typeof(IDbContextFactory<>))
// .SingleInstance();
builder.Register(_ => new LoggerConfiguration().MinimumLevel.Debug().CreateLogger())
.As<ILogger>()
.SingleInstance();
@ -35,7 +60,7 @@ namespace Recyclarr
builder.RegisterModule<RadarrAutofacModule>();
builder.RegisterType<GuideProcessor>().As<IGuideProcessor>();
builder.RegisterType<CustomFormatRepository>()
builder.RegisterType<DatabaseContext>()
.InstancePerLifetimeScope();
builder.RegisterType<ConfigPersister<RadarrConfig>>()

@ -1,6 +1,8 @@
using Autofac;
using Autofac.Extensions.DependencyInjection;
using Microsoft.AspNetCore.Hosting;
using Microsoft.Extensions.Hosting;
using Recyclarr.Code.Database;
namespace Recyclarr
{
@ -8,9 +10,15 @@ namespace Recyclarr
{
public static void Main(string[] args)
{
CreateHostBuilder(args)
var host = CreateHostBuilder(args)
.UseServiceProviderFactory(new AutofacServiceProviderFactory())
.Build().Run();
.Build();
var container = host.Services.GetAutofacRoot();
var dbInitializer = container.Resolve<DatabaseInitializer>();
dbInitializer.Initialize();
host.Run();
}
private static IHostBuilder CreateHostBuilder(string[] args) =>

@ -9,6 +9,8 @@
<PackageReference Include="Microsoft.EntityFrameworkCore" Version="5.*" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Design" Version="5.*" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" Version="5.*" />
<PackageReference Include="Microsoft.AspNetCore.Diagnostics.EntityFrameworkCore" Version="5.*" />
<PackageReference Include="Microsoft.Extensions.Configuration" Version="5.*" />
</ItemGroup>
<ItemGroup>

@ -1,12 +1,9 @@
using Autofac;
using Blazored.LocalStorage;
using BlazorPro.BlazorSize;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using MudBlazor.Services;
namespace Recyclarr
{
@ -22,16 +19,11 @@ namespace Recyclarr
// This method gets called by the runtime. Use this method to add services to the container.
// For more information on how to configure your application, visit https://go.microsoft.com/fwlink/?LinkID=398940
public void ConfigureServices(IServiceCollection services)
{
services.AddRazorPages();
services.AddServerSideBlazor();
services.AddMediaQueryService();
services.AddMudServices();
services.AddBlazoredLocalStorage();
}
public void ConfigureServices(IServiceCollection services) =>
CompositionRoot.Build(services, Configuration);
public void ConfigureContainer(ContainerBuilder builder) => CompositionRoot.Build(builder);
public void ConfigureContainer(ContainerBuilder builder) =>
CompositionRoot.Build(builder);
// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)

@ -11,7 +11,7 @@ namespace TrashLib.Cache
builder.RegisterModule<ConfigAutofacModule>();
builder.RegisterType<CacheGuidBuilder>().As<ICacheGuidBuilder>();
builder.RegisterType<ServiceCache>().As<IServiceCache>();
builder.RegisterType<FilesystemServiceCache>().As<IServiceCache>();
}
}
}

@ -6,19 +6,17 @@ namespace TrashLib.Cache
{
internal class CacheGuidBuilder : ICacheGuidBuilder
{
private readonly string _baseUrl;
private readonly IFNV1a _hash;
public CacheGuidBuilder(IServiceConfiguration config)
public CacheGuidBuilder()
{
_baseUrl = config.BaseUrl;
_hash = FNV1aFactory.Instance.Create(FNVConfig.GetPredefinedConfig(32));
}
public string MakeGuid()
public string MakeGuid(IServiceConfiguration config)
{
return _hash
.ComputeHash(Encoding.ASCII.GetBytes(_baseUrl))
.ComputeHash(Encoding.ASCII.GetBytes(config.BaseUrl))
.AsHexString();
}
}

@ -1,15 +0,0 @@
using System;
namespace TrashLib.Cache
{
[AttributeUsage(AttributeTargets.Class)]
internal sealed class CacheObjectNameAttribute : Attribute
{
public CacheObjectNameAttribute(string name)
{
Name = name;
}
public string Name { get; }
}
}

@ -0,0 +1,79 @@
using System.Collections.Generic;
using System.IO;
using System.IO.Abstractions;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using Newtonsoft.Json.Serialization;
using Serilog;
using TrashLib.Config;
namespace TrashLib.Cache
{
internal class FilesystemServiceCache : IServiceCache
{
private readonly IFileSystem _fileSystem;
private readonly ICacheStoragePath _storagePath;
private readonly ICacheGuidBuilder _guidBuilder;
public FilesystemServiceCache(
IFileSystem fileSystem,
ICacheStoragePath storagePath,
ILogger log,
ICacheGuidBuilder guidBuilder)
{
_fileSystem = fileSystem;
_storagePath = storagePath;
_guidBuilder = guidBuilder;
Log = log;
}
private ILogger Log { get; }
public IEnumerable<T>? Load<T>(IServiceConfiguration config) where T : class
{
var path = PathFromAttribute<T>(config);
if (!_fileSystem.File.Exists(path))
{
return null;
}
var json = _fileSystem.File.ReadAllText(path);
try
{
return JObject.Parse(json).ToObject<IEnumerable<T>>();
}
catch (JsonException e)
{
Log.Error("Failed to read cache data, will proceed without cache. Reason: {Msg}", e.Message);
}
return null;
}
public void Save<T>(IEnumerable<T> objList, IServiceConfiguration config) where T : class
{
var path = PathFromAttribute<T>(config);
_fileSystem.Directory.CreateDirectory(Path.GetDirectoryName(path));
_fileSystem.File.WriteAllText(path, JsonConvert.SerializeObject(objList, new JsonSerializerSettings
{
Formatting = Formatting.Indented,
ContractResolver = new DefaultContractResolver
{
NamingStrategy = new SnakeCaseNamingStrategy()
}
}));
}
private string PathFromAttribute<T>(IServiceConfiguration config)
{
var objectName = GetCacheObjectName<T>();
return Path.Combine(_storagePath.Path, _guidBuilder.MakeGuid(config), objectName + ".json");
}
private static string GetCacheObjectName<T>()
{
return typeof(T).Name;
}
}
}

@ -1,7 +1,9 @@
namespace TrashLib.Cache
using TrashLib.Config;
namespace TrashLib.Cache
{
public interface ICacheGuidBuilder
{
string MakeGuid();
string MakeGuid(IServiceConfiguration config);
}
}

@ -1,8 +1,13 @@
namespace TrashLib.Cache
using System.Collections.Generic;
using TrashLib.Config;
using TrashLib.Radarr.CustomFormat.Cache;
using TrashLib.Radarr.CustomFormat.Models.Cache;
namespace TrashLib.Cache
{
public interface IServiceCache
{
T? Load<T>(ICacheGuidBuilder guidBuilder) where T : class;
void Save<T>(T obj, ICacheGuidBuilder guidBuilder) where T : class;
IEnumerable<T> Load<T>(IServiceConfiguration config) where T : ServiceCacheObject;
void Save<T>(IEnumerable<T> objList, IServiceConfiguration config) where T : ServiceCacheObject;
}
}

@ -1,89 +0,0 @@
using System;
using System.IO;
using System.IO.Abstractions;
using System.Reflection;
using System.Text.RegularExpressions;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using Newtonsoft.Json.Serialization;
using Serilog;
namespace TrashLib.Cache
{
internal class ServiceCache : IServiceCache
{
private static readonly Regex AllowedObjectNameCharacters = new(@"^[\w-]+$", RegexOptions.Compiled);
private readonly IFileSystem _fileSystem;
private readonly ICacheStoragePath _storagePath;
public ServiceCache(
IFileSystem fileSystem,
ICacheStoragePath storagePath,
ILogger log)
{
_fileSystem = fileSystem;
_storagePath = storagePath;
Log = log;
}
private ILogger Log { get; }
public T? Load<T>(ICacheGuidBuilder guidBuilder) where T : class
{
var path = PathFromAttribute<T>(guidBuilder);
if (!_fileSystem.File.Exists(path))
{
return null;
}
var json = _fileSystem.File.ReadAllText(path);
try
{
return JObject.Parse(json).ToObject<T>();
}
catch (JsonException e)
{
Log.Error("Failed to read cache data, will proceed without cache. Reason: {Msg}", e.Message);
}
return null;
}
public void Save<T>(T obj, ICacheGuidBuilder guidBuilder) where T : class
{
var path = PathFromAttribute<T>(guidBuilder);
_fileSystem.Directory.CreateDirectory(Path.GetDirectoryName(path));
_fileSystem.File.WriteAllText(path, JsonConvert.SerializeObject(obj, new JsonSerializerSettings
{
Formatting = Formatting.Indented,
ContractResolver = new DefaultContractResolver
{
NamingStrategy = new SnakeCaseNamingStrategy()
}
}));
}
private static string GetCacheObjectNameAttribute<T>()
{
var attribute = typeof(T).GetCustomAttribute<CacheObjectNameAttribute>();
if (attribute == null)
{
throw new ArgumentException($"{nameof(CacheObjectNameAttribute)} is missing on type {nameof(T)}");
}
return attribute.Name;
}
private string PathFromAttribute<T>(ICacheGuidBuilder guidBuilder)
{
var objectName = GetCacheObjectNameAttribute<T>();
if (!AllowedObjectNameCharacters.IsMatch(objectName))
{
throw new ArgumentException($"Object name '{objectName}' has unacceptable characters");
}
return Path.Combine(_storagePath.Path, guidBuilder.MakeGuid(), objectName + ".json");
}
}
}

@ -2,6 +2,8 @@ using System.Collections.Generic;
using System.Linq;
using Serilog;
using TrashLib.Cache;
using TrashLib.Config;
using TrashLib.Radarr.Config;
using TrashLib.Radarr.CustomFormat.Models;
using TrashLib.Radarr.CustomFormat.Models.Cache;
@ -12,25 +14,19 @@ namespace TrashLib.Radarr.CustomFormat.Cache
private readonly IServiceCache _cache;
private readonly ICacheGuidBuilder _guidBuilder;
public CachePersister(ILogger log, IServiceCache cache, ICacheGuidBuilder guidBuilder)
public CachePersister(ILogger log, IServiceCache cache)
{
Log = log;
_cache = cache;
_guidBuilder = guidBuilder;
}
private ILogger Log { get; }
public CustomFormatCache? CfCache { get; private set; }
public List<TrashIdMapping> CfCache { get; private set; }
public void Load()
public void Load(IServiceConfiguration config)
{
CfCache = _cache.Load<CustomFormatCache>(_guidBuilder);
if (CfCache == null)
{
Log.Debug("Custom format cache does not exist; proceeding without it");
return;
}
CfCache = _cache.Load<TrashIdMapping>(config).ToList();
Log.Debug("Loaded Cache");

@ -1,25 +0,0 @@
using System;
using TrashLib.Cache;
using TrashLib.Config;
namespace TrashLib.Radarr.CustomFormat.Cache
{
internal class CachePersisterFactory : ICachePersisterFactory
{
private readonly Func<IServiceConfiguration, ICacheGuidBuilder> _guidBuilderFactory;
private readonly Func<ICacheGuidBuilder, ICachePersister> _persisterFactory;
public CachePersisterFactory(
Func<IServiceConfiguration, ICacheGuidBuilder> guidBuilderFactory,
Func<ICacheGuidBuilder, ICachePersister> persisterFactory)
{
_guidBuilderFactory = guidBuilderFactory;
_persisterFactory = persisterFactory;
}
public ICachePersister Create(IServiceConfiguration config)
{
return _persisterFactory(_guidBuilderFactory(config));
}
}
}

@ -6,7 +6,7 @@ namespace TrashLib.Radarr.CustomFormat.Cache
{
public interface ICachePersister
{
CustomFormatCache? CfCache { get; }
List<TrashIdMapping> CfCache { get; }
void Load();
void Save();
void Update(IEnumerable<ProcessedCustomFormatData> customFormats);

@ -0,0 +1,8 @@
namespace TrashLib.Radarr.CustomFormat.Cache
{
public abstract class ServiceCacheObject
{
public int Id { get; set; }
public string ServiceBaseUrl { get; set; } = default!;
}
}

@ -1,26 +0,0 @@
using System.Collections.Generic;
using TrashLib.Cache;
namespace TrashLib.Radarr.CustomFormat.Models.Cache
{
[CacheObjectName("custom-format-cache")]
public class CustomFormatCache
{
public const int LatestVersion = 1;
public int Version { get; init; } = LatestVersion;
public List<TrashIdMapping> TrashIdMappings { get; init; } = new();
}
public class TrashIdMapping
{
public TrashIdMapping(string trashId, int customFormatId)
{
TrashId = trashId;
CustomFormatId = customFormatId;
}
public string TrashId { get; }
public int CustomFormatId { get; }
}
}

@ -0,0 +1,10 @@
using TrashLib.Radarr.CustomFormat.Cache;
namespace TrashLib.Radarr.CustomFormat.Models.Cache
{
public class TrashIdMapping : ServiceCacheObject
{
public string TrashId { get; set; } = default!;
public int CustomFormatId { get; set; }
}
}
Loading…
Cancel
Save