changes to dependency injection and microsoft logging, removes nlog, adds first version of album sync form server
This commit is contained in:
parent
8f7496a3df
commit
c06e8763a4
@ -1,6 +1,6 @@
|
|||||||
using System.Diagnostics;
|
using System.Diagnostics;
|
||||||
using System.Threading.Channels;
|
using System.Threading.Channels;
|
||||||
using NLog;
|
using Microsoft.Extensions.Logging;
|
||||||
using PiwigoDirectorySync.Services;
|
using PiwigoDirectorySync.Services;
|
||||||
using Spectre.Console.Cli;
|
using Spectre.Console.Cli;
|
||||||
|
|
||||||
@ -8,31 +8,40 @@ namespace PiwigoDirectorySync.Commands;
|
|||||||
|
|
||||||
public class ScanCommand : AsyncCommand<ScanSettings>
|
public class ScanCommand : AsyncCommand<ScanSettings>
|
||||||
{
|
{
|
||||||
private static readonly ILogger Logger = LogManager.GetCurrentClassLogger();
|
private readonly IFileIndexer _fileIndexer;
|
||||||
|
private readonly IFileSystemScanner _fileSystemScanner;
|
||||||
|
private readonly ILogger<ScanCommand> _logger;
|
||||||
|
|
||||||
|
public ScanCommand(ILogger<ScanCommand> logger, IFileIndexer fileIndexer, IFileSystemScanner fileSystemScanner)
|
||||||
|
{
|
||||||
|
_logger = logger;
|
||||||
|
_fileIndexer = fileIndexer;
|
||||||
|
_fileSystemScanner = fileSystemScanner;
|
||||||
|
}
|
||||||
|
|
||||||
public override async Task<int> ExecuteAsync(CommandContext context, ScanSettings settings)
|
public override async Task<int> ExecuteAsync(CommandContext context, ScanSettings settings)
|
||||||
{
|
{
|
||||||
//TODO: check files for deletion -> files in db but no longer exist
|
//TODO: check files for deletion -> files in db but no longer exist
|
||||||
|
|
||||||
Logger.Info("Starting scanner and remover");
|
_logger.LogInformation("Starting scanner and remover");
|
||||||
var stopWatch = Stopwatch.StartNew();
|
var stopWatch = Stopwatch.StartNew();
|
||||||
|
|
||||||
var cancellationTokenSource = new CancellationTokenSource();
|
var cancellationTokenSource = new CancellationTokenSource();
|
||||||
|
|
||||||
var fileQueue = Channel.CreateUnbounded<string>();
|
var fileQueue = Channel.CreateUnbounded<string>();
|
||||||
|
|
||||||
var indexer = new FileIndexer(fileQueue, settings.PiwigoServerId);
|
var indexerTask = _fileIndexer.StartProcessingAsync(fileQueue, settings.PiwigoServerId, cancellationTokenSource.Token);
|
||||||
var indexerTask = indexer.StartProcessingAsync(cancellationTokenSource.Token);
|
|
||||||
|
|
||||||
var scanner = new FileSystemScanner(fileQueue, settings.PiwigoServerId);
|
await _fileSystemScanner.ScanAsync(fileQueue, settings.PiwigoServerId, cancellationTokenSource.Token);
|
||||||
await scanner.ScanAsync(cancellationTokenSource.Token);
|
|
||||||
|
|
||||||
fileQueue.Writer.Complete();
|
fileQueue.Writer.Complete();
|
||||||
|
|
||||||
await Task.WhenAll(fileQueue.Reader.Completion, indexerTask);
|
await Task.WhenAll(fileQueue.Reader.Completion, indexerTask);
|
||||||
|
|
||||||
stopWatch.Stop();
|
stopWatch.Stop();
|
||||||
Logger.Info($"Processed {indexer.TotalFilesScanned} image files in {stopWatch.Elapsed.TotalSeconds} seconds");
|
_logger.LogInformation("Processed {IndexerTotalFilesScanned} image files in {ElapsedTotalSeconds} seconds", _fileIndexer.TotalFilesScanned, stopWatch.Elapsed.TotalSeconds);
|
||||||
|
|
||||||
|
//TODO: write failed files to log
|
||||||
|
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
33
PiwigoDirectorySync/Commands/SyncAlbumsCommand.cs
Normal file
33
PiwigoDirectorySync/Commands/SyncAlbumsCommand.cs
Normal file
@ -0,0 +1,33 @@
|
|||||||
|
using System.Diagnostics;
|
||||||
|
using Microsoft.Extensions.Logging;
|
||||||
|
using PiwigoDirectorySync.Services;
|
||||||
|
using Spectre.Console.Cli;
|
||||||
|
|
||||||
|
namespace PiwigoDirectorySync.Commands;
|
||||||
|
|
||||||
|
public class SyncAlbumsCommand : AsyncCommand<SyncAlbumsSettings>
|
||||||
|
{
|
||||||
|
private readonly IAlbumSynchronizer _albumSynchronizer;
|
||||||
|
private readonly ILogger<SyncAlbumsCommand> _logger;
|
||||||
|
|
||||||
|
public SyncAlbumsCommand(ILogger<SyncAlbumsCommand> logger, IAlbumSynchronizer albumSynchronizer)
|
||||||
|
{
|
||||||
|
_logger = logger;
|
||||||
|
_albumSynchronizer = albumSynchronizer;
|
||||||
|
}
|
||||||
|
|
||||||
|
public override async Task<int> ExecuteAsync(CommandContext context, SyncAlbumsSettings settings)
|
||||||
|
{
|
||||||
|
_logger.LogInformation("Starting album synchronization");
|
||||||
|
var stopWatch = Stopwatch.StartNew();
|
||||||
|
|
||||||
|
var cancellationTokenSource = new CancellationTokenSource();
|
||||||
|
await _albumSynchronizer.SynchronizeAlbums(settings.PiwigoServerId, cancellationTokenSource.Token);
|
||||||
|
|
||||||
|
stopWatch.Stop();
|
||||||
|
_logger.LogInformation("Synchronized all albums with piwigo server {SettingsPiwigoServerId} in {ElapsedTotalSeconds} seconds", settings.PiwigoServerId,
|
||||||
|
stopWatch.Elapsed.TotalSeconds);
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
}
|
11
PiwigoDirectorySync/Commands/SyncAlbumsSettings.cs
Normal file
11
PiwigoDirectorySync/Commands/SyncAlbumsSettings.cs
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
using System.Diagnostics.CodeAnalysis;
|
||||||
|
using Spectre.Console.Cli;
|
||||||
|
|
||||||
|
namespace PiwigoDirectorySync.Commands;
|
||||||
|
|
||||||
|
[SuppressMessage("ReSharper", "UnusedAutoPropertyAccessor.Global")]
|
||||||
|
public class SyncAlbumsSettings : CommandSettings
|
||||||
|
{
|
||||||
|
[CommandArgument(0, "<PiwigoServerId>")]
|
||||||
|
public int PiwigoServerId { get; set; }
|
||||||
|
}
|
@ -0,0 +1,36 @@
|
|||||||
|
using Microsoft.Extensions.DependencyInjection;
|
||||||
|
using Spectre.Console.Cli;
|
||||||
|
|
||||||
|
namespace PiwigoDirectorySync.Infrastructure;
|
||||||
|
|
||||||
|
public sealed class DependencyInjectionTypeRegistrar : ITypeRegistrar
|
||||||
|
{
|
||||||
|
private readonly IServiceCollection _builder;
|
||||||
|
|
||||||
|
public DependencyInjectionTypeRegistrar(IServiceCollection builder)
|
||||||
|
{
|
||||||
|
_builder = builder;
|
||||||
|
}
|
||||||
|
|
||||||
|
public ITypeResolver Build() => new DependencyTypeResolver(_builder.BuildServiceProvider());
|
||||||
|
|
||||||
|
public void Register(Type service, Type implementation)
|
||||||
|
{
|
||||||
|
_builder.AddSingleton(service, implementation);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void RegisterInstance(Type service, object implementation)
|
||||||
|
{
|
||||||
|
_builder.AddSingleton(service, implementation);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void RegisterLazy(Type service, Func<object> func)
|
||||||
|
{
|
||||||
|
if (func is null)
|
||||||
|
{
|
||||||
|
throw new ArgumentNullException(nameof(func));
|
||||||
|
}
|
||||||
|
|
||||||
|
_builder.AddSingleton(service, _ => func());
|
||||||
|
}
|
||||||
|
}
|
23
PiwigoDirectorySync/Infrastructure/DependencyTypeResolver.cs
Normal file
23
PiwigoDirectorySync/Infrastructure/DependencyTypeResolver.cs
Normal file
@ -0,0 +1,23 @@
|
|||||||
|
using Spectre.Console.Cli;
|
||||||
|
|
||||||
|
namespace PiwigoDirectorySync.Infrastructure;
|
||||||
|
|
||||||
|
public sealed class DependencyTypeResolver : ITypeResolver, IDisposable
|
||||||
|
{
|
||||||
|
private readonly IServiceProvider _provider;
|
||||||
|
|
||||||
|
public DependencyTypeResolver(IServiceProvider provider)
|
||||||
|
{
|
||||||
|
_provider = provider ?? throw new ArgumentNullException(nameof(provider));
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Dispose()
|
||||||
|
{
|
||||||
|
if (_provider is IDisposable disposable)
|
||||||
|
{
|
||||||
|
disposable.Dispose();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public object? Resolve(Type? type) => type == null ? null : _provider.GetService(type);
|
||||||
|
}
|
15
PiwigoDirectorySync/Infrastructure/PiwigoClientFactory.cs
Normal file
15
PiwigoDirectorySync/Infrastructure/PiwigoClientFactory.cs
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
using Microsoft.Extensions.Logging;
|
||||||
|
using Piwigo.Client;
|
||||||
|
using PiwigoDirectorySync.Persistence;
|
||||||
|
|
||||||
|
namespace PiwigoDirectorySync.Infrastructure;
|
||||||
|
|
||||||
|
public static class PiwigoClientFactory
|
||||||
|
{
|
||||||
|
public static async Task<IPiwigoClient> GetPiwigoClientAsync(ServerEntity piwigoServer, ILoggerFactory loggerFactory, CancellationToken ct)
|
||||||
|
{
|
||||||
|
var piwigoClient = PiwigoClient.CreateClient(piwigoServer.Url, piwigoServer.Username, piwigoServer.Password, loggerFactory);
|
||||||
|
await piwigoClient.Session.LoginAsync(ct);
|
||||||
|
return piwigoClient;
|
||||||
|
}
|
||||||
|
}
|
@ -22,5 +22,4 @@ public class AlbumEntity
|
|||||||
|
|
||||||
public required int ServerId { get; set; }
|
public required int ServerId { get; set; }
|
||||||
public ServerEntity Server { get; set; } = null!;
|
public ServerEntity Server { get; set; } = null!;
|
||||||
|
|
||||||
}
|
}
|
@ -13,4 +13,9 @@ public static class ExtensionMethods
|
|||||||
{
|
{
|
||||||
return await dbSet.Where(a => a.ServerId == serverId && a.Path == relativePath).FirstOrDefaultAsync(ct);
|
return await dbSet.Where(a => a.ServerId == serverId && a.Path == relativePath).FirstOrDefaultAsync(ct);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static async Task<AlbumEntity?> FindByServerIdAsync(this DbSet<AlbumEntity> dbSet, int piwigoServerId, int serverAlbumId, CancellationToken ct)
|
||||||
|
{
|
||||||
|
return await dbSet.Where(a => a.ServerId == piwigoServerId && a.ServerAlbumId == serverAlbumId).FirstOrDefaultAsync(ct);
|
||||||
|
}
|
||||||
}
|
}
|
@ -12,23 +12,24 @@
|
|||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<PackageReference Include="Microsoft.Extensions.Configuration.UserSecrets" Version="7.0.0" />
|
|
||||||
<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">
|
<PackageReference Include="Microsoft.EntityFrameworkCore.Design" Version="7.0.10">
|
||||||
<PrivateAssets>all</PrivateAssets>
|
<PrivateAssets>all</PrivateAssets>
|
||||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||||
</PackageReference>
|
</PackageReference>
|
||||||
<PackageReference Include="Microsoft.EntityFrameworkCore.InMemory" Version="7.0.10"/>
|
<PackageReference Include="Microsoft.EntityFrameworkCore.InMemory" Version="7.0.10"/>
|
||||||
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" Version="7.0.10"/>
|
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" Version="7.0.10"/>
|
||||||
|
<PackageReference Include="Microsoft.Extensions.Configuration.UserSecrets" Version="7.0.0" />
|
||||||
|
<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="Microsoft.Extensions.Logging.Console" Version="7.0.0" />
|
||||||
|
<PackageReference Include="Microsoft.Extensions.Logging.Debug" Version="7.0.0" />
|
||||||
|
<PackageReference Include="Piwigo.Client" Version="0.1.0.17"/>
|
||||||
|
<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"/>
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
@ -48,6 +49,7 @@
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
|
|
||||||
|
@ -1,15 +1,25 @@
|
|||||||
using NLog;
|
using Microsoft.Extensions.DependencyInjection;
|
||||||
using NLog.Extensions.Logging;
|
using Microsoft.Extensions.Logging;
|
||||||
using PiwigoDirectorySync;
|
|
||||||
using PiwigoDirectorySync.Commands;
|
using PiwigoDirectorySync.Commands;
|
||||||
|
using PiwigoDirectorySync.Infrastructure;
|
||||||
|
using PiwigoDirectorySync.Services;
|
||||||
using Spectre.Console.Cli;
|
using Spectre.Console.Cli;
|
||||||
|
|
||||||
LogManager.Configuration = new NLogLoggingConfiguration(AppSettings.Config.GetSection("NLog"));
|
var registrations = new ServiceCollection();
|
||||||
var logFactory = LogManager.Setup().LogFactory;
|
registrations.AddLogging(l => l.AddSimpleConsole(c =>
|
||||||
var logger = logFactory.GetCurrentClassLogger();
|
{
|
||||||
|
c.SingleLine = true;
|
||||||
|
c.IncludeScopes = true;
|
||||||
|
})
|
||||||
|
.AddDebug());
|
||||||
|
|
||||||
logger.Info("Starting command app");
|
registrations.AddTransient<IFileIndexer, FileIndexer>();
|
||||||
var app = new CommandApp();
|
registrations.AddTransient<IFileSystemScanner, FileSystemScanner>();
|
||||||
|
registrations.AddTransient<IAlbumSynchronizer, AlbumSynchronizer>();
|
||||||
|
|
||||||
|
var registrar = new DependencyInjectionTypeRegistrar(registrations);
|
||||||
|
|
||||||
|
var app = new CommandApp(registrar);
|
||||||
app.Configure(config =>
|
app.Configure(config =>
|
||||||
{
|
{
|
||||||
#if DEBUG
|
#if DEBUG
|
||||||
@ -18,6 +28,7 @@ app.Configure(config =>
|
|||||||
#endif
|
#endif
|
||||||
|
|
||||||
config.AddCommand<ScanCommand>("scan");
|
config.AddCommand<ScanCommand>("scan");
|
||||||
|
config.AddBranch("sync", c => { c.AddCommand<SyncAlbumsCommand>("albums"); });
|
||||||
});
|
});
|
||||||
|
|
||||||
return app.Run(args);
|
return app.Run(args);
|
@ -1,17 +1,23 @@
|
|||||||
{
|
{
|
||||||
"$schema": "http://json.schemastore.org/launchsettings.json",
|
"$schema": "http://json.schemastore.org/launchsettings.json",
|
||||||
"profiles": {
|
"profiles": {
|
||||||
"PiwigoDirectorySync": {
|
"SyncLocalJpgs": {
|
||||||
"commandName": "Project",
|
"commandName": "Project",
|
||||||
"commandLineArgs": "scan 1",
|
"commandLineArgs": "scan 1",
|
||||||
"environmentVariables": {
|
"environmentVariables": {
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"PiwigoDirectorySyncPng": {
|
"SyncLocalPngs": {
|
||||||
"commandName": "Project",
|
"commandName": "Project",
|
||||||
"commandLineArgs": "scan 2",
|
"commandLineArgs": "scan 2",
|
||||||
"environmentVariables": {
|
"environmentVariables": {
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
"SyncAlbums": {
|
||||||
|
"commandName": "Project",
|
||||||
|
"commandLineArgs": "sync albums 1",
|
||||||
|
"environmentVariables": {
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
92
PiwigoDirectorySync/Services/AlbumSynchronizer.cs
Normal file
92
PiwigoDirectorySync/Services/AlbumSynchronizer.cs
Normal file
@ -0,0 +1,92 @@
|
|||||||
|
using Microsoft.Extensions.Logging;
|
||||||
|
using Piwigo.Client;
|
||||||
|
using Piwigo.Client.Albums;
|
||||||
|
using PiwigoDirectorySync.Infrastructure;
|
||||||
|
using PiwigoDirectorySync.Persistence;
|
||||||
|
|
||||||
|
namespace PiwigoDirectorySync.Services;
|
||||||
|
|
||||||
|
public class AlbumSynchronizer : IAlbumSynchronizer
|
||||||
|
{
|
||||||
|
private readonly ILogger<AlbumSynchronizer> _logger;
|
||||||
|
private readonly ILoggerFactory _loggerFactory;
|
||||||
|
|
||||||
|
public AlbumSynchronizer(ILogger<AlbumSynchronizer> logger, ILoggerFactory loggerFactory)
|
||||||
|
{
|
||||||
|
_logger = logger;
|
||||||
|
_loggerFactory = loggerFactory;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task SynchronizeAlbums(int piwigoServerId, CancellationToken ct)
|
||||||
|
{
|
||||||
|
await using var dbContext = new PersistenceContext();
|
||||||
|
var piwigoServer = await dbContext.PiwigoServers.FindAsync(new object?[] { piwigoServerId }, ct);
|
||||||
|
if (piwigoServer is null)
|
||||||
|
{
|
||||||
|
_logger.LogError("Could not sync albums with piwigo server {PiwigoServerId}", piwigoServerId);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var piwigoClient = await PiwigoClientFactory.GetPiwigoClientAsync(piwigoServer, _loggerFactory, ct);
|
||||||
|
|
||||||
|
await UpdatePiwigoAlbumsFromServerAsync(dbContext, piwigoClient, piwigoServer, ct);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static async Task UpdatePiwigoAlbumsFromServerAsync(PersistenceContext dbContext, IPiwigoClient piwigoClient, ServerEntity piwigoServer, CancellationToken ct)
|
||||||
|
{
|
||||||
|
var serverAlbums = await piwigoClient.Album.GetListAsync(null, true, false, ThumbnailSize.Thumb, ct);
|
||||||
|
var serverAlbumDictionary = serverAlbums.ToDictionary(a => a.Id, a => a);
|
||||||
|
|
||||||
|
foreach (var serverAlbum in serverAlbums)
|
||||||
|
{
|
||||||
|
var albumEntity = await GetOrAddPiwigoAlbumEntityAsync(dbContext, piwigoServer, serverAlbum, serverAlbumDictionary, ct);
|
||||||
|
if (serverAlbum.IdUpperCat.HasValue)
|
||||||
|
{
|
||||||
|
albumEntity.ParentId = (await dbContext.PiwigoAlbums.FindByServerIdAsync(piwigoServer.Id, serverAlbum.IdUpperCat.Value, ct))?.Id;
|
||||||
|
}
|
||||||
|
|
||||||
|
await dbContext.SaveChangesAsync(ct);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static async Task<AlbumEntity> GetOrAddPiwigoAlbumEntityAsync(PersistenceContext dbContext, ServerEntity piwigoServer, Album serverAlbum,
|
||||||
|
IDictionary<int, Album> serverAlbumDictionary, CancellationToken ct)
|
||||||
|
{
|
||||||
|
var albumEntity = await dbContext.PiwigoAlbums.FindByServerIdAsync(piwigoServer.Id, serverAlbum.Id, ct);
|
||||||
|
if (albumEntity != null)
|
||||||
|
{
|
||||||
|
return albumEntity;
|
||||||
|
}
|
||||||
|
|
||||||
|
albumEntity = new AlbumEntity
|
||||||
|
{
|
||||||
|
ServerId = piwigoServer.Id,
|
||||||
|
Server = piwigoServer,
|
||||||
|
Name = serverAlbum.Name,
|
||||||
|
ServerAlbumId = serverAlbum.Id,
|
||||||
|
Path = GeneratePath(serverAlbum, serverAlbumDictionary)
|
||||||
|
};
|
||||||
|
dbContext.PiwigoAlbums.Add(albumEntity);
|
||||||
|
|
||||||
|
return albumEntity;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static string GeneratePath(Album serverAlbum, IDictionary<int, Album> serverAlbumDictionary)
|
||||||
|
{
|
||||||
|
if (!serverAlbum.IdUpperCat.HasValue)
|
||||||
|
{
|
||||||
|
return serverAlbum.Name;
|
||||||
|
}
|
||||||
|
|
||||||
|
var parentId = serverAlbum.IdUpperCat.Value;
|
||||||
|
var path = serverAlbum.Name;
|
||||||
|
while (parentId != 0)
|
||||||
|
{
|
||||||
|
var currentParent = serverAlbumDictionary[parentId];
|
||||||
|
path = Path.Combine(currentParent.Name, path);
|
||||||
|
parentId = currentParent.IdUpperCat ?? 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
return path;
|
||||||
|
}
|
||||||
|
}
|
@ -1,49 +1,45 @@
|
|||||||
using System.Security.Cryptography;
|
using System.Security.Cryptography;
|
||||||
using System.Threading.Channels;
|
using System.Threading.Channels;
|
||||||
using Microsoft.EntityFrameworkCore;
|
using Microsoft.EntityFrameworkCore;
|
||||||
using NLog;
|
using Microsoft.Extensions.Logging;
|
||||||
using PiwigoDirectorySync.Persistence;
|
using PiwigoDirectorySync.Persistence;
|
||||||
|
|
||||||
namespace PiwigoDirectorySync.Services;
|
namespace PiwigoDirectorySync.Services;
|
||||||
|
|
||||||
public class FileIndexer
|
public class FileIndexer : IFileIndexer
|
||||||
{
|
{
|
||||||
private static readonly ILogger Logger = LogManager.GetCurrentClassLogger();
|
|
||||||
private readonly IList<string> _failedFiles = new List<string>();
|
private readonly IList<string> _failedFiles = new List<string>();
|
||||||
|
private readonly ILogger<FileIndexer> _logger;
|
||||||
|
|
||||||
private readonly Channel<string> _fileQueue;
|
public FileIndexer(ILogger<FileIndexer> logger)
|
||||||
private readonly int _piwigoServerId;
|
|
||||||
|
|
||||||
public FileIndexer(Channel<string> fileQueue, int piwigoServerId)
|
|
||||||
{
|
{
|
||||||
_fileQueue = fileQueue ?? throw new ArgumentNullException(nameof(fileQueue));
|
_logger = logger;
|
||||||
_piwigoServerId = piwigoServerId;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public int TotalFilesScanned { get; private set; }
|
public int TotalFilesScanned { get; private set; }
|
||||||
public IReadOnlyCollection<string> FailedFiles => _failedFiles.AsReadOnly();
|
public IReadOnlyCollection<string> FailedFiles => _failedFiles.AsReadOnly();
|
||||||
|
|
||||||
public async Task StartProcessingAsync(CancellationToken ct)
|
public async Task StartProcessingAsync(Channel<string> fileQueue, int piwigoServerId, CancellationToken ct)
|
||||||
{
|
{
|
||||||
await using var db = new PersistenceContext();
|
await using var db = new PersistenceContext();
|
||||||
var piwigoServer = await db.PiwigoServers.GetByIdAsync(_piwigoServerId, ct);
|
var piwigoServer = await db.PiwigoServers.GetByIdAsync(piwigoServerId, ct);
|
||||||
|
|
||||||
await foreach (var fullFilePath in _fileQueue.Reader.ReadAllAsync(ct))
|
await foreach (var fullFilePath in fileQueue.Reader.ReadAllAsync(ct))
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
if (ct.IsCancellationRequested)
|
if (ct.IsCancellationRequested)
|
||||||
{
|
{
|
||||||
Logger.Warn("Indexing cancelled");
|
_logger.LogWarning("Indexing cancelled");
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
Logger.Info($"Indexing file {fullFilePath}");
|
_logger.LogInformation("Indexing file {FullFilePath}", fullFilePath);
|
||||||
var fileInfo = new FileInfo(fullFilePath);
|
var fileInfo = new FileInfo(fullFilePath);
|
||||||
|
|
||||||
if (!fileInfo.Exists)
|
if (!fileInfo.Exists)
|
||||||
{
|
{
|
||||||
Logger.Warn($"File {fullFilePath} not found");
|
_logger.LogWarning("File {FullFilePath} not found", fullFilePath);
|
||||||
_failedFiles.Add(fullFilePath);
|
_failedFiles.Add(fullFilePath);
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
@ -70,7 +66,7 @@ public class FileIndexer
|
|||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
_failedFiles.Add(fullFilePath);
|
_failedFiles.Add(fullFilePath);
|
||||||
Logger.Error(ex, $"could not delete file {fullFilePath}");
|
_logger.LogError(ex, "could not delete file {FullFilePath}", fullFilePath);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,47 +1,44 @@
|
|||||||
using System.Threading.Channels;
|
using System.Threading.Channels;
|
||||||
using NLog;
|
using Microsoft.Extensions.Logging;
|
||||||
using PiwigoDirectorySync.Persistence;
|
using PiwigoDirectorySync.Persistence;
|
||||||
|
|
||||||
namespace PiwigoDirectorySync.Services;
|
namespace PiwigoDirectorySync.Services;
|
||||||
|
|
||||||
public class FileSystemScanner
|
public class FileSystemScanner : IFileSystemScanner
|
||||||
{
|
{
|
||||||
private static readonly ILogger Logger = LogManager.GetCurrentClassLogger();
|
private readonly ILogger<FileSystemScanner> _logger;
|
||||||
|
|
||||||
private readonly Channel<string> _fileQueue;
|
|
||||||
private readonly int _piwigoServerId;
|
|
||||||
|
|
||||||
public FileSystemScanner(Channel<string> fileQueue, int piwigoServerId)
|
public FileSystemScanner(ILogger<FileSystemScanner> logger)
|
||||||
{
|
{
|
||||||
_fileQueue = fileQueue ?? throw new ArgumentNullException(nameof(fileQueue));
|
_logger = logger;
|
||||||
_piwigoServerId = piwigoServerId;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task ScanAsync(CancellationToken ct)
|
public async Task ScanAsync(Channel<string> fileQueue, int piwigoServerId, CancellationToken ct)
|
||||||
{
|
{
|
||||||
await using var db = new PersistenceContext();
|
await using var db = new PersistenceContext();
|
||||||
|
|
||||||
var piwigoServer = await db.PiwigoServers.GetByIdAsync(_piwigoServerId, ct);
|
var piwigoServer = await db.PiwigoServers.GetByIdAsync(piwigoServerId, ct);
|
||||||
Logger.Info($"Scanning files for piwigo server {piwigoServer.Name} in directory {piwigoServer.RootDirectory}");
|
_logger.LogInformation("Scanning files for piwigo server {PiwigoServerName} in directory {PiwigoServerRootDirectory}", piwigoServer.Name, piwigoServer.RootDirectory);
|
||||||
|
|
||||||
await ScanRootDirectory(new DirectoryInfo(piwigoServer.RootDirectory), ct);
|
await ScanRootDirectory(fileQueue, new DirectoryInfo(piwigoServer.RootDirectory), ct);
|
||||||
}
|
}
|
||||||
|
|
||||||
private async ValueTask ScanRootDirectory(DirectoryInfo directory, CancellationToken ct)
|
private async ValueTask ScanRootDirectory(Channel<string> fileQueue, DirectoryInfo directory, CancellationToken ct)
|
||||||
{
|
{
|
||||||
Logger.Info($"Scanning root directory {directory.FullName} for sidecars to delete");
|
_logger.LogInformation("Scanning root directory {DirectoryFullName} for sidecars to delete", directory.FullName);
|
||||||
var parallelOptions = new ParallelOptions
|
var parallelOptions = new ParallelOptions
|
||||||
{
|
{
|
||||||
CancellationToken = ct
|
CancellationToken = ct
|
||||||
};
|
};
|
||||||
await Parallel.ForEachAsync(GetDirectories(directory), parallelOptions, FindAndEnqueueFilesToAdd);
|
await Parallel.ForEachAsync(GetDirectories(directory), parallelOptions, (d, c) => FindAndEnqueueFilesToAdd(fileQueue, d, c));
|
||||||
}
|
}
|
||||||
|
|
||||||
private async ValueTask FindAndEnqueueFilesToAdd(DirectoryInfo directory, CancellationToken ct)
|
private async ValueTask FindAndEnqueueFilesToAdd(Channel<string> fileQueue, DirectoryInfo directory, CancellationToken ct)
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
Logger.Info($"Scanning directory {directory.FullName} for images");
|
_logger.LogInformation("Scanning directory {DirectoryFullName} for images", directory.FullName);
|
||||||
|
|
||||||
var imageFiles = AppSettings.SupportedExtensions.SelectMany(ext => directory.GetFiles($"*.{ext}", SearchOption.TopDirectoryOnly))
|
var imageFiles = AppSettings.SupportedExtensions.SelectMany(ext => directory.GetFiles($"*.{ext}", SearchOption.TopDirectoryOnly))
|
||||||
.Select(f => f.FullName)
|
.Select(f => f.FullName)
|
||||||
@ -49,19 +46,19 @@ public class FileSystemScanner
|
|||||||
|
|
||||||
if (!imageFiles.Any())
|
if (!imageFiles.Any())
|
||||||
{
|
{
|
||||||
Logger.Debug($"No iamges in {directory.FullName} found, skipping");
|
_logger.LogDebug("No images in {DirectoryFullName} found, skipping", directory.FullName);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
foreach (var imageFile in imageFiles.Select(f => new FileInfo(f)))
|
foreach (var imageFile in imageFiles.Select(f => new FileInfo(f)))
|
||||||
{
|
{
|
||||||
Logger.Debug($"Found image {imageFile.FullName}, enqueue index");
|
_logger.LogDebug("Found image {ImageFileFullName}, enqueue index", imageFile.FullName);
|
||||||
await _fileQueue.Writer.WriteAsync(imageFile.FullName, ct);
|
await fileQueue.Writer.WriteAsync(imageFile.FullName, ct);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
Logger.Error(ex, $"could not scan directory {directory.FullName}");
|
_logger.LogError(ex, "could not scan directory {DirectoryFullName}", directory.FullName);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
6
PiwigoDirectorySync/Services/IAlbumSynchronizer.cs
Normal file
6
PiwigoDirectorySync/Services/IAlbumSynchronizer.cs
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
namespace PiwigoDirectorySync.Services;
|
||||||
|
|
||||||
|
public interface IAlbumSynchronizer
|
||||||
|
{
|
||||||
|
Task SynchronizeAlbums(int piwigoServerId, CancellationToken ct);
|
||||||
|
}
|
10
PiwigoDirectorySync/Services/IFileIndexer.cs
Normal file
10
PiwigoDirectorySync/Services/IFileIndexer.cs
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
using System.Threading.Channels;
|
||||||
|
|
||||||
|
namespace PiwigoDirectorySync.Services;
|
||||||
|
|
||||||
|
public interface IFileIndexer
|
||||||
|
{
|
||||||
|
int TotalFilesScanned { get; }
|
||||||
|
IReadOnlyCollection<string> FailedFiles { get; }
|
||||||
|
Task StartProcessingAsync(Channel<string> fileQueue, int piwigoServerId, CancellationToken ct);
|
||||||
|
}
|
8
PiwigoDirectorySync/Services/IFileSystemScanner.cs
Normal file
8
PiwigoDirectorySync/Services/IFileSystemScanner.cs
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
using System.Threading.Channels;
|
||||||
|
|
||||||
|
namespace PiwigoDirectorySync.Services;
|
||||||
|
|
||||||
|
public interface IFileSystemScanner
|
||||||
|
{
|
||||||
|
Task ScanAsync(Channel<string> fileQueue, int piwigoServerId, CancellationToken ct);
|
||||||
|
}
|
@ -13,56 +13,5 @@
|
|||||||
"Settings": {
|
"Settings": {
|
||||||
"DbProvider": "Sqlite",
|
"DbProvider": "Sqlite",
|
||||||
"ImageRootDirectory": ".\\"
|
"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"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
}
|
||||||
}
|
}
|
Loading…
Reference in New Issue
Block a user