Fixing tracks editing

This commit is contained in:
Zoe Roux 2021-03-15 23:52:11 +01:00
parent d38926924c
commit d2a38c9b3d
7 changed files with 59 additions and 13 deletions

View File

@ -27,9 +27,9 @@ namespace Kyoo.Models
public int Runtime { get; set; } //This runtime variable should be in minutes public int Runtime { get; set; } //This runtime variable should be in minutes
[LoadableRelation] public virtual ICollection<MetadataID> ExternalIDs { get; set; } [EditableRelation] [LoadableRelation] public virtual ICollection<MetadataID> ExternalIDs { get; set; }
[LoadableRelation] public virtual ICollection<Track> Tracks { get; set; } [EditableRelation] [LoadableRelation] public virtual ICollection<Track> Tracks { get; set; }
public Episode() { } public Episode() { }

View File

@ -143,7 +143,7 @@ namespace Kyoo
? Activator.CreateInstance(property.PropertyType) ? Activator.CreateInstance(property.PropertyType)
: null; : null;
if (value?.Equals(defaultValue) == false) if (value?.Equals(defaultValue) == false && value != property.GetValue(first))
property.SetValue(first, value); property.SetValue(first, value);
} }
@ -304,6 +304,22 @@ namespace Kyoo
action(i); action(i);
} }
public static async Task ForEachAsync<T>([CanBeNull] this IEnumerable<T> self, Func<T, Task> action)
{
if (self == null)
return;
foreach (T i in self)
await action(i);
}
public static async Task ForEachAsync<T>([CanBeNull] this IAsyncEnumerable<T> self, Action<T> action)
{
if (self == null)
return;
await foreach (T i in self)
action(i);
}
private static MethodInfo GetMethod(Type type, BindingFlags flag, string name, Type[] generics, object[] args) private static MethodInfo GetMethod(Type type, BindingFlags flag, string name, Type[] generics, object[] args)
{ {
MethodInfo[] methods = type.GetMethods(flag | BindingFlags.Public | BindingFlags.NonPublic) MethodInfo[] methods = type.GetMethods(flag | BindingFlags.Public | BindingFlags.NonPublic)

View File

@ -44,6 +44,11 @@ namespace Kyoo.Controllers
return Database.Set<T>().FirstOrDefaultAsync(x => x.ID == id); return Database.Set<T>().FirstOrDefaultAsync(x => x.ID == id);
} }
public virtual Task<T> GetWithTracking(int id)
{
return Database.Set<T>().AsTracking().FirstOrDefaultAsync(x => x.ID == id);
}
public virtual Task<T> Get(string slug) public virtual Task<T> Get(string slug)
{ {
return Database.Set<T>().FirstOrDefaultAsync(x => x.Slug == slug); return Database.Set<T>().FirstOrDefaultAsync(x => x.Slug == slug);
@ -159,7 +164,7 @@ namespace Kyoo.Controllers
Database.ChangeTracker.LazyLoadingEnabled = false; Database.ChangeTracker.LazyLoadingEnabled = false;
try try
{ {
T old = await Get(edited.ID); T old = await GetWithTracking(edited.ID);
if (old == null) if (old == null)
throw new ItemNotFound($"No resource found with the ID {edited.ID}."); throw new ItemNotFound($"No resource found with the ID {edited.ID}.");
@ -180,8 +185,8 @@ namespace Kyoo.Controllers
await navigation.LoadAsync(); await navigation.LoadAsync();
// TODO this may be usless for lists since the API does not return IDs but the // TODO this may be usless for lists since the API does not return IDs but the
// TODO LinkEquality does not check slugs (their are lazy loaded and only the ID is available) // TODO LinkEquality does not check slugs (their are lazy loaded and only the ID is available)
if (Utility.ResourceEquals(getter.GetClrValue(edited), getter.GetClrValue(old))) // if (Utility.ResourceEquals(getter.GetClrValue(edited), getter.GetClrValue(old)))
navigation.Metadata.PropertyInfo.SetValue(edited, default); // navigation.Metadata.PropertyInfo.SetValue(edited, default);
} }
else else
navigation.Metadata.PropertyInfo.SetValue(edited, default); navigation.Metadata.PropertyInfo.SetValue(edited, default);

View File

@ -15,15 +15,20 @@ namespace Kyoo.Controllers
private readonly DatabaseContext _database; private readonly DatabaseContext _database;
private readonly IProviderRepository _providers; private readonly IProviderRepository _providers;
private readonly IShowRepository _shows; private readonly IShowRepository _shows;
private readonly ITrackRepository _tracks;
protected override Expression<Func<Episode, object>> DefaultSort => x => x.EpisodeNumber; protected override Expression<Func<Episode, object>> DefaultSort => x => x.EpisodeNumber;
public EpisodeRepository(DatabaseContext database, IProviderRepository providers, IShowRepository shows) public EpisodeRepository(DatabaseContext database,
IProviderRepository providers,
IShowRepository shows,
ITrackRepository tracks)
: base(database) : base(database)
{ {
_database = database; _database = database;
_providers = providers; _providers = providers;
_shows = shows; _shows = shows;
_tracks = tracks;
} }
@ -161,6 +166,14 @@ namespace Kyoo.Controllers
await base.Validate(resource); await base.Validate(resource);
if (resource.Tracks != null)
{
// TODO remove old values
resource.Tracks = await resource.Tracks
.SelectAsync(x => _tracks.CreateIfNotExists(x, true))
.ToListAsync();
}
if (resource.ExternalIDs != null) if (resource.ExternalIDs != null)
{ {
foreach (MetadataID link in resource.ExternalIDs) foreach (MetadataID link in resource.ExternalIDs)
@ -181,10 +194,10 @@ namespace Kyoo.Controllers
throw new ArgumentNullException(nameof(obj)); throw new ArgumentNullException(nameof(obj));
_database.Entry(obj).State = EntityState.Deleted; _database.Entry(obj).State = EntityState.Deleted;
await obj.Tracks.ForEachAsync(x => _tracks.CreateIfNotExists(x, true));
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.Deleted; _database.Entry(entry).State = EntityState.Deleted;
// Since Tracks & Episodes are on the same database and handled by dotnet-ef, we can't use the repository to delete them.
await _database.SaveChangesAsync(); await _database.SaveChangesAsync();
} }
} }

View File

@ -36,7 +36,12 @@ namespace Kyoo.Controllers
await _database.DisposeAsync(); await _database.DisposeAsync();
} }
public Task<Track> Get(string slug, StreamType type = StreamType.Unknown) public override Task<Track> Get(string slug)
{
return Get(slug, StreamType.Unknown);
}
public Task<Track> Get(string slug, StreamType type)
{ {
Match match = Regex.Match(slug, Match match = Regex.Match(slug,
@"(?<show>.*)-s(?<season>\d+)e(?<episode>\d+)\.(?<language>.{0,3})(?<forced>-forced)?(\..*)?"); @"(?<show>.*)-s(?<season>\d+)e(?<episode>\d+)\.(?<language>.{0,3})(?<forced>-forced)?(\..*)?");
@ -93,6 +98,8 @@ namespace Kyoo.Controllers
return obj; return obj;
} }
protected override Task Validate(Track resource) protected override Task Validate(Track resource)
{ {
return Task.CompletedTask; return Task.CompletedTask;

View File

@ -12,7 +12,10 @@ namespace Kyoo
{ {
public class DatabaseContext : DbContext public class DatabaseContext : DbContext
{ {
public DatabaseContext(DbContextOptions<DatabaseContext> options) : base(options) { } public DatabaseContext(DbContextOptions<DatabaseContext> options) : base(options)
{
ChangeTracker.QueryTrackingBehavior = QueryTrackingBehavior.NoTracking;
}
public DbSet<Library> Libraries { get; set; } public DbSet<Library> Libraries { get; set; }
public DbSet<Collection> Collections { get; set; } public DbSet<Collection> Collections { get; set; }
@ -35,6 +38,8 @@ namespace Kyoo
NpgsqlConnection.GlobalTypeMapper.MapEnum<Status>(); NpgsqlConnection.GlobalTypeMapper.MapEnum<Status>();
NpgsqlConnection.GlobalTypeMapper.MapEnum<ItemType>(); NpgsqlConnection.GlobalTypeMapper.MapEnum<ItemType>();
NpgsqlConnection.GlobalTypeMapper.MapEnum<StreamType>(); NpgsqlConnection.GlobalTypeMapper.MapEnum<StreamType>();
ChangeTracker.QueryTrackingBehavior = QueryTrackingBehavior.NoTracking;
} }
protected override void OnModelCreating(ModelBuilder modelBuilder) protected override void OnModelCreating(ModelBuilder modelBuilder)

View File

@ -58,9 +58,9 @@ namespace Kyoo
services.AddDbContext<DatabaseContext>(options => services.AddDbContext<DatabaseContext>(options =>
{ {
options.UseNpgsql(_configuration.GetConnectionString("Database")); options.UseNpgsql(_configuration.GetConnectionString("Database"))
// .EnableSensitiveDataLogging() .EnableSensitiveDataLogging()
// .UseLoggerFactory(LoggerFactory.Create(builder => builder.AddConsole())); .UseLoggerFactory(LoggerFactory.Create(builder => builder.AddConsole()));
}, ServiceLifetime.Transient); }, ServiceLifetime.Transient);
services.AddDbContext<IdentityDatabase>(options => services.AddDbContext<IdentityDatabase>(options =>