Starting edits in a simple way

This commit is contained in:
Zoe Roux 2021-03-16 02:33:31 +01:00
parent 27c89ccc85
commit 2317445053
5 changed files with 33 additions and 111 deletions

View File

@ -17,15 +17,4 @@ namespace Kyoo.Models.Attributes
RelationID = relationID; RelationID = relationID;
} }
} }
[AttributeUsage(AttributeTargets.Property)]
public class LinkRelationAttribute : Attribute
{
public string Relation { get; }
public LinkRelationAttribute(string relation)
{
Relation = relation;
}
}
} }

View File

@ -37,8 +37,7 @@ namespace Kyoo.Models
[LoadableRelation] public virtual ICollection<Collection> Collections { get; set; } [LoadableRelation] public virtual ICollection<Collection> Collections { get; set; }
#if ENABLE_INTERNAL_LINKS #if ENABLE_INTERNAL_LINKS
[LinkRelation(nameof(Libraries))] [SerializeIgnore] [SerializeIgnore] public virtual ICollection<Link<Library, Show>> LibraryLinks { get; set; }
public virtual ICollection<Link<Library, Show>> LibraryLinks { get; set; }
[SerializeIgnore] public virtual ICollection<Link<Collection, Show>> CollectionLinks { get; set; } [SerializeIgnore] public virtual ICollection<Link<Collection, Show>> CollectionLinks { get; set; }
[SerializeIgnore] public virtual ICollection<Link<Show, Genre>> GenreLinks { get; set; } [SerializeIgnore] public virtual ICollection<Link<Show, Genre>> GenreLinks { get; set; }
#endif #endif

View File

@ -124,7 +124,7 @@ namespace Kyoo
return first; return first;
} }
public static T Complete<T>(T first, T second) public static T Complete<T>(T first, T second, Func<PropertyInfo, bool> ignore = null)
{ {
if (first == null) if (first == null)
throw new ArgumentNullException(nameof(first)); throw new ArgumentNullException(nameof(first));
@ -136,6 +136,9 @@ namespace Kyoo
.Where(x => x.CanRead && x.CanWrite .Where(x => x.CanRead && x.CanWrite
&& Attribute.GetCustomAttribute(x, typeof(NotMergableAttribute)) == null); && Attribute.GetCustomAttribute(x, typeof(NotMergableAttribute)) == null);
if (ignore != null)
properties = properties.Where(ignore);
foreach (PropertyInfo property in properties) foreach (PropertyInfo property in properties)
{ {
object value = property.GetValue(second); object value = property.GetValue(second);
@ -253,11 +256,11 @@ namespace Kyoo
return types.FirstOrDefault(x => x.IsGenericType && x.GetGenericTypeDefinition() == genericType); return types.FirstOrDefault(x => x.IsGenericType && x.GetGenericTypeDefinition() == genericType);
} }
public static async IAsyncEnumerable<T2> SelectAsync<T, T2>([NotNull] this IEnumerable<T> self, public static async IAsyncEnumerable<T2> SelectAsync<T, T2>([CanBeNull] this IEnumerable<T> self,
[NotNull] Func<T, Task<T2>> mapper) [NotNull] Func<T, Task<T2>> mapper)
{ {
if (self == null) if (self == null)
throw new ArgumentNullException(nameof(self)); yield break;
if (mapper == null) if (mapper == null)
throw new ArgumentNullException(nameof(mapper)); throw new ArgumentNullException(nameof(mapper));

View File

@ -1,5 +1,4 @@
using System; using System;
using System.Collections;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
using System.Linq.Expressions; using System.Linq.Expressions;
@ -10,8 +9,6 @@ using Kyoo.Models;
using Kyoo.Models.Attributes; using Kyoo.Models.Attributes;
using Kyoo.Models.Exceptions; using Kyoo.Models.Exceptions;
using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.ChangeTracking;
using Microsoft.EntityFrameworkCore.Metadata;
namespace Kyoo.Controllers namespace Kyoo.Controllers
{ {
@ -155,7 +152,6 @@ namespace Kyoo.Controllers
} }
} }
public virtual async Task<T> Edit(T edited, bool resetOld) public virtual async Task<T> Edit(T edited, bool resetOld)
{ {
if (edited == null) if (edited == null)
@ -170,83 +166,11 @@ namespace Kyoo.Controllers
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}.");
List<PropertyInfo> navigations = typeof(T).GetProperties()
.Where(x => x.GetCustomAttribute<EditableRelationAttribute>() != null)
.ToList();
List<string> links = typeof(T).GetProperties()
.Select(x => x.GetCustomAttribute<LinkRelationAttribute>()?.Relation)
.Where(x => x != null)
.ToList();
// This handle every links so every Many to Many
// TODO should handle non links value (One to Many)
foreach (string relationName in links)
{
PropertyInfo relation = navigations.Find(x => x.Name == relationName);
navigations.Remove(relation);
if (relation!.GetValue(edited) == null && !resetOld)
continue;
await _library.Load(old, relation.Name);
IEnumerable<IResource> oValues = (relation.GetValue(old) as IEnumerable)!.Cast<IResource>();
List<IResource> nValues = (relation.GetValue(edited) as IEnumerable)?.Cast<IResource>().ToList();
foreach (IResource x in oValues)
{
int nIndex = nValues?.FindIndex(y => Utility.ResourceEquals(x, y)) ?? -1;
if (nIndex == -1 || resetOld)
await _linkManager.Delete(old, x);
else
nValues!.RemoveAt(nIndex);
}
await nValues.ForEachAsync(x => _linkManager.Add(old, x));
}
// This handle every X to One
foreach (PropertyInfo relation in navigations)
{
IResource oValues = relation.GetValue(old) as IResource;
IResource nValues = relation.GetValue(edited) as IResource;
if (nValues == null && !resetOld)
continue;
if (false) // TODO change if false with if relation is casquade delete
await _library.Delete(oValue);
relation.SetValue(old, nValues);
// TODO call create if not exist
// TODO change relationID to the new value ID.
}
// This handle every One to Many or Many to One
foreach (PropertyInfo navigation in navigations)
{
if (resetOld || navigation.GetValue(edited) != default)
{
// TODO only works for X To One and not X to Many
await _library.Value.Load(old, navigation.Name);
object value = navigation.GetValue(old);
if (value is IEnumerable list) // TODO handle externalIds & PeopleRoles (implement Link<> for those)
list.ForEach(x => _library.Value.Delete(x));
else if (value is IResource resource)
_library.Value.Delete(resource);
}
if (navigation.GetCustomAttribute<EditableRelationAttribute>() == null)
{
navigation.SetValue(edited, default);
continue;
}
}
await EditRelations(old, edited);
if (resetOld) if (resetOld)
Utility.Nullify(old); Utility.Nullify(old);
Utility.Complete(old, edited); Utility.Complete(old, edited, x => x.GetCustomAttribute<EditableRelationAttribute>() != null);
await Validate(old);
await Database.SaveChangesAsync(); await Database.SaveChangesAsync();
return old; return old;
} }
@ -256,12 +180,12 @@ namespace Kyoo.Controllers
} }
} }
protected bool ShouldValidate<T2>(T2 value) protected virtual Task EditRelations(T resource, T newValues)
{ {
return value != null && Database.Entry(value).State == EntityState.Detached; return Validate(resource);
} }
protected virtual Task Validate(T resource) private Task Validate(T resource)
{ {
if (string.IsNullOrEmpty(resource.Slug)) if (string.IsNullOrEmpty(resource.Slug))
throw new ArgumentException("Resource can't have null as a slug."); throw new ArgumentException("Resource can't have null as a slug.");

View File

@ -1,4 +1,5 @@
using System; using System;
using System.Collections;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
using System.Linq.Expressions; using System.Linq.Expressions;
@ -153,25 +154,31 @@ namespace Kyoo.Controllers
{ {
await base.Create(obj); await base.Create(obj);
_database.Entry(obj).State = EntityState.Added; _database.Entry(obj).State = EntityState.Added;
obj.Tracks = await obj.Tracks.SelectAsync(x => _tracks.CreateIfNotExists(x, true)).ToListAsync();
obj.ExternalIDs.ForEach(x => _database.Entry(x).State = EntityState.Added); obj.ExternalIDs.ForEach(x => _database.Entry(x).State = EntityState.Added);
obj.Tracks.ForEach(x => _database.Entry(x).State = EntityState.Added);
await _database.SaveChangesAsync($"Trying to insert a duplicated episode (slug {obj.Slug} already exists)."); await _database.SaveChangesAsync($"Trying to insert a duplicated episode (slug {obj.Slug} already exists).");
return obj; return obj;
} }
protected override async Task Validate(Episode resource) protected override async Task EditRelations(Episode resource, Episode changed)
{ {
if (resource.ShowID <= 0) if (resource.ShowID <= 0)
throw new InvalidOperationException($"Can't store an episode not related to any show (showID: {resource.ShowID})."); throw new InvalidOperationException($"Can't store an episode not related to any show (showID: {resource.ShowID}).");
await base.Validate(resource); await base.EditRelations(resource, changed);
// if (resource.Tracks != null) ICollection<Track> oldTracks = resource.Tracks;
// { resource.Tracks = await changed.Tracks.SelectAsync(async track =>
// resource.Tracks = await resource.Tracks {
// .SelectAsync(x => _tracks.CreateIfNotExists(x, true)) Track oldValue = oldTracks?.FirstOrDefault(x => Utility.ResourceEquals(track, x));
// .ToListAsync(); if (oldValue == null)
// } return await _tracks.CreateIfNotExists(track, true);
oldTracks.Remove(oldValue);
return oldValue;
})
.ToListAsync();
foreach (Track x in oldTracks)
await _tracks.Delete(x);
if (resource.ExternalIDs != null) if (resource.ExternalIDs != null)
{ {