diff --git a/Kyoo.Common/Controllers/ILibraryManager.cs b/Kyoo.Common/Controllers/ILibraryManager.cs index 2a700ddc..23123d36 100644 --- a/Kyoo.Common/Controllers/ILibraryManager.cs +++ b/Kyoo.Common/Controllers/ILibraryManager.cs @@ -43,7 +43,7 @@ namespace Kyoo.Controllers Task GetSeason(string showSlug, int seasonNumber); Task GetEpisode(string showSlug, int seasonNumber, int episodeNumber); Task GetMovieEpisode(string movieSlug); - Task GetTrack(string slug, StreamType type = StreamType.Unknow); + Task GetTrack(string slug, StreamType type = StreamType.Unknown); Task GetGenre(string slug); Task GetStudio(string slug); Task GetPeople(string slug); diff --git a/Kyoo.Common/Controllers/IRepository.cs b/Kyoo.Common/Controllers/IRepository.cs index b5d527b3..f741de7b 100644 --- a/Kyoo.Common/Controllers/IRepository.cs +++ b/Kyoo.Common/Controllers/IRepository.cs @@ -95,20 +95,7 @@ namespace Kyoo.Controllers Task Create([NotNull] T obj); - Task CreateIfNotExists([NotNull] T obj); - async Task CreateIfNotExists([NotNull] T obj, bool silentFail) - { - try - { - return await CreateIfNotExists(obj); - } - catch - { - if (!silentFail) - throw; - return null; - } - } + Task CreateIfNotExists([NotNull] T obj, bool silentFail = false); Task Edit([NotNull] T edited, bool resetOld); Task Delete(int id); @@ -147,7 +134,7 @@ namespace Kyoo.Controllers public interface ITrackRepository : IRepository { - Task Get(string slug, StreamType type = StreamType.Unknow); + Task Get(string slug, StreamType type = StreamType.Unknown); } public interface ILibraryRepository : IRepository { } diff --git a/Kyoo.Common/Controllers/Implementations/LibraryManager.cs b/Kyoo.Common/Controllers/Implementations/LibraryManager.cs index 4cce6dd1..9789f77f 100644 --- a/Kyoo.Common/Controllers/Implementations/LibraryManager.cs +++ b/Kyoo.Common/Controllers/Implementations/LibraryManager.cs @@ -110,7 +110,7 @@ namespace Kyoo.Controllers return EpisodeRepository.Get(showID, seasonNumber, episodeNumber); } - public Task GetTrack(string slug, StreamType type = StreamType.Unknow) + public Task GetTrack(string slug, StreamType type = StreamType.Unknown) { return TrackRepository.Get(slug, type); } diff --git a/Kyoo.Common/Models/Attributes/EditableRelation.cs b/Kyoo.Common/Models/Attributes/EditableRelation.cs new file mode 100644 index 00000000..78b479e9 --- /dev/null +++ b/Kyoo.Common/Models/Attributes/EditableRelation.cs @@ -0,0 +1,6 @@ +using System; + +namespace Kyoo.Models.Attributes +{ + public class EditableRelation : Attribute { } +} \ No newline at end of file diff --git a/Kyoo.Common/Models/Resources/Episode.cs b/Kyoo.Common/Models/Resources/Episode.cs index 79229631..d596b6e1 100644 --- a/Kyoo.Common/Models/Resources/Episode.cs +++ b/Kyoo.Common/Models/Resources/Episode.cs @@ -23,7 +23,7 @@ namespace Kyoo.Models public int Runtime { get; set; } //This runtime variable should be in minutes [JsonIgnore] public string Poster { get; set; } - public virtual IEnumerable ExternalIDs { get; set; } + [EditableRelation] public virtual IEnumerable ExternalIDs { get; set; } [JsonIgnore] public virtual IEnumerable Tracks { get; set; } diff --git a/Kyoo.Common/Models/Resources/Library.cs b/Kyoo.Common/Models/Resources/Library.cs index dfb17da7..765d48d3 100644 --- a/Kyoo.Common/Models/Resources/Library.cs +++ b/Kyoo.Common/Models/Resources/Library.cs @@ -10,7 +10,7 @@ namespace Kyoo.Models public string Name { get; set; } public IEnumerable Paths { get; set; } - public virtual IEnumerable Providers { get; set; } + [EditableRelation] public virtual IEnumerable Providers { get; set; } [JsonIgnore] public virtual IEnumerable Shows { get; set; } [JsonIgnore] public virtual IEnumerable Collections { get; set; } diff --git a/Kyoo.Common/Models/Resources/People.cs b/Kyoo.Common/Models/Resources/People.cs index 44c1ad63..5b8828b8 100644 --- a/Kyoo.Common/Models/Resources/People.cs +++ b/Kyoo.Common/Models/Resources/People.cs @@ -9,9 +9,9 @@ namespace Kyoo.Models public string Slug { get; set; } public string Name { get; set; } public string Poster { get; set; } - public virtual IEnumerable ExternalIDs { get; set; } + [EditableRelation] public virtual IEnumerable ExternalIDs { get; set; } - [JsonReadOnly] public virtual IEnumerable Roles { get; set; } + [EditableRelation] [JsonReadOnly] public virtual IEnumerable Roles { get; set; } public People() {} diff --git a/Kyoo.Common/Models/Resources/Season.cs b/Kyoo.Common/Models/Resources/Season.cs index b0deac23..9bdd2f10 100644 --- a/Kyoo.Common/Models/Resources/Season.cs +++ b/Kyoo.Common/Models/Resources/Season.cs @@ -16,7 +16,7 @@ namespace Kyoo.Models public int? Year { get; set; } [JsonIgnore] public string Poster { get; set; } - public virtual IEnumerable ExternalIDs { get; set; } + [EditableRelation] public virtual IEnumerable ExternalIDs { get; set; } [JsonIgnore] public virtual Show Show { get; set; } [JsonIgnore] public virtual IEnumerable Episodes { get; set; } diff --git a/Kyoo.Common/Models/Resources/Show.cs b/Kyoo.Common/Models/Resources/Show.cs index 4e1705c1..3ecd8c5e 100644 --- a/Kyoo.Common/Models/Resources/Show.cs +++ b/Kyoo.Common/Models/Resources/Show.cs @@ -28,9 +28,9 @@ namespace Kyoo.Models [JsonIgnore] public int? StudioID { get; set; } - [JsonReadOnly] public virtual Studio Studio { get; set; } - [JsonReadOnly] public virtual IEnumerable Genres { get; set; } - [JsonReadOnly] public virtual IEnumerable People { get; set; } + [EditableRelation] [JsonReadOnly] public virtual Studio Studio { get; set; } + [EditableRelation] [JsonReadOnly] public virtual IEnumerable Genres { get; set; } + [EditableRelation] [JsonReadOnly] public virtual IEnumerable People { get; set; } [JsonIgnore] public virtual IEnumerable Seasons { get; set; } [JsonIgnore] public virtual IEnumerable Episodes { get; set; } [JsonIgnore] public virtual IEnumerable Libraries { get; set; } diff --git a/Kyoo.Common/Models/Resources/Track.cs b/Kyoo.Common/Models/Resources/Track.cs index fe655f96..3c97e2fd 100644 --- a/Kyoo.Common/Models/Resources/Track.cs +++ b/Kyoo.Common/Models/Resources/Track.cs @@ -8,7 +8,7 @@ namespace Kyoo.Models { public enum StreamType { - Unknow = 0, + Unknown = 0, Video = 1, Audio = 2, Subtitle = 3, diff --git a/Kyoo.Common/Utility.cs b/Kyoo.Common/Utility.cs index 4aa13f0f..7d8cbd98 100644 --- a/Kyoo.Common/Utility.cs +++ b/Kyoo.Common/Utility.cs @@ -102,9 +102,8 @@ namespace Kyoo Type type = typeof(T); IEnumerable properties = type.GetProperties() - .Where(x => x.CanRead - && x.CanWrite - && Attribute.GetCustomAttribute(x, typeof(NotMergableAttribute)) == null); + .Where(x => x.CanRead && x.CanWrite + && Attribute.GetCustomAttribute(x, typeof(NotMergableAttribute)) == null); foreach (PropertyInfo property in properties) { @@ -114,11 +113,7 @@ namespace Kyoo : null; if (value?.Equals(defaultValue) == false) - { - if (value is IEnumerable enumerable && !(value is string)) - value = RunGenericMethod(typeof(Enumerable), "ToList", GetEnumerableType(enumerable), value); property.SetValue(first, value); - } } if (first is IOnMerge merge) diff --git a/Kyoo.CommonAPI/JsonSerializer.cs b/Kyoo.CommonAPI/JsonSerializer.cs index 4e0c6bbe..6cbc9c89 100644 --- a/Kyoo.CommonAPI/JsonSerializer.cs +++ b/Kyoo.CommonAPI/JsonSerializer.cs @@ -13,7 +13,19 @@ using Newtonsoft.Json.Serialization; namespace Kyoo.Controllers { - public class JsonPropertySelector : CamelCasePropertyNamesContractResolver + public class JsonPropertyIgnorer : CamelCasePropertyNamesContractResolver + { + protected override JsonProperty CreateProperty(MemberInfo member, MemberSerialization memberSerialization) + { + JsonProperty property = base.CreateProperty(member, memberSerialization); + + property.ShouldSerialize = i => member.GetCustomAttribute(true) == null; + property.ShouldDeserialize = i => member.GetCustomAttribute(true) == null; + return property; + } + } + + public class JsonPropertySelector : JsonPropertyIgnorer { private readonly Dictionary> _ignored; private readonly Dictionary> _forceSerialize; @@ -60,20 +72,9 @@ namespace Kyoo.Controllers JsonProperty property = base.CreateProperty(member, memberSerialization); if (IsSerializationForced(property.DeclaringType, property.PropertyName)) - { property.ShouldSerialize = i => true; - property.Ignored = false; - } else if (IsIgnored(property.DeclaringType, property.PropertyName)) - { property.ShouldSerialize = i => false; - property.Ignored = true; - } - else - { - property.ShouldSerialize = i => member.GetCustomAttribute(true) == null; - property.ShouldDeserialize = i => member.GetCustomAttribute(true) == null; - } return property; } } diff --git a/Kyoo.CommonAPI/LocalRepository.cs b/Kyoo.CommonAPI/LocalRepository.cs index b06b4f01..871f3cf5 100644 --- a/Kyoo.CommonAPI/LocalRepository.cs +++ b/Kyoo.CommonAPI/LocalRepository.cs @@ -8,6 +8,7 @@ using System.Threading.Tasks; using JetBrains.Annotations; using Kyoo.CommonApi; using Kyoo.Models; +using Kyoo.Models.Attributes; using Kyoo.Models.Exceptions; using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore.ChangeTracking; @@ -118,25 +119,32 @@ namespace Kyoo.Controllers return obj; } - public virtual async Task CreateIfNotExists(T obj) + public virtual async Task CreateIfNotExists(T obj, bool silentFail = false) { - if (obj == null) - throw new ArgumentNullException(nameof(obj)); - - T old = await Get(obj.Slug); - if (old != null) - return old; try { + if (obj == null) + throw new ArgumentNullException(nameof(obj)); + + T old = await Get(obj.Slug); + if (old != null) + return old; + return await Create(obj); } catch (DuplicatedItemException) { - old = await Get(obj.Slug); + T old = await Get(obj!.Slug); if (old == null) throw new SystemException("Unknown database state."); return old; } + catch + { + if (silentFail) + return default; + throw; + } } public virtual async Task Edit(T edited, bool resetOld) @@ -152,14 +160,17 @@ namespace Kyoo.Controllers if (old == null) throw new ItemNotFound($"No resource found with the ID {edited.ID}."); + IEnumerable relations = Database.Entry(old).Collections + .Concat(Database.Entry(old).Navigations); + foreach (NavigationEntry navigation in relations) + if (navigation.Metadata.PropertyInfo.GetCustomAttribute() != null) + await navigation.LoadAsync(); + if (resetOld) Utility.Nullify(old); Utility.Complete(old, edited); + // TODO Validation set values & setting values trigger a change round in the OEM. A change round should only be triggered if the item is actually different. await Validate(old); - // TODO should fix this, new links & deleted links should be kept. - // TODO The changetracker has trash values now & values can't be listed before the validation (exception is thrown) - foreach (EntityEntry x in Database.ChangeTracker.Entries().Where(x => x.Entity != old)) - x.State = EntityState.Detached; await Database.SaveChangesAsync(); return old; } @@ -168,7 +179,7 @@ namespace Kyoo.Controllers Database.ChangeTracker.LazyLoadingEnabled = true; } } - + protected virtual Task Validate(T resource) { if (string.IsNullOrEmpty(resource.Slug)) @@ -298,13 +309,13 @@ namespace Kyoo.Controllers .Then(x => item.ID = x.ID); } - Task IRepository.CreateIfNotExists(T item) + Task IRepository.CreateIfNotExists(T item, bool silentFail) { TInternal obj = item as TInternal ?? new TInternal(); if (!(item is TInternal)) Utility.Assign(obj, item); - return CreateIfNotExists(obj).Cast() + return CreateIfNotExists(obj, silentFail).Cast() .Then(x => item.ID = x.ID); } diff --git a/Kyoo/Controllers/Repositories/LibraryItemRepository.cs b/Kyoo/Controllers/Repositories/LibraryItemRepository.cs index 07f0251e..ff45d342 100644 --- a/Kyoo/Controllers/Repositories/LibraryItemRepository.cs +++ b/Kyoo/Controllers/Repositories/LibraryItemRepository.cs @@ -100,7 +100,13 @@ namespace Kyoo.Controllers } public override Task Create(LibraryItem obj) => throw new InvalidOperationException(); - public override Task CreateIfNotExists(LibraryItem obj) => throw new InvalidOperationException(); + + public override Task CreateIfNotExists(LibraryItem obj, bool silentFail = false) + { + if (silentFail) + return Task.FromResult(default); + throw new InvalidOperationException(); + } public override Task Edit(LibraryItem obj, bool reset) => throw new InvalidOperationException(); protected override Task Validate(LibraryItem resource) => throw new InvalidOperationException(); public override Task Delete(int id) => throw new InvalidOperationException(); diff --git a/Kyoo/Controllers/Repositories/ShowRepository.cs b/Kyoo/Controllers/Repositories/ShowRepository.cs index da1be292..d58c6602 100644 --- a/Kyoo/Controllers/Repositories/ShowRepository.cs +++ b/Kyoo/Controllers/Repositories/ShowRepository.cs @@ -75,7 +75,8 @@ namespace Kyoo.Controllers query = $"%{query}%"; return await _database.Shows .Where(x => EF.Functions.ILike(x.Title, query) - /*|| x.Aliases.Any(y => EF.Functions.ILike(y, query))*/) // NOT TRANSLATABLE. + || EF.Functions.ILike(x.Slug, query) + /*|| x.Aliases.Any(y => EF.Functions.ILike(y, query))*/) // NOT TRANSLATABLE. .Take(20) .ToListAsync(); } diff --git a/Kyoo/Controllers/Repositories/TrackRepository.cs b/Kyoo/Controllers/Repositories/TrackRepository.cs index e930774e..25bce25b 100644 --- a/Kyoo/Controllers/Repositories/TrackRepository.cs +++ b/Kyoo/Controllers/Repositories/TrackRepository.cs @@ -36,7 +36,7 @@ namespace Kyoo.Controllers await _database.DisposeAsync(); } - public Task Get(string slug, StreamType type = StreamType.Unknow) + public Task Get(string slug, StreamType type = StreamType.Unknown) { Match match = Regex.Match(slug, @"(?.*)-s(?\d+)e(?\d+)\.(?.{0,3})(?-forced)?(\..*)?"); @@ -57,7 +57,7 @@ namespace Kyoo.Controllers string language = match.Groups["language"].Value; bool forced = match.Groups["forced"].Success; - if (type == StreamType.Unknow) + if (type == StreamType.Unknown) { return _database.Tracks.FirstOrDefaultAsync(x => x.Episode.Show.Slug == showSlug && x.Episode.SeasonNumber == seasonNumber diff --git a/Kyoo/Controllers/TaskManager.cs b/Kyoo/Controllers/TaskManager.cs index de13479e..9a791fdc 100644 --- a/Kyoo/Controllers/TaskManager.cs +++ b/Kyoo/Controllers/TaskManager.cs @@ -55,7 +55,7 @@ namespace Kyoo.Controllers } else { - await Task.Delay(10, cancellationToken); + await Task.Delay(1000, cancellationToken); QueueScheduledTasks(); } } @@ -63,8 +63,8 @@ namespace Kyoo.Controllers private void QueueScheduledTasks() { - List tasksToQueue = _tasks.Where(x => x.scheduledDate <= DateTime.Now) - .Select(x => x.task.Slug).ToList(); + IEnumerable tasksToQueue = _tasks.Where(x => x.scheduledDate <= DateTime.Now) + .Select(x => x.task.Slug); foreach (string task in tasksToQueue) StartTask(task); } diff --git a/Kyoo/Controllers/Transcoder/TranscoderAPI.cs b/Kyoo/Controllers/Transcoder/TranscoderAPI.cs index b82ad9a3..ff7e6905 100644 --- a/Kyoo/Controllers/Transcoder/TranscoderAPI.cs +++ b/Kyoo/Controllers/Transcoder/TranscoderAPI.cs @@ -41,7 +41,7 @@ namespace Kyoo.Controllers.TranscoderLink for (int i = 0; i < arrayLength; i++) { Stream stream = Marshal.PtrToStructure(streamsPtr); - if (stream!.Type != StreamType.Unknow) + if (stream!.Type != StreamType.Unknown) { tracks[j] = new Track(stream); j++; diff --git a/Kyoo/Models/Resources/LibraryDE.cs b/Kyoo/Models/Resources/LibraryDE.cs index f2521904..07febaa6 100644 --- a/Kyoo/Models/Resources/LibraryDE.cs +++ b/Kyoo/Models/Resources/LibraryDE.cs @@ -6,7 +6,7 @@ namespace Kyoo.Models { public class LibraryDE : Library { - [JsonIgnore] [NotMergable] public virtual ICollection ProviderLinks { get; set; } + [EditableRelation] [JsonIgnore] [NotMergable] public virtual ICollection ProviderLinks { get; set; } [ExpressionRewrite(nameof(ProviderLinks), nameof(ProviderLink.Provider))] public override IEnumerable Providers { diff --git a/Kyoo/Models/Resources/ShowDE.cs b/Kyoo/Models/Resources/ShowDE.cs index 010b33f6..e1ad4d34 100644 --- a/Kyoo/Models/Resources/ShowDE.cs +++ b/Kyoo/Models/Resources/ShowDE.cs @@ -6,7 +6,7 @@ namespace Kyoo.Models { public class ShowDE : Show { - [JsonReadOnly] [NotMergable] public virtual ICollection GenreLinks { get; set; } + [EditableRelation] [JsonReadOnly] [NotMergable] public virtual ICollection GenreLinks { get; set; } [ExpressionRewrite(nameof(GenreLinks), nameof(GenreLink.Genre))] public override IEnumerable Genres { diff --git a/Kyoo/Startup.cs b/Kyoo/Startup.cs index 4e6cc6e5..fc738d26 100644 --- a/Kyoo/Startup.cs +++ b/Kyoo/Startup.cs @@ -41,7 +41,7 @@ namespace Kyoo }); services.AddControllers() - .AddNewtonsoftJson(x => x.SerializerSettings.ContractResolver = new JsonPropertySelector()); + .AddNewtonsoftJson(x => x.SerializerSettings.ContractResolver = new JsonPropertyIgnorer()); services.AddHttpClient(); services.AddDbContext(options =>