fixes concurrency issues on get or add album
All checks were successful
PiwigoDirectorySync/pipeline/head This commit looks good

This commit is contained in:
Philipp Häfelfinger 2023-09-12 00:17:21 +02:00
parent f111df487d
commit 3b8dad1af2
2 changed files with 37 additions and 21 deletions

View File

@ -25,6 +25,7 @@
<PackageReference Include="Microsoft.Extensions.Configuration.Json" Version="7.0.0"/> <PackageReference Include="Microsoft.Extensions.Configuration.Json" Version="7.0.0"/>
<PackageReference Include="Microsoft.Extensions.Logging.Configuration" Version="7.0.0" /> <PackageReference Include="Microsoft.Extensions.Logging.Configuration" Version="7.0.0" />
<PackageReference Include="Piwigo.Client" Version="0.1.0.19" /> <PackageReference Include="Piwigo.Client" Version="0.1.0.19" />
<PackageReference Include="Polly" Version="7.2.4" />
<PackageReference Include="Serilog" Version="3.0.1" /> <PackageReference Include="Serilog" Version="3.0.1" />
<PackageReference Include="Serilog.Extensions.Logging" Version="7.0.0" /> <PackageReference Include="Serilog.Extensions.Logging" Version="7.0.0" />
<PackageReference Include="Serilog.Sinks.Console" Version="4.1.0" /> <PackageReference Include="Serilog.Sinks.Console" Version="4.1.0" />

View File

@ -1,9 +1,11 @@
using System.Collections.Concurrent; using System.Collections.Concurrent;
using System.Data;
using System.Threading.Channels; using System.Threading.Channels;
using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.DependencyInjection;
using PiwigoDirectorySync.Infrastructure; using PiwigoDirectorySync.Infrastructure;
using PiwigoDirectorySync.Persistence; using PiwigoDirectorySync.Persistence;
using Polly;
using Serilog; using Serilog;
namespace PiwigoDirectorySync.Services; namespace PiwigoDirectorySync.Services;
@ -62,7 +64,7 @@ internal class FileIndexer : IFileIndexer
var relativePath = Path.GetRelativePath(piwigoServer.RootDirectory, fullFilePath); var relativePath = Path.GetRelativePath(piwigoServer.RootDirectory, fullFilePath);
var album = await GetOrAddAlbumAsync(db, piwigoServer, fileInfo.Directory!, ct); var album = await GetOrAddAlbumAsync(piwigoServerId, fileInfo.Directory!, ct);
var image = await GetOrAddImageAsync(db, album, relativePath, ct); var image = await GetOrAddImageAsync(db, album, relativePath, ct);
@ -86,15 +88,14 @@ internal class FileIndexer : IFileIndexer
} }
} }
private static async Task<ImageEntity> GetOrAddImageAsync(PersistenceContext db, AlbumEntity album, string relativePath, CancellationToken ct) private static async Task<ImageEntity> GetOrAddImageAsync(PersistenceContext db, int albumId, string relativePath, CancellationToken ct)
{ {
var imageEntity = await db.PiwigoImages.Where(i => i.AlbumId == album.Id && i.FilePath == relativePath).FirstOrDefaultAsync(ct); var imageEntity = await db.PiwigoImages.Where(i => i.AlbumId == albumId && i.FilePath == relativePath).FirstOrDefaultAsync(ct);
if (imageEntity is null) if (imageEntity is null)
{ {
imageEntity = new ImageEntity imageEntity = new ImageEntity
{ {
AlbumId = album.Id, AlbumId = albumId,
Album = album,
FilePath = relativePath, FilePath = relativePath,
UploadRequired = true, UploadRequired = true,
DeleteRequired = false DeleteRequired = false
@ -105,38 +106,52 @@ internal class FileIndexer : IFileIndexer
return imageEntity; return imageEntity;
} }
private static async Task<AlbumEntity> GetOrAddAlbumAsync(PersistenceContext db, ServerEntity server, DirectoryInfo directory, CancellationToken ct) private async Task<int> GetOrAddAlbumAsync(int serverId, DirectoryInfo directory, CancellationToken ct)
{ {
var albumPath = Path.GetRelativePath(server.RootDirectory, directory.FullName); return await Policy.Handle<DbUpdateException>()
var album = await db.PiwigoAlbums.FindByServerAndPathAsync(server.Id, albumPath, ct); .RetryAsync(3, (ex, _) => { _logger.Warning(ex, "Could not create album for directory {AlbumDirectory}, retrying", directory); })
.ExecuteAsync(async () =>
{
await using var scope = _serviceProvider.CreateAsyncScope();
await using var db = scope.ServiceProvider.GetRequiredService<PersistenceContext>();
await using var tx = await db.Database.BeginTransactionAsync(IsolationLevel.Serializable, ct);
var piwigoServer = await db.PiwigoServers.GetByIdAsync(serverId, ct);
_logger.Information("Get or crate album for directory {AlbumDirectory}", directory);
var albumId = await GetOrAddAlbumAsync(db, serverId, piwigoServer.RootDirectory, directory, ct);
await tx.CommitAsync(ct);
return albumId;
});
}
private static async Task<int> GetOrAddAlbumAsync(PersistenceContext db, int serverId, string rootDirectory, DirectoryInfo directory, CancellationToken ct)
{
var albumPath = Path.GetRelativePath(rootDirectory, directory.FullName);
var album = await db.PiwigoAlbums.FindByServerAndPathAsync(serverId, albumPath, ct);
if (album != null) if (album != null)
{ {
return album; return album.Id;
} }
AlbumEntity? parentAlbum; int? parentAlbumId = null;
if (string.Equals(new DirectoryInfo(server.RootDirectory).FullName, directory.Parent!.FullName)) if (!string.Equals(new DirectoryInfo(rootDirectory).FullName, directory.Parent!.FullName))
{ {
parentAlbum = null; parentAlbumId = await GetOrAddAlbumAsync(db, serverId, rootDirectory, directory.Parent!, ct);
}
else
{
parentAlbum = await GetOrAddAlbumAsync(db, server, directory.Parent!, ct);
} }
album = new AlbumEntity album = new AlbumEntity
{ {
ServerId = server.Id, ServerId = serverId,
Server = server,
Name = directory.Name, Name = directory.Name,
Path = albumPath, Path = albumPath,
ParentId = parentAlbum?.Id, ParentId = parentAlbumId
Parent = parentAlbum
}; };
db.PiwigoAlbums.Add(album); db.PiwigoAlbums.Add(album);
await db.SaveChangesAsync(ct); await db.SaveChangesAsync(ct);
return album; return album.Id;
} }
} }