adds application infra and first draft of persistence entities

This commit is contained in:
Philipp Häfelfinger 2023-08-29 23:54:00 +02:00
parent 18124da30f
commit 28f4ec09f2
17 changed files with 344 additions and 7 deletions

View File

@ -9,7 +9,7 @@
]
},
"dotnet-ef": {
"version": "7.0.3",
"version": "7.0.10",
"commands": [
"dotnet-ef"
]

View File

@ -72,6 +72,7 @@
<s:Boolean x:Key="/Default/Environment/SettingsMigration/IsMigratorApplied/=JetBrains_002EReSharper_002EPsi_002ECSharp_002ECodeStyle_002ECSharpPlaceEmbeddedOnSameLineMigration/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/Environment/SettingsMigration/IsMigratorApplied/=JetBrains_002EReSharper_002EPsi_002ECSharp_002ECodeStyle_002ECSharpUseContinuousIndentInsideBracesMigration/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/Environment/SettingsMigration/IsMigratorApplied/=JetBrains_002EReSharper_002EPsi_002ECSharp_002ECodeStyle_002ESettingsUpgrade_002EMigrateBlankLinesAroundFieldToBlankLinesAroundProperty/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/UserDictionary/Words/=appsettings/@EntryIndexedValue">True</s:Boolean>

View File

@ -0,0 +1,17 @@
using Microsoft.Extensions.Configuration;
namespace PiwigoDirectorySync;
public static class AppSettings
{
public static IConfigurationRoot Config { get; } = new ConfigurationBuilder().SetBasePath(AppContext.BaseDirectory)
.AddJsonFile("appsettings.json", true)
.AddJsonFile(Path.Combine(Environment.CurrentDirectory, "appsettings.json"), true)
.AddJsonFile("/etc/PiwigoDirectorySync/appsettings.json", true)
.AddEnvironmentVariables()
.Build();
public static Settings Settings { get; } = Config.GetSection("settings").Get<Settings>() ?? throw new InvalidOperationException("Could not parse settings");
public static string ConnectionString => Config.GetConnectionString(Settings.DbProvider) ?? throw new InvalidOperationException($"Could not find connection string for provider {Settings.DbProvider}");
}

View File

@ -0,0 +1,37 @@
using System.Diagnostics;
using System.Threading.Channels;
using NLog;
using PiwigoDirectorySync.Service;
using Spectre.Console.Cli;
namespace PiwigoDirectorySync.Commands;
public class ScanCommand : AsyncCommand<ScanSettings>
{
private static readonly ILogger Logger = LogManager.GetCurrentClassLogger();
public override async Task<int> ExecuteAsync(CommandContext context, ScanSettings settings)
{
Logger.Info("Starting scanner and remover");
var stopWatch = Stopwatch.StartNew();
var cancellationTokenSource = new CancellationTokenSource();
var fileQueue = Channel.CreateUnbounded<string>();
var indexer = new FileIndexer(fileQueue);
var indexerTask = indexer.StartProcessingAsync(cancellationTokenSource.Token);
var scanner = new FileScanner(fileQueue);
await scanner.ScanAsync(cancellationTokenSource.Token);
fileQueue.Writer.Complete();
await Task.WhenAll(fileQueue.Reader.Completion, indexerTask);
stopWatch.Stop();
Logger.Info($"Processed {indexer.TotalFilesScanned} image files in {stopWatch.Elapsed.TotalSeconds} seconds");
return 0;
}
}

View File

@ -0,0 +1,7 @@
using Spectre.Console.Cli;
namespace PiwigoDirectorySync.Commands;
public class ScanSettings : CommandSettings
{
}

View File

@ -0,0 +1,22 @@
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;
using Microsoft.EntityFrameworkCore;
namespace PiwigoDirectorySync.Persistence;
public class AlbumEntity
{
[Key]
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public int Id { get; set; }
public int? ParentId { get; set; }
public AlbumEntity? Parent { get; set; }
public required int ServerId { get; set; }
public PiwigoServerEntity Server { get; set; } = null!;
public required string Name { get; set; }
public required string DirectoryName { get; set; }
public string FullDirectory => Parent is not null ? $"{Parent.FullDirectory}{Path.DirectorySeparatorChar}{DirectoryName}" : DirectoryName;
public int? PiwigoAlbumId { get; set; }
}

View File

@ -0,0 +1,20 @@
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;
using Microsoft.EntityFrameworkCore;
namespace PiwigoDirectorySync.Persistence;
public class ImageEntity
{
[Key]
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public int Id { get; set; }
public required string Filename { get; set; }
public DateTime LastChange { get; set; }
public string? Md5Sum { get; set; }
public required int AlbumId { get; set; }
public AlbumEntity Album { get; set; } = null!;
public int ServerImageId { get; set; }
public bool UploadRequired { get; set; }
public bool DeleteRequired { get; set; }
}

View File

@ -0,0 +1,25 @@
using Microsoft.EntityFrameworkCore;
namespace PiwigoDirectorySync.Persistence;
public class PersistenceContext : DbContext
{
public DbSet<PiwigoServerEntity> PiwigoServers { get; set; }
public DbSet<AlbumEntity> PiwigoAlbums { get; set; }
public DbSet<ImageEntity> PiwigoImages { get; set; }
protected override void OnConfiguring(DbContextOptionsBuilder options)
{
switch (AppSettings.Settings.DbProvider)
{
case "Sqlite":
options.UseSqlite(AppSettings.ConnectionString);
break;
case "InMemory":
options.UseInMemoryDatabase(AppSettings.ConnectionString);
break;
default:
throw new InvalidOperationException($"DbProvider {AppSettings.Settings.DbProvider} is not supported");
}
}
}

View File

@ -0,0 +1,19 @@
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;
using Microsoft.EntityFrameworkCore;
namespace PiwigoDirectorySync.Persistence;
[Index(nameof(Name), IsUnique = true)]
public class PiwigoServerEntity
{
[Key]
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public int Id { get; set; }
public required string Name { get; set; }
public required string Url { get; set; }
public required string Username { get; set; }
public required string Password { get; set; }
public required string RootDirectory { get; set; }
}

View File

@ -1,17 +1,46 @@
<Project Sdk="Microsoft.NET.Sdk">
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net7.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
<PublishTrimmed>false</PublishTrimmed>
<PublishSingleFile>true</PublishSingleFile>
<DockerDefaultTargetOS>Linux</DockerDefaultTargetOS>
<UserSecretsId>c68c0447-8c7d-4e88-bcc6-96a9853828c7</UserSecretsId>
</PropertyGroup>
<ItemGroup>
<Content Include="..\.dockerignore">
<Link>.dockerignore</Link>
</Content>
<PackageReference Include="Piwigo.Client" Version="0.1.0.17"/>
<PackageReference Include="Microsoft.Extensions.Configuration.Binder" Version="7.0.0"/>
<PackageReference Include="Microsoft.Extensions.Configuration.EnvironmentVariables" Version="7.0.0"/>
<PackageReference Include="Microsoft.Extensions.Configuration.Json" Version="7.0.0"/>
<PackageReference Include="NLog.Extensions.Logging" Version="5.2.0"/>
<PackageReference Include="Spectre.Console.Analyzer" Version="0.47.0">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
<PackageReference Include="Spectre.Console.Cli" Version="0.47.0"/>
<PackageReference Include="Microsoft.EntityFrameworkCore.Design" Version="7.0.10">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
<PackageReference Include="Microsoft.EntityFrameworkCore.InMemory" Version="7.0.10"/>
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" Version="7.0.10"/>
</ItemGroup>
<ItemGroup>
<Content Include="..\.dockerignore">
<Link>.dockerignore</Link>
</Content>
</ItemGroup>
<ItemGroup>
<None Update="appsettings.json">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</None>
</ItemGroup>
</Project>

View File

@ -1,3 +1,15 @@
// See https://aka.ms/new-console-template for more information
using NLog;
using NLog.Extensions.Logging;
using PiwigoDirectorySync;
using PiwigoDirectorySync.Commands;
using Spectre.Console.Cli;
Console.WriteLine("Hello, World!");
LogManager.Configuration = new NLogLoggingConfiguration(AppSettings.Config.GetSection("NLog"));
var logFactory = LogManager.Setup().LogFactory;
var logger = logFactory.GetCurrentClassLogger();
logger.Info("Starting command app");
var app = new CommandApp();
app.Configure(c => { c.AddCommand<ScanCommand>("scan"); });
return app.Run(args);

View File

@ -0,0 +1,17 @@
using System.Threading.Channels;
namespace PiwigoDirectorySync.Service;
public class FileIndexer
{
private readonly Channel<string> _fileQueue;
public FileIndexer(Channel<string> fileQueue)
{
_fileQueue = fileQueue ?? throw new ArgumentNullException(nameof(fileQueue));
}
public int TotalFilesScanned { get; }
public Task StartProcessingAsync(CancellationToken token) => throw new NotImplementedException();
}

View File

@ -0,0 +1,15 @@
using System.Threading.Channels;
namespace PiwigoDirectorySync.Service;
public class FileScanner
{
private readonly Channel<string> _fileQueue;
public FileScanner(Channel<string> fileQueue)
{
_fileQueue = fileQueue ?? throw new ArgumentNullException(nameof(fileQueue));
}
public async Task ScanAsync(CancellationToken token) => throw new NotImplementedException();
}

View File

@ -0,0 +1,9 @@
namespace PiwigoDirectorySync;
public class Settings
{
public string DbProvider { get; set; } = null!;
public string ImageRootDirectory { get; set; } = null!;
public bool HasErrors => string.IsNullOrEmpty(DbProvider) || string.IsNullOrEmpty(ImageRootDirectory);
}

View File

@ -0,0 +1,5 @@
$comment=$args[0]
write-host "adding migration for Sqlite"
dotnet ef migrations add --project PiwigoDirectorySync.csproj --startup-project PiwigoDirectorySync.csproj --context PiwigoDirectorySync.Persistence.PersistenceContext "$comment" --output-dir Migrations -- --DbProvider Sqlite
#dotnet ef migrations add --project PiwigoDirectorySync/PiwigoDirectorySync.csproj --startup-project PiwigoDirectorySync/PiwigoDirectorySync.csproj --context PiwigoDirectorySync.Persistence.Persistence.PersistenceContext "$comment" --output-dir Migrations -- --DbProvider Sqlite

View File

@ -0,0 +1,68 @@
{
"Logging": {
"LogLevel": {
"Default": "Debug",
"System": "Information",
"Microsoft": "Information"
}
},
"ConnectionStrings": {
"Sqlite": "Data Source=piwigoSync.db",
"InMemory": "InMemorySyncDb"
},
"Settings": {
"DbProvider": "Sqlite",
"ImageRootDirectory": ".\\"
},
"NLog": {
"autoReload": true,
"throwConfigExceptions": true,
"default-wrapper": {
"type": "AsyncWrapper",
"overflowAction": "Block"
},
"targets": {
"cli-console": {
"type": "ColoredConsole",
"layout": "${longdate} | ${uppercase:${level}} | ${logger} | ${message} ${exception:format=tostring}",
"rowHighlightingRules": [
{
"condition": "level == LogLevel.Trace",
"foregroundColor": "DarkGray"
},
{
"condition": "level == LogLevel.Debug",
"foregroundColor": "White"
},
{
"condition": "level == LogLevel.Info",
"foregroundColor": "DarkGreen"
},
{
"condition": "level == LogLevel.Warn",
"foregroundColor": "Yellow"
},
{
"condition": "level == LogLevel.Error",
"foregroundColor": "DarkMagenta"
},
{
"condition": "level == LogLevel.Fatal",
"foregroundColor": "DarkRed"
}
]
}
},
"rules": [
{
"logger": "*",
"minLevel": "Info",
"writeTo": "cli-console"
},
{
"logger": "Microsoft.*",
"maxLevel": "Info"
}
]
}
}

View File

@ -1,2 +1,36 @@
# PiwigoDirectorySync
This application synchronizes the local directory structure with piwigo servers.
Each directory level gets created as album or sub album in piwigo.
## Building / tooling
### Restoring dotnet tools
In the root path you may just run the command ``dotnet tool restore`` to install all dotnet tools and extensions used in this project.
### Handle development secrets
To make sure no local development db configuration gets committed, we use the dotnet-user-secrets tool.
When you configure your environment for the first time and you do not like the default settings, use the following commands to set the secrets:
``
cd PiwigoDirectorySync
dotnet user-secrets set "DbProvider" "MariaDb"
dotnet user-secrets set "ConnectionStrings:MariaDb" "Server=localhost;User Id=photowfdev;Password=password123;Database=photowfdev"
``
You'll find your secrets under `~/.microsoft/usersecrets/c68c0447-8c7d-4e88-bcc6-96a9853828c7/secrets.json`
### Docker
Build the application and docker image for hosting using docker.
TODO: add some docker build details
### Publish
Build the application for manual installation using publish.
TODO: add some publish details