diff --git a/.config/dotnet-tools.json b/.config/dotnet-tools.json
index b29be29..7412df8 100644
--- a/.config/dotnet-tools.json
+++ b/.config/dotnet-tools.json
@@ -9,7 +9,7 @@
]
},
"dotnet-ef": {
- "version": "7.0.3",
+ "version": "7.0.10",
"commands": [
"dotnet-ef"
]
diff --git a/PiwigoDirectorySync.sln.DotSettings b/PiwigoDirectorySync.sln.DotSettings
index fe19aae..f2694d2 100644
--- a/PiwigoDirectorySync.sln.DotSettings
+++ b/PiwigoDirectorySync.sln.DotSettings
@@ -72,6 +72,7 @@
True
True
True
+ True
diff --git a/PiwigoDirectorySync/AppSettings.cs b/PiwigoDirectorySync/AppSettings.cs
new file mode 100644
index 0000000..130b9b3
--- /dev/null
+++ b/PiwigoDirectorySync/AppSettings.cs
@@ -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() ?? 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}");
+}
\ No newline at end of file
diff --git a/PiwigoDirectorySync/Commands/ScanCommand.cs b/PiwigoDirectorySync/Commands/ScanCommand.cs
new file mode 100644
index 0000000..d0865e8
--- /dev/null
+++ b/PiwigoDirectorySync/Commands/ScanCommand.cs
@@ -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
+{
+ private static readonly ILogger Logger = LogManager.GetCurrentClassLogger();
+
+ public override async Task ExecuteAsync(CommandContext context, ScanSettings settings)
+ {
+ Logger.Info("Starting scanner and remover");
+ var stopWatch = Stopwatch.StartNew();
+
+ var cancellationTokenSource = new CancellationTokenSource();
+
+ var fileQueue = Channel.CreateUnbounded();
+
+ 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;
+ }
+}
\ No newline at end of file
diff --git a/PiwigoDirectorySync/Commands/ScanSettings.cs b/PiwigoDirectorySync/Commands/ScanSettings.cs
new file mode 100644
index 0000000..bf7c7e0
--- /dev/null
+++ b/PiwigoDirectorySync/Commands/ScanSettings.cs
@@ -0,0 +1,7 @@
+using Spectre.Console.Cli;
+
+namespace PiwigoDirectorySync.Commands;
+
+public class ScanSettings : CommandSettings
+{
+}
\ No newline at end of file
diff --git a/PiwigoDirectorySync/Persistence/AlbumEntity.cs b/PiwigoDirectorySync/Persistence/AlbumEntity.cs
new file mode 100644
index 0000000..28ceb69
--- /dev/null
+++ b/PiwigoDirectorySync/Persistence/AlbumEntity.cs
@@ -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; }
+}
\ No newline at end of file
diff --git a/PiwigoDirectorySync/Persistence/ImageEntity.cs b/PiwigoDirectorySync/Persistence/ImageEntity.cs
new file mode 100644
index 0000000..201ae8b
--- /dev/null
+++ b/PiwigoDirectorySync/Persistence/ImageEntity.cs
@@ -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; }
+}
\ No newline at end of file
diff --git a/PiwigoDirectorySync/Persistence/PersistenceContext.cs b/PiwigoDirectorySync/Persistence/PersistenceContext.cs
new file mode 100644
index 0000000..58672c8
--- /dev/null
+++ b/PiwigoDirectorySync/Persistence/PersistenceContext.cs
@@ -0,0 +1,25 @@
+using Microsoft.EntityFrameworkCore;
+
+namespace PiwigoDirectorySync.Persistence;
+
+public class PersistenceContext : DbContext
+{
+ public DbSet PiwigoServers { get; set; }
+ public DbSet PiwigoAlbums { get; set; }
+ public DbSet 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");
+ }
+ }
+}
\ No newline at end of file
diff --git a/PiwigoDirectorySync/Persistence/PiwigoServerEntity.cs b/PiwigoDirectorySync/Persistence/PiwigoServerEntity.cs
new file mode 100644
index 0000000..c51668e
--- /dev/null
+++ b/PiwigoDirectorySync/Persistence/PiwigoServerEntity.cs
@@ -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; }
+}
\ No newline at end of file
diff --git a/PiwigoDirectorySync/PiwigoDirectorySync.csproj b/PiwigoDirectorySync/PiwigoDirectorySync.csproj
index 0619d80..0968c8f 100644
--- a/PiwigoDirectorySync/PiwigoDirectorySync.csproj
+++ b/PiwigoDirectorySync/PiwigoDirectorySync.csproj
@@ -1,17 +1,46 @@
-
+
Exe
net7.0
enable
enable
+ false
+ true
Linux
+ c68c0447-8c7d-4e88-bcc6-96a9853828c7
-
- .dockerignore
-
+
+
+
+
+
+
+ all
+ runtime; build; native; contentfiles; analyzers; buildtransitive
+
+
+
+ all
+ runtime; build; native; contentfiles; analyzers; buildtransitive
+
+
+
+
+
+ .dockerignore
+
+
+
+
+
+ Always
+
+
+
+
diff --git a/PiwigoDirectorySync/Program.cs b/PiwigoDirectorySync/Program.cs
index 2d6e81e..f62bf05 100644
--- a/PiwigoDirectorySync/Program.cs
+++ b/PiwigoDirectorySync/Program.cs
@@ -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!");
\ No newline at end of file
+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("scan"); });
+
+return app.Run(args);
\ No newline at end of file
diff --git a/PiwigoDirectorySync/Service/FileIndexer.cs b/PiwigoDirectorySync/Service/FileIndexer.cs
new file mode 100644
index 0000000..37af38d
--- /dev/null
+++ b/PiwigoDirectorySync/Service/FileIndexer.cs
@@ -0,0 +1,17 @@
+using System.Threading.Channels;
+
+namespace PiwigoDirectorySync.Service;
+
+public class FileIndexer
+{
+ private readonly Channel _fileQueue;
+
+ public FileIndexer(Channel fileQueue)
+ {
+ _fileQueue = fileQueue ?? throw new ArgumentNullException(nameof(fileQueue));
+ }
+
+ public int TotalFilesScanned { get; }
+
+ public Task StartProcessingAsync(CancellationToken token) => throw new NotImplementedException();
+}
\ No newline at end of file
diff --git a/PiwigoDirectorySync/Service/FileScanner.cs b/PiwigoDirectorySync/Service/FileScanner.cs
new file mode 100644
index 0000000..80bc9dd
--- /dev/null
+++ b/PiwigoDirectorySync/Service/FileScanner.cs
@@ -0,0 +1,15 @@
+using System.Threading.Channels;
+
+namespace PiwigoDirectorySync.Service;
+
+public class FileScanner
+{
+ private readonly Channel _fileQueue;
+
+ public FileScanner(Channel fileQueue)
+ {
+ _fileQueue = fileQueue ?? throw new ArgumentNullException(nameof(fileQueue));
+ }
+
+ public async Task ScanAsync(CancellationToken token) => throw new NotImplementedException();
+}
\ No newline at end of file
diff --git a/PiwigoDirectorySync/Settings.cs b/PiwigoDirectorySync/Settings.cs
new file mode 100644
index 0000000..2134014
--- /dev/null
+++ b/PiwigoDirectorySync/Settings.cs
@@ -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);
+}
\ No newline at end of file
diff --git a/PiwigoDirectorySync/addMigration.ps1 b/PiwigoDirectorySync/addMigration.ps1
new file mode 100644
index 0000000..5fd66a3
--- /dev/null
+++ b/PiwigoDirectorySync/addMigration.ps1
@@ -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
diff --git a/PiwigoDirectorySync/appsettings.json b/PiwigoDirectorySync/appsettings.json
new file mode 100644
index 0000000..3393ae2
--- /dev/null
+++ b/PiwigoDirectorySync/appsettings.json
@@ -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"
+ }
+ ]
+ }
+}
\ No newline at end of file
diff --git a/README.md b/README.md
index f8d21e1..b620a34 100644
--- a/README.md
+++ b/README.md
@@ -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
\ No newline at end of file