Merge pull request #9 from AnonymusRaccoon/threading

Threading
This commit is contained in:
Zoe Roux 2020-06-15 02:36:14 +02:00
commit fc65716906
19 changed files with 465 additions and 87 deletions

View File

@ -1,11 +1,12 @@
using System.Collections.Generic; using System;
using System.Collections.Generic;
using System.Threading.Tasks; using System.Threading.Tasks;
using JetBrains.Annotations; using JetBrains.Annotations;
using Kyoo.Models; using Kyoo.Models;
namespace Kyoo.Controllers namespace Kyoo.Controllers
{ {
public interface ILibraryManager public interface ILibraryManager : IDisposable, IAsyncDisposable
{ {
// Get by slug // Get by slug
Task<Library> GetLibrary(string slug); Task<Library> GetLibrary(string slug);

View File

@ -1,3 +1,4 @@
using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Threading.Tasks; using System.Threading.Tasks;
using JetBrains.Annotations; using JetBrains.Annotations;
@ -5,7 +6,7 @@ using Kyoo.Models;
namespace Kyoo.Controllers namespace Kyoo.Controllers
{ {
public interface IRepository<T> public interface IRepository<T> : IDisposable, IAsyncDisposable
{ {
Task<T> Get(int id); Task<T> Get(int id);
Task<T> Get(string slug); Task<T> Get(string slug);

View File

@ -0,0 +1,14 @@
using System;
namespace Kyoo.Models.Exceptions
{
public class DuplicatedItemException : Exception
{
public override string Message { get; }
public DuplicatedItemException(string message)
{
Message = message;
}
}
}

View File

@ -41,6 +41,36 @@ namespace Kyoo.Controllers
_people = people; _people = people;
} }
public void Dispose()
{
_libraries.Dispose();
_collections.Dispose();
_shows.Dispose();
_seasons.Dispose();
_episodes.Dispose();
_tracks.Dispose();
_genres.Dispose();
_studios.Dispose();
_people.Dispose();
_providers.Dispose();
}
public async ValueTask DisposeAsync()
{
await Task.WhenAll(
_libraries.DisposeAsync().AsTask(),
_collections.DisposeAsync().AsTask(),
_shows.DisposeAsync().AsTask(),
_seasons.DisposeAsync().AsTask(),
_episodes.DisposeAsync().AsTask(),
_tracks.DisposeAsync().AsTask(),
_genres.DisposeAsync().AsTask(),
_studios.DisposeAsync().AsTask(),
_people.DisposeAsync().AsTask(),
_providers.DisposeAsync().AsTask()
);
}
public Task<Library> GetLibrary(string slug) public Task<Library> GetLibrary(string slug)
{ {
return _libraries.Get(slug); return _libraries.Get(slug);

View File

@ -5,6 +5,7 @@ using System.Threading.Tasks;
using Kyoo.Models; using Kyoo.Models;
using Kyoo.Models.Exceptions; using Kyoo.Models.Exceptions;
using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore;
using Npgsql;
namespace Kyoo.Controllers namespace Kyoo.Controllers
{ {
@ -18,6 +19,16 @@ namespace Kyoo.Controllers
_database = database; _database = database;
} }
public void Dispose()
{
_database.Dispose();
}
public ValueTask DisposeAsync()
{
return _database.DisposeAsync();
}
public Task<Collection> Get(int id) public Task<Collection> Get(int id)
{ {
return _database.Collections.FirstOrDefaultAsync(x => x.ID == id); return _database.Collections.FirstOrDefaultAsync(x => x.ID == id);
@ -46,8 +57,19 @@ namespace Kyoo.Controllers
if (obj == null) if (obj == null)
throw new ArgumentNullException(nameof(obj)); throw new ArgumentNullException(nameof(obj));
await _database.Collections.AddAsync(obj); _database.Entry(obj).State = EntityState.Added;
try
{
await _database.SaveChangesAsync(); await _database.SaveChangesAsync();
}
catch (DbUpdateException ex)
{
if (Helper.IsDuplicateException(ex))
throw new DuplicatedItemException($"Trying to insert a duplicated collection (slug {obj.Slug} already exists).");
throw;
}
return obj.ID; return obj.ID;
} }
@ -59,8 +81,18 @@ namespace Kyoo.Controllers
Collection old = await Get(obj.Slug); Collection old = await Get(obj.Slug);
if (old != null) if (old != null)
return old.ID; return old.ID;
try
{
return await Create(obj); return await Create(obj);
} }
catch (DuplicatedItemException)
{
old = await Get(obj.Slug);
if (old == null)
throw new SystemException("Unknown database state.");
return old.ID;
}
}
public async Task Edit(Collection edited, bool resetOld) public async Task Edit(Collection edited, bool resetOld)
{ {

View File

@ -6,6 +6,7 @@ using Kyoo.Models;
using Kyoo.Models.Exceptions; using Kyoo.Models.Exceptions;
using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.DependencyInjection;
using Npgsql;
namespace Kyoo.Controllers namespace Kyoo.Controllers
{ {
@ -21,6 +22,16 @@ namespace Kyoo.Controllers
_serviceProvider = serviceProvider; _serviceProvider = serviceProvider;
} }
public void Dispose()
{
_database.Dispose();
}
public ValueTask DisposeAsync()
{
return _database.DisposeAsync();
}
public async Task<Episode> Get(int id) public async Task<Episode> Get(int id)
{ {
return await _database.Episodes.FirstOrDefaultAsync(x => x.ID == id); return await _database.Episodes.FirstOrDefaultAsync(x => x.ID == id);
@ -73,7 +84,17 @@ namespace Kyoo.Controllers
if (obj.Tracks != null) if (obj.Tracks != null)
foreach (Track entry in obj.Tracks) foreach (Track entry in obj.Tracks)
_database.Entry(entry).State = EntityState.Added; _database.Entry(entry).State = EntityState.Added;
try
{
await _database.SaveChangesAsync(); await _database.SaveChangesAsync();
}
catch (DbUpdateException ex)
{
if (Helper.IsDuplicateException(ex))
throw new DuplicatedItemException($"Trying to insert a duplicated episode (slug {obj.Slug} already exists).");
throw;
}
return obj.ID; return obj.ID;
} }
@ -85,8 +106,18 @@ namespace Kyoo.Controllers
Episode old = await Get(obj.Slug); Episode old = await Get(obj.Slug);
if (old != null) if (old != null)
return old.ID; return old.ID;
try
{
return await Create(obj); return await Create(obj);
} }
catch (DuplicatedItemException)
{
old = await Get(obj.Slug);
if (old == null)
throw new SystemException("Unknown database state.");
return old.ID;
}
}
public async Task Edit(Episode edited, bool resetOld) public async Task Edit(Episode edited, bool resetOld)
{ {
@ -116,7 +147,7 @@ namespace Kyoo.Controllers
obj.ExternalIDs = (await Task.WhenAll(obj.ExternalIDs.Select(async x => obj.ExternalIDs = (await Task.WhenAll(obj.ExternalIDs.Select(async x =>
{ {
using IServiceScope serviceScope = _serviceProvider.CreateScope(); using IServiceScope serviceScope = _serviceProvider.CreateScope();
IProviderRepository providers = serviceScope.ServiceProvider.GetService<IProviderRepository>(); await using IProviderRepository providers = serviceScope.ServiceProvider.GetService<IProviderRepository>();
x.ProviderID = await providers.CreateIfNotExists(x.Provider); x.ProviderID = await providers.CreateIfNotExists(x.Provider);
return x; return x;

View File

@ -18,6 +18,16 @@ namespace Kyoo.Controllers
_database = database; _database = database;
} }
public void Dispose()
{
_database.Dispose();
}
public ValueTask DisposeAsync()
{
return _database.DisposeAsync();
}
public async Task<Genre> Get(int id) public async Task<Genre> Get(int id)
{ {
return await _database.Genres.FirstOrDefaultAsync(x => x.ID == id); return await _database.Genres.FirstOrDefaultAsync(x => x.ID == id);
@ -47,7 +57,18 @@ namespace Kyoo.Controllers
throw new ArgumentNullException(nameof(obj)); throw new ArgumentNullException(nameof(obj));
await _database.Genres.AddAsync(obj); await _database.Genres.AddAsync(obj);
try
{
await _database.SaveChangesAsync(); await _database.SaveChangesAsync();
}
catch (DbUpdateException ex)
{
if (Helper.IsDuplicateException(ex))
throw new DuplicatedItemException($"Trying to insert a duplicated genre (slug {obj.Slug} already exists).");
throw;
}
return obj.ID; return obj.ID;
} }
@ -59,8 +80,18 @@ namespace Kyoo.Controllers
Genre old = await Get(obj.Slug); Genre old = await Get(obj.Slug);
if (old != null) if (old != null)
return old.ID; return old.ID;
try
{
return await Create(obj); return await Create(obj);
} }
catch (DuplicatedItemException)
{
old = await Get(obj.Slug);
if (old == null)
throw new SystemException("Unknown database state.");
return old.ID;
}
}
public async Task Edit(Genre edited, bool resetOld) public async Task Edit(Genre edited, bool resetOld)
{ {

View File

@ -0,0 +1,14 @@
using Microsoft.EntityFrameworkCore;
using Npgsql;
namespace Kyoo.Controllers
{
public static class Helper
{
public static bool IsDuplicateException(DbUpdateException ex)
{
return ex.InnerException is PostgresException inner
&& inner.SqlState == PostgresErrorCodes.UniqueViolation;
}
}
}

View File

@ -21,6 +21,16 @@ namespace Kyoo.Controllers
_serviceProvider = serviceProvider; _serviceProvider = serviceProvider;
} }
public void Dispose()
{
_database.Dispose();
}
public ValueTask DisposeAsync()
{
return _database.DisposeAsync();
}
public Task<Library> Get(int id) public Task<Library> Get(int id)
{ {
return _database.Libraries.FirstOrDefaultAsync(x => x.ID == id); return _database.Libraries.FirstOrDefaultAsync(x => x.ID == id);
@ -54,7 +64,18 @@ namespace Kyoo.Controllers
if (obj.ProviderLinks != null) if (obj.ProviderLinks != null)
foreach (ProviderLink entry in obj.ProviderLinks) foreach (ProviderLink entry in obj.ProviderLinks)
_database.Entry(entry).State = EntityState.Added; _database.Entry(entry).State = EntityState.Added;
try
{
await _database.SaveChangesAsync(); await _database.SaveChangesAsync();
}
catch (DbUpdateException ex)
{
if (Helper.IsDuplicateException(ex))
throw new DuplicatedItemException($"Trying to insert a duplicated library (slug {obj.Slug} already exists).");
throw;
}
return obj.ID; return obj.ID;
} }
@ -63,11 +84,21 @@ namespace Kyoo.Controllers
if (obj == null) if (obj == null)
throw new ArgumentNullException(nameof(obj)); throw new ArgumentNullException(nameof(obj));
Library old = await Get(obj.Name); Library old = await Get(obj.Slug);
if (old != null) if (old != null)
return old.ID; return old.ID;
try
{
return await Create(obj); return await Create(obj);
} }
catch (DuplicatedItemException)
{
old = await Get(obj.Slug);
if (old == null)
throw new SystemException("Unknown database state.");
return old.ID;
}
}
public async Task Edit(Library edited, bool resetOld) public async Task Edit(Library edited, bool resetOld)
{ {
@ -91,7 +122,7 @@ namespace Kyoo.Controllers
obj.ProviderLinks = (await Task.WhenAll(obj.ProviderLinks.Select(async x => obj.ProviderLinks = (await Task.WhenAll(obj.ProviderLinks.Select(async x =>
{ {
using IServiceScope serviceScope = _serviceProvider.CreateScope(); using IServiceScope serviceScope = _serviceProvider.CreateScope();
IProviderRepository providers = serviceScope.ServiceProvider.GetService<IProviderRepository>(); await using IProviderRepository providers = serviceScope.ServiceProvider.GetService<IProviderRepository>();
x.ProviderID = await providers.CreateIfNotExists(x.Provider); x.ProviderID = await providers.CreateIfNotExists(x.Provider);
return x; return x;

View File

@ -20,6 +20,16 @@ namespace Kyoo.Controllers
_serviceProvider = serviceProvider; _serviceProvider = serviceProvider;
} }
public void Dispose()
{
_database.Dispose();
}
public ValueTask DisposeAsync()
{
return _database.DisposeAsync();
}
public Task<People> Get(int id) public Task<People> Get(int id)
{ {
return _database.Peoples.FirstOrDefaultAsync(x => x.ID == id); return _database.Peoples.FirstOrDefaultAsync(x => x.ID == id);
@ -53,7 +63,18 @@ namespace Kyoo.Controllers
if (obj.ExternalIDs != null) if (obj.ExternalIDs != null)
foreach (MetadataID entry in obj.ExternalIDs) foreach (MetadataID entry in obj.ExternalIDs)
_database.Entry(entry).State = EntityState.Added; _database.Entry(entry).State = EntityState.Added;
try
{
await _database.SaveChangesAsync(); await _database.SaveChangesAsync();
}
catch (DbUpdateException ex)
{
if (Helper.IsDuplicateException(ex))
throw new DuplicatedItemException($"Trying to insert a duplicated people (slug {obj.Slug} already exists).");
throw;
}
return obj.ID; return obj.ID;
} }
@ -65,8 +86,18 @@ namespace Kyoo.Controllers
People old = await Get(obj.Slug); People old = await Get(obj.Slug);
if (old != null) if (old != null)
return old.ID; return old.ID;
try
{
return await Create(obj); return await Create(obj);
} }
catch (DuplicatedItemException)
{
old = await Get(obj.Slug);
if (old == null)
throw new SystemException("Unknown database state.");
return old.ID;
}
}
public async Task Edit(People edited, bool resetOld) public async Task Edit(People edited, bool resetOld)
{ {
@ -90,7 +121,7 @@ namespace Kyoo.Controllers
obj.ExternalIDs = (await Task.WhenAll(obj.ExternalIDs.Select(async x => obj.ExternalIDs = (await Task.WhenAll(obj.ExternalIDs.Select(async x =>
{ {
using IServiceScope serviceScope = _serviceProvider.CreateScope(); using IServiceScope serviceScope = _serviceProvider.CreateScope();
IProviderRepository providers = serviceScope.ServiceProvider.GetService<IProviderRepository>(); await using IProviderRepository providers = serviceScope.ServiceProvider.GetService<IProviderRepository>();
x.ProviderID = await providers.CreateIfNotExists(x.Provider); x.ProviderID = await providers.CreateIfNotExists(x.Provider);
return x; return x;

View File

@ -18,6 +18,16 @@ namespace Kyoo.Controllers
_database = database; _database = database;
} }
public void Dispose()
{
_database.Dispose();
}
public ValueTask DisposeAsync()
{
return _database.DisposeAsync();
}
public async Task<ProviderID> Get(int id) public async Task<ProviderID> Get(int id)
{ {
return await _database.Providers.FirstOrDefaultAsync(x => x.ID == id); return await _database.Providers.FirstOrDefaultAsync(x => x.ID == id);
@ -46,8 +56,19 @@ namespace Kyoo.Controllers
if (obj == null) if (obj == null)
throw new ArgumentNullException(nameof(obj)); throw new ArgumentNullException(nameof(obj));
await _database.Providers.AddAsync(obj); _database.Entry(obj).State = EntityState.Added;
try
{
await _database.SaveChangesAsync(); await _database.SaveChangesAsync();
}
catch (DbUpdateException ex)
{
if (Helper.IsDuplicateException(ex))
throw new DuplicatedItemException($"Trying to insert a duplicated provider (name {obj.Name} already exists).");
throw;
}
return obj.ID; return obj.ID;
} }
@ -59,8 +80,18 @@ namespace Kyoo.Controllers
ProviderID old = await Get(obj.Name); ProviderID old = await Get(obj.Name);
if (old != null) if (old != null)
return old.ID; return old.ID;
try
{
return await Create(obj); return await Create(obj);
} }
catch (DuplicatedItemException)
{
old = await Get(obj.Name);
if (old == null)
throw new SystemException("Unknown database state.");
return old.ID;
}
}
public async Task Edit(ProviderID edited, bool resetOld) public async Task Edit(ProviderID edited, bool resetOld)
{ {

View File

@ -21,6 +21,16 @@ namespace Kyoo.Controllers
_serviceProvider = serviceProvider; _serviceProvider = serviceProvider;
} }
public void Dispose()
{
_database.Dispose();
}
public ValueTask DisposeAsync()
{
return _database.DisposeAsync();
}
public async Task<Season> Get(int id) public async Task<Season> Get(int id)
{ {
return await _database.Seasons.FirstOrDefaultAsync(x => x.ID == id); return await _database.Seasons.FirstOrDefaultAsync(x => x.ID == id);
@ -66,7 +76,18 @@ namespace Kyoo.Controllers
if (obj.ExternalIDs != null) if (obj.ExternalIDs != null)
foreach (MetadataID entry in obj.ExternalIDs) foreach (MetadataID entry in obj.ExternalIDs)
_database.Entry(entry).State = EntityState.Added; _database.Entry(entry).State = EntityState.Added;
try
{
await _database.SaveChangesAsync(); await _database.SaveChangesAsync();
}
catch (DbUpdateException ex)
{
if (Helper.IsDuplicateException(ex))
throw new DuplicatedItemException($"Trying to insert a duplicated season (slug {obj.Slug} already exists).");
throw;
}
return obj.ID; return obj.ID;
} }
@ -78,8 +99,18 @@ namespace Kyoo.Controllers
Season old = await Get(obj.Slug); Season old = await Get(obj.Slug);
if (old != null) if (old != null)
return old.ID; return old.ID;
try
{
return await Create(obj); return await Create(obj);
} }
catch (DuplicatedItemException)
{
old = await Get(obj.Slug);
if (old == null)
throw new SystemException("Unknown database state.");
return old.ID;
}
}
public async Task Edit(Season edited, bool resetOld) public async Task Edit(Season edited, bool resetOld)
{ {
@ -109,7 +140,7 @@ namespace Kyoo.Controllers
obj.ExternalIDs = (await Task.WhenAll(obj.ExternalIDs.Select(async x => obj.ExternalIDs = (await Task.WhenAll(obj.ExternalIDs.Select(async x =>
{ {
using IServiceScope serviceScope = _serviceProvider.CreateScope(); using IServiceScope serviceScope = _serviceProvider.CreateScope();
IProviderRepository providers = serviceScope.ServiceProvider.GetService<IProviderRepository>(); await using IProviderRepository providers = serviceScope.ServiceProvider.GetService<IProviderRepository>();
x.ProviderID = await providers.CreateIfNotExists(x.Provider); x.ProviderID = await providers.CreateIfNotExists(x.Provider);
return x; return x;

View File

@ -24,6 +24,17 @@ namespace Kyoo.Controllers
_studios = studios; _studios = studios;
} }
public void Dispose()
{
_database.Dispose();
_studios.Dispose();
}
public async ValueTask DisposeAsync()
{
await Task.WhenAll(_database.DisposeAsync().AsTask(), _studios.DisposeAsync().AsTask());
}
public async Task<Show> Get(int id) public async Task<Show> Get(int id)
{ {
return await _database.Shows.FirstOrDefaultAsync(x => x.ID == id); return await _database.Shows.FirstOrDefaultAsync(x => x.ID == id);
@ -69,7 +80,18 @@ namespace Kyoo.Controllers
if (obj.ExternalIDs != null) if (obj.ExternalIDs != null)
foreach (MetadataID entry in obj.ExternalIDs) foreach (MetadataID entry in obj.ExternalIDs)
_database.Entry(entry).State = EntityState.Added; _database.Entry(entry).State = EntityState.Added;
try
{
await _database.SaveChangesAsync(); await _database.SaveChangesAsync();
}
catch (DbUpdateException ex)
{
if (Helper.IsDuplicateException(ex))
throw new DuplicatedItemException($"Trying to insert a duplicated show (slug {obj.Slug} already exists).");
throw;
}
return obj.ID; return obj.ID;
} }
@ -81,8 +103,18 @@ namespace Kyoo.Controllers
Show old = await Get(obj.Slug); Show old = await Get(obj.Slug);
if (old != null) if (old != null)
return old.ID; return old.ID;
try
{
return await Create(obj); return await Create(obj);
} }
catch (DuplicatedItemException)
{
old = await Get(obj.Slug);
if (old == null)
throw new SystemException("Unknown database state.");
return old.ID;
}
}
public async Task Edit(Show edited, bool resetOld) public async Task Edit(Show edited, bool resetOld)
{ {
@ -111,7 +143,7 @@ namespace Kyoo.Controllers
obj.GenreLinks = (await Task.WhenAll(obj.GenreLinks.Select(async x => obj.GenreLinks = (await Task.WhenAll(obj.GenreLinks.Select(async x =>
{ {
using IServiceScope serviceScope = _serviceProvider.CreateScope(); using IServiceScope serviceScope = _serviceProvider.CreateScope();
IGenreRepository genres = serviceScope.ServiceProvider.GetService<IGenreRepository>(); await using IGenreRepository genres = serviceScope.ServiceProvider.GetService<IGenreRepository>();
x.GenreID = await genres.CreateIfNotExists(x.Genre); x.GenreID = await genres.CreateIfNotExists(x.Genre);
return x; return x;
@ -123,7 +155,7 @@ namespace Kyoo.Controllers
obj.People = (await Task.WhenAll(obj.People.Select(async x => obj.People = (await Task.WhenAll(obj.People.Select(async x =>
{ {
using IServiceScope serviceScope = _serviceProvider.CreateScope(); using IServiceScope serviceScope = _serviceProvider.CreateScope();
IPeopleRepository people = serviceScope.ServiceProvider.GetService<IPeopleRepository>(); await using IPeopleRepository people = serviceScope.ServiceProvider.GetService<IPeopleRepository>();
x.PeopleID = await people.CreateIfNotExists(x.People); x.PeopleID = await people.CreateIfNotExists(x.People);
return x; return x;
@ -135,7 +167,7 @@ namespace Kyoo.Controllers
obj.ExternalIDs = (await Task.WhenAll(obj.ExternalIDs.Select(async x => obj.ExternalIDs = (await Task.WhenAll(obj.ExternalIDs.Select(async x =>
{ {
using IServiceScope serviceScope = _serviceProvider.CreateScope(); using IServiceScope serviceScope = _serviceProvider.CreateScope();
IProviderRepository providers = serviceScope.ServiceProvider.GetService<IProviderRepository>(); await using IProviderRepository providers = serviceScope.ServiceProvider.GetService<IProviderRepository>();
x.ProviderID = await providers.CreateIfNotExists(x.Provider); x.ProviderID = await providers.CreateIfNotExists(x.Provider);
return x; return x;

View File

@ -18,6 +18,16 @@ namespace Kyoo.Controllers
_database = database; _database = database;
} }
public void Dispose()
{
_database.Dispose();
}
public ValueTask DisposeAsync()
{
return _database.DisposeAsync();
}
public async Task<Studio> Get(int id) public async Task<Studio> Get(int id)
{ {
return await _database.Studios.FirstOrDefaultAsync(x => x.ID == id); return await _database.Studios.FirstOrDefaultAsync(x => x.ID == id);
@ -46,8 +56,18 @@ namespace Kyoo.Controllers
if (obj == null) if (obj == null)
throw new ArgumentNullException(nameof(obj)); throw new ArgumentNullException(nameof(obj));
await _database.Studios.AddAsync(obj); _database.Entry(obj).State = EntityState.Added;
try
{
await _database.SaveChangesAsync(); await _database.SaveChangesAsync();
}
catch (DbUpdateException ex)
{
if (Helper.IsDuplicateException(ex))
throw new DuplicatedItemException($"Trying to insert a duplicated studio (slug {obj.Slug} already exists).");
throw;
}
return obj.ID; return obj.ID;
} }
@ -59,8 +79,18 @@ namespace Kyoo.Controllers
Studio old = await Get(obj.Slug); Studio old = await Get(obj.Slug);
if (old != null) if (old != null)
return old.ID; return old.ID;
try
{
return await Create(obj); return await Create(obj);
} }
catch (DuplicatedItemException)
{
old = await Get(obj.Slug);
if (old == null)
throw new SystemException("Unknown database state.");
return old.ID;
}
}
public async Task Edit(Studio edited, bool resetOld) public async Task Edit(Studio edited, bool resetOld)
{ {

View File

@ -17,6 +17,16 @@ namespace Kyoo.Controllers
_database = database; _database = database;
} }
public void Dispose()
{
_database.Dispose();
}
public ValueTask DisposeAsync()
{
return _database.DisposeAsync();
}
public async Task<Track> Get(int id) public async Task<Track> Get(int id)
{ {
return await _database.Tracks.FirstOrDefaultAsync(x => x.ID == id); return await _database.Tracks.FirstOrDefaultAsync(x => x.ID == id);
@ -52,8 +62,18 @@ namespace Kyoo.Controllers
if (obj.EpisodeID <= 0) if (obj.EpisodeID <= 0)
throw new InvalidOperationException($"Can't store a track not related to any episode (episodeID: {obj.EpisodeID})."); throw new InvalidOperationException($"Can't store a track not related to any episode (episodeID: {obj.EpisodeID}).");
await _database.Tracks.AddAsync(obj); _database.Entry(obj).State = EntityState.Added;
try
{
await _database.SaveChangesAsync(); await _database.SaveChangesAsync();
}
catch (DbUpdateException ex)
{
if (Helper.IsDuplicateException(ex))
throw new DuplicatedItemException($"Trying to insert a duplicated track (slug {obj.Slug} already exists).");
throw;
}
return obj.ID; return obj.ID;
} }

View File

@ -49,21 +49,6 @@ namespace Kyoo
} }
public class DatabaseFactory
{
private readonly DbContextOptions<DatabaseContext> _options;
public DatabaseFactory(DbContextOptions<DatabaseContext> options)
{
_options = options;
}
public DatabaseContext NewDatabaseConnection()
{
return new DatabaseContext(_options);
}
}
public class DatabaseContext : DbContext public class DatabaseContext : DbContext
{ {
public DatabaseContext(DbContextOptions<DatabaseContext> options) : base(options) { } public DatabaseContext(DbContextOptions<DatabaseContext> options) : base(options) { }

View File

@ -43,16 +43,11 @@ namespace Kyoo
services.AddControllers().AddNewtonsoftJson(); services.AddControllers().AddNewtonsoftJson();
services.AddHttpClient(); services.AddHttpClient();
services.AddSingleton(x => new DatabaseFactory(
new DbContextOptionsBuilder<DatabaseContext>()
.UseLazyLoadingProxies()
.UseNpgsql(_configuration.GetConnectionString("Database")).Options));
services.AddDbContext<DatabaseContext>(options => services.AddDbContext<DatabaseContext>(options =>
{ {
options.UseLazyLoadingProxies() options.UseLazyLoadingProxies()
.UseNpgsql(_configuration.GetConnectionString("Database")); .UseNpgsql(_configuration.GetConnectionString("Database"))
// .EnableSensitiveDataLogging() .EnableSensitiveDataLogging();
// .UseLoggerFactory(LoggerFactory.Create(builder => builder.AddConsole())); // .UseLoggerFactory(LoggerFactory.Create(builder => builder.AddConsole()));
}); });

View File

@ -7,6 +7,7 @@ using System.Linq;
using System.Text.RegularExpressions; using System.Text.RegularExpressions;
using System.Threading; using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
using Kyoo.Models.Exceptions;
using Kyoo.Models.Watch; using Kyoo.Models.Watch;
using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.DependencyInjection;
@ -30,7 +31,7 @@ namespace Kyoo.Controllers
public async Task<IEnumerable<string>> GetPossibleParameters() public async Task<IEnumerable<string>> GetPossibleParameters()
{ {
using IServiceScope serviceScope = _serviceProvider.CreateScope(); using IServiceScope serviceScope = _serviceProvider.CreateScope();
ILibraryManager libraryManager = serviceScope.ServiceProvider.GetService<ILibraryManager>(); await using ILibraryManager libraryManager = serviceScope.ServiceProvider.GetService<ILibraryManager>();
return (await libraryManager.GetLibraries()).Select(x => x.Slug); return (await libraryManager.GetLibraries()).Select(x => x.Slug);
} }
@ -40,7 +41,9 @@ namespace Kyoo.Controllers
return null; return null;
} }
public async Task Run(IServiceProvider serviceProvider, CancellationToken cancellationToken, string argument = null) public async Task Run(IServiceProvider serviceProvider,
CancellationToken cancellationToken,
string argument = null)
{ {
_serviceProvider = serviceProvider; _serviceProvider = serviceProvider;
_thumbnailsManager = serviceProvider.GetService<IThumbnailsManager>(); _thumbnailsManager = serviceProvider.GetService<IThumbnailsManager>();
@ -51,7 +54,7 @@ namespace Kyoo.Controllers
try try
{ {
using IServiceScope serviceScope = _serviceProvider.CreateScope(); using IServiceScope serviceScope = _serviceProvider.CreateScope();
ILibraryManager libraryManager = serviceScope.ServiceProvider.GetService<ILibraryManager>(); await using ILibraryManager libraryManager = serviceScope.ServiceProvider.GetService<ILibraryManager>();
ICollection<Episode> episodes = await libraryManager.GetEpisodes(); ICollection<Episode> episodes = await libraryManager.GetEpisodes();
ICollection<Library> libraries = argument == null ICollection<Library> libraries = argument == null
? await libraryManager.GetLibraries() ? await libraryManager.GetLibraries()
@ -63,7 +66,11 @@ namespace Kyoo.Controllers
await libraryManager.DeleteEpisode(episode); await libraryManager.DeleteEpisode(episode);
} }
await Task.WhenAll(libraries.Select(x => Scan(x, episodes, cancellationToken))); // TODO replace this grotesque way to load the providers.
foreach (Library library in libraries)
library.Providers = library.Providers;
await Task.WhenAll(libraries.Select(x => Scan(x, episodes, cancellationToken)).ToArray());
} }
catch (Exception ex) catch (Exception ex)
{ {
@ -72,10 +79,10 @@ namespace Kyoo.Controllers
Console.WriteLine("Scan finished!"); Console.WriteLine("Scan finished!");
} }
private async Task Scan(Library library, ICollection<Episode> episodes, CancellationToken cancellationToken) private Task Scan(Library library, IEnumerable<Episode> episodes, CancellationToken cancellationToken)
{ {
Console.WriteLine($"Scanning library {library.Name} at {string.Join(", ", library.Paths)}."); Console.WriteLine($"Scanning library {library.Name} at {string.Join(", ", library.Paths)}.");
foreach (string path in library.Paths) return Task.WhenAll(library.Paths.Select(async path =>
{ {
if (cancellationToken.IsCancellationRequested) if (cancellationToken.IsCancellationRequested)
return; return;
@ -87,34 +94,40 @@ namespace Kyoo.Controllers
} }
catch (DirectoryNotFoundException) catch (DirectoryNotFoundException)
{ {
Console.Error.WriteLine($"The library's directory {path} could not be found (library slug: {library.Slug})"); await Console.Error.WriteLineAsync($"The library's directory {path} could not be found (library slug: {library.Slug})");
return; return;
} }
catch (PathTooLongException) catch (PathTooLongException)
{ {
Console.Error.WriteLine($"The library's directory {path} is too long for this system. (library slug: {library.Slug})"); await Console.Error.WriteLineAsync($"The library's directory {path} is too long for this system. (library slug: {library.Slug})");
return; return;
} }
catch (ArgumentException) catch (ArgumentException)
{ {
Console.Error.WriteLine($"The library's directory {path} is invalid. (library slug: {library.Slug})"); await Console.Error.WriteLineAsync($"The library's directory {path} is invalid. (library slug: {library.Slug})");
return; return;
} }
catch (UnauthorizedAccessException) catch (UnauthorizedAccessException)
{ {
Console.Error.WriteLine($"Permission denied: can't access library's directory at {path}. (library slug: {library.Slug})"); await Console.Error.WriteLineAsync($"Permission denied: can't access library's directory at {path}. (library slug: {library.Slug})");
return; return;
} }
// return Task.WhenAll(files.Select(file => List<IGrouping<string, string>> shows = files
foreach (string file in files) .Where(x => IsVideo(x) && episodes.All(y => y.Path != x))
{ .GroupBy(Path.GetDirectoryName)
if (!IsVideo(file) || episodes.Any(x => x.Path == file)) .ToList();
continue; //return Task.CompletedTask;
string relativePath = file.Substring(path.Length); await Task.WhenAll(shows
/*return*/ await RegisterFile(file, relativePath, library, cancellationToken); .Select(x => x.First())
}//)); .Select(x => RegisterFile(x, x.Substring(path.Length), library, cancellationToken))
}//)); .ToArray());
await Task.WhenAll(shows
.SelectMany(x => x.Skip(1))
.Select(x => RegisterFile(x, x.Substring(path.Length), library, cancellationToken))
.ToArray());
}).ToArray());
} }
private async Task RegisterFile(string path, string relativePath, Library library, CancellationToken token) private async Task RegisterFile(string path, string relativePath, Library library, CancellationToken token)
@ -123,7 +136,7 @@ namespace Kyoo.Controllers
return; return;
using IServiceScope serviceScope = _serviceProvider.CreateScope(); using IServiceScope serviceScope = _serviceProvider.CreateScope();
ILibraryManager libraryManager = serviceScope.ServiceProvider.GetService<ILibraryManager>(); await using ILibraryManager libraryManager = serviceScope.ServiceProvider.GetService<ILibraryManager>();
string patern = _config.GetValue<string>("regex"); string patern = _config.GetValue<string>("regex");
Regex regex = new Regex(patern, RegexOptions.IgnoreCase); Regex regex = new Regex(patern, RegexOptions.IgnoreCase);
@ -162,9 +175,17 @@ namespace Kyoo.Controllers
if (collection != null) if (collection != null)
return collection; return collection;
collection = await _metadataProvider.GetCollectionFromName(collectionName, library); collection = await _metadataProvider.GetCollectionFromName(collectionName, library);
try
{
await libraryManager.RegisterCollection(collection); await libraryManager.RegisterCollection(collection);
return collection; return collection;
} }
catch (DuplicatedItemException)
{
return await libraryManager.GetCollection(collection.Slug);
}
}
private async Task<Show> GetShow(ILibraryManager libraryManager, private async Task<Show> GetShow(ILibraryManager libraryManager,
string showTitle, string showTitle,
@ -178,11 +199,19 @@ namespace Kyoo.Controllers
show = await _metadataProvider.SearchShow(showTitle, isMovie, library); show = await _metadataProvider.SearchShow(showTitle, isMovie, library);
show.Path = showPath; show.Path = showPath;
show.People = await _metadataProvider.GetPeople(show, library); show.People = await _metadataProvider.GetPeople(show, library);
try
{
await libraryManager.RegisterShow(show); await libraryManager.RegisterShow(show);
await _thumbnailsManager.Validate(show.People); await _thumbnailsManager.Validate(show.People);
await _thumbnailsManager.Validate(show); await _thumbnailsManager.Validate(show);
return show; return show;
} }
catch (DuplicatedItemException)
{
return await libraryManager.GetShow(show.Slug);
}
}
private async Task<Season> GetSeason(ILibraryManager libraryManager, private async Task<Season> GetSeason(ILibraryManager libraryManager,
Show show, Show show,
@ -195,9 +224,16 @@ namespace Kyoo.Controllers
if (season == null) if (season == null)
{ {
season = await _metadataProvider.GetSeason(show, seasonNumber, library); season = await _metadataProvider.GetSeason(show, seasonNumber, library);
try
{
await libraryManager.RegisterSeason(season); await libraryManager.RegisterSeason(season);
await _thumbnailsManager.Validate(season); await _thumbnailsManager.Validate(season);
} }
catch (DuplicatedItemException)
{
season = await libraryManager.GetSeason(show.Slug, season.SeasonNumber);
}
}
season.Show = show; season.Show = show;
return season; return season;
} }

View File

@ -7,12 +7,14 @@
"LogLevel": { "LogLevel": {
"Default": "Warning", "Default": "Warning",
"Microsoft": "Warning", "Microsoft": "Warning",
"Microsoft.Hosting.Lifetime": "Information" "Microsoft.Hosting.Lifetime": "Information",
"Microsoft.EntityFrameworkCore.DbUpdateException": "None",
"Microsoft.EntityFrameworkCore.Update": "None"
} }
}, },
"AllowedHosts": "*", "AllowedHosts": "*",
"ConnectionStrings": { "ConnectionStrings": {
"Database": "Server=127.0.0.1; Port=5432; Database=kyooDB; User Id=kyoo; Password=kyooPassword;" "Database": "Server=127.0.0.1; Port=5432; Database=kyooDB; User Id=kyoo; Password=kyooPassword; Pooling=true; MaxPoolSize=60;"
}, },
"scheduledTasks": { "scheduledTasks": {