diff --git a/Kyoo.Common/Models/Attributes/RelationAttributes.cs b/Kyoo.Common/Models/Attributes/RelationAttributes.cs index aac0e633..fedfc6db 100644 --- a/Kyoo.Common/Models/Attributes/RelationAttributes.cs +++ b/Kyoo.Common/Models/Attributes/RelationAttributes.cs @@ -17,4 +17,15 @@ namespace Kyoo.Models.Attributes RelationID = relationID; } } + + [AttributeUsage(AttributeTargets.Property)] + public class LinkRelationAttribute : Attribute + { + public string Relation { get; } + + public LinkRelationAttribute(string relation) + { + Relation = relation; + } + } } \ No newline at end of file diff --git a/Kyoo.Common/Models/Resources/Show.cs b/Kyoo.Common/Models/Resources/Show.cs index 7b948b55..f77bfc2e 100644 --- a/Kyoo.Common/Models/Resources/Show.cs +++ b/Kyoo.Common/Models/Resources/Show.cs @@ -37,7 +37,8 @@ namespace Kyoo.Models [LoadableRelation] public virtual ICollection Collections { get; set; } #if ENABLE_INTERNAL_LINKS - [SerializeIgnore] public virtual ICollection> LibraryLinks { get; set; } + [LinkRelation(nameof(Libraries))] [SerializeIgnore] + public virtual ICollection> LibraryLinks { get; set; } [SerializeIgnore] public virtual ICollection> CollectionLinks { get; set; } [SerializeIgnore] public virtual ICollection> GenreLinks { get; set; } #endif diff --git a/Kyoo.CommonAPI/JsonSerializer.cs b/Kyoo.CommonAPI/JsonSerializer.cs index ce19ba29..c9238049 100644 --- a/Kyoo.CommonAPI/JsonSerializer.cs +++ b/Kyoo.CommonAPI/JsonSerializer.cs @@ -132,7 +132,7 @@ namespace Kyoo.Controllers public void SetValue(object target, object value) { - throw new NotImplementedException(); + // Values are ignored and should not be editable, except if the internal value is set. } } } \ No newline at end of file diff --git a/Kyoo.CommonAPI/LocalRepository.cs b/Kyoo.CommonAPI/LocalRepository.cs index 0b6f2572..489f9742 100644 --- a/Kyoo.CommonAPI/LocalRepository.cs +++ b/Kyoo.CommonAPI/LocalRepository.cs @@ -1,4 +1,5 @@ using System; +using System.Collections; using System.Collections.Generic; using System.Linq; using System.Linq.Expressions; @@ -153,6 +154,7 @@ namespace Kyoo.Controllers throw; } } + public virtual async Task Edit(T edited, bool resetOld) { @@ -168,47 +170,77 @@ namespace Kyoo.Controllers if (old == null) throw new ItemNotFound($"No resource found with the ID {edited.ID}."); - // foreach (PropertyInfo navigation in typeof(T).GetProperties() - // .Where(x => x.GetCustomAttribute() != null)) - // { - // if (navigation.GetCustomAttribute() == null) - // { - // navigation.SetValue(edited, default); - // continue; - // } - // - // 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 - // list.ForEach(x => _library.Value.Delete(x)); - // else if (value is IResource resource) - // _library.Value.Delete(resource); - // } - // } - foreach (NavigationEntry navigation in Database.Entry(old).Navigations) + List navigations = typeof(T).GetProperties() + .Where(x => x.GetCustomAttribute() != null) + .ToList(); + List links = typeof(T).GetProperties() + .Select(x => x.GetCustomAttribute()?.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) { - if (navigation.Metadata.PropertyInfo.GetCustomAttribute() != null) - { - if (resetOld) - { - await navigation.LoadAsync(); - continue; - } - IClrPropertyGetter getter = navigation.Metadata.GetGetter(); + PropertyInfo relation = navigations.Find(x => x.Name == relationName); + navigations.Remove(relation); - if (getter.HasDefaultValue(edited)) - continue; - await navigation.LoadAsync(); - // 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) - // if (Utility.ResourceEquals(getter.GetClrValue(edited), getter.GetClrValue(old))) - // navigation.Metadata.PropertyInfo.SetValue(edited, default); + if (relation!.GetValue(edited) == null && !resetOld) + continue; + + await _library.Load(old, relation.Name); + IEnumerable oValues = (relation.GetValue(old) as IEnumerable)!.Cast(); + List nValues = (relation.GetValue(edited) as IEnumerable)?.Cast().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() == null) + { + navigation.SetValue(edited, default); + continue; } - else - navigation.Metadata.PropertyInfo.SetValue(edited, default); } if (resetOld) diff --git a/Kyoo/Models/DatabaseContext.cs b/Kyoo/Models/DatabaseContext.cs index 6122d40a..125f0ed8 100644 --- a/Kyoo/Models/DatabaseContext.cs +++ b/Kyoo/Models/DatabaseContext.cs @@ -15,6 +15,7 @@ namespace Kyoo public DatabaseContext(DbContextOptions options) : base(options) { ChangeTracker.QueryTrackingBehavior = QueryTrackingBehavior.NoTracking; + ChangeTracker.LazyLoadingEnabled = false; } public DbSet Libraries { get; set; } @@ -40,6 +41,7 @@ namespace Kyoo NpgsqlConnection.GlobalTypeMapper.MapEnum(); ChangeTracker.QueryTrackingBehavior = QueryTrackingBehavior.NoTracking; + ChangeTracker.LazyLoadingEnabled = false; } protected override void OnModelCreating(ModelBuilder modelBuilder)