Making edit routes kinda work

This commit is contained in:
Zoe Roux 2020-12-24 18:49:34 +01:00
parent 5229e319b1
commit 41cabdd1b5
21 changed files with 78 additions and 71 deletions

View File

@ -43,7 +43,7 @@ namespace Kyoo.Controllers
Task<Season> GetSeason(string showSlug, int seasonNumber);
Task<Episode> GetEpisode(string showSlug, int seasonNumber, int episodeNumber);
Task<Episode> GetMovieEpisode(string movieSlug);
Task<Track> GetTrack(string slug, StreamType type = StreamType.Unknow);
Task<Track> GetTrack(string slug, StreamType type = StreamType.Unknown);
Task<Genre> GetGenre(string slug);
Task<Studio> GetStudio(string slug);
Task<People> GetPeople(string slug);

View File

@ -95,20 +95,7 @@ namespace Kyoo.Controllers
Task<T> Create([NotNull] T obj);
Task<T> CreateIfNotExists([NotNull] T obj);
async Task<T> CreateIfNotExists([NotNull] T obj, bool silentFail)
{
try
{
return await CreateIfNotExists(obj);
}
catch
{
if (!silentFail)
throw;
return null;
}
}
Task<T> CreateIfNotExists([NotNull] T obj, bool silentFail = false);
Task<T> Edit([NotNull] T edited, bool resetOld);
Task Delete(int id);
@ -147,7 +134,7 @@ namespace Kyoo.Controllers
public interface ITrackRepository : IRepository<Track>
{
Task<Track> Get(string slug, StreamType type = StreamType.Unknow);
Task<Track> Get(string slug, StreamType type = StreamType.Unknown);
}
public interface ILibraryRepository : IRepository<Library> { }

View File

@ -110,7 +110,7 @@ namespace Kyoo.Controllers
return EpisodeRepository.Get(showID, seasonNumber, episodeNumber);
}
public Task<Track> GetTrack(string slug, StreamType type = StreamType.Unknow)
public Task<Track> GetTrack(string slug, StreamType type = StreamType.Unknown)
{
return TrackRepository.Get(slug, type);
}

View File

@ -0,0 +1,6 @@
using System;
namespace Kyoo.Models.Attributes
{
public class EditableRelation : Attribute { }
}

View File

@ -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<MetadataID> ExternalIDs { get; set; }
[EditableRelation] public virtual IEnumerable<MetadataID> ExternalIDs { get; set; }
[JsonIgnore] public virtual IEnumerable<Track> Tracks { get; set; }

View File

@ -10,7 +10,7 @@ namespace Kyoo.Models
public string Name { get; set; }
public IEnumerable<string> Paths { get; set; }
public virtual IEnumerable<ProviderID> Providers { get; set; }
[EditableRelation] public virtual IEnumerable<ProviderID> Providers { get; set; }
[JsonIgnore] public virtual IEnumerable<Show> Shows { get; set; }
[JsonIgnore] public virtual IEnumerable<Collection> Collections { get; set; }

View File

@ -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<MetadataID> ExternalIDs { get; set; }
[EditableRelation] public virtual IEnumerable<MetadataID> ExternalIDs { get; set; }
[JsonReadOnly] public virtual IEnumerable<PeopleRole> Roles { get; set; }
[EditableRelation] [JsonReadOnly] public virtual IEnumerable<PeopleRole> Roles { get; set; }
public People() {}

View File

@ -16,7 +16,7 @@ namespace Kyoo.Models
public int? Year { get; set; }
[JsonIgnore] public string Poster { get; set; }
public virtual IEnumerable<MetadataID> ExternalIDs { get; set; }
[EditableRelation] public virtual IEnumerable<MetadataID> ExternalIDs { get; set; }
[JsonIgnore] public virtual Show Show { get; set; }
[JsonIgnore] public virtual IEnumerable<Episode> Episodes { get; set; }

View File

@ -28,9 +28,9 @@ namespace Kyoo.Models
[JsonIgnore] public int? StudioID { get; set; }
[JsonReadOnly] public virtual Studio Studio { get; set; }
[JsonReadOnly] public virtual IEnumerable<Genre> Genres { get; set; }
[JsonReadOnly] public virtual IEnumerable<PeopleRole> People { get; set; }
[EditableRelation] [JsonReadOnly] public virtual Studio Studio { get; set; }
[EditableRelation] [JsonReadOnly] public virtual IEnumerable<Genre> Genres { get; set; }
[EditableRelation] [JsonReadOnly] public virtual IEnumerable<PeopleRole> People { get; set; }
[JsonIgnore] public virtual IEnumerable<Season> Seasons { get; set; }
[JsonIgnore] public virtual IEnumerable<Episode> Episodes { get; set; }
[JsonIgnore] public virtual IEnumerable<Library> Libraries { get; set; }

View File

@ -8,7 +8,7 @@ namespace Kyoo.Models
{
public enum StreamType
{
Unknow = 0,
Unknown = 0,
Video = 1,
Audio = 2,
Subtitle = 3,

View File

@ -102,9 +102,8 @@ namespace Kyoo
Type type = typeof(T);
IEnumerable<PropertyInfo> 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)

View File

@ -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<JsonReadOnly>(true) == null;
property.ShouldDeserialize = i => member.GetCustomAttribute<JsonIgnore>(true) == null;
return property;
}
}
public class JsonPropertySelector : JsonPropertyIgnorer
{
private readonly Dictionary<Type, HashSet<string>> _ignored;
private readonly Dictionary<Type, HashSet<string>> _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<JsonReadOnly>(true) == null;
property.ShouldDeserialize = i => member.GetCustomAttribute<JsonIgnore>(true) == null;
}
return property;
}
}

View File

@ -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<T> CreateIfNotExists(T obj)
public virtual async Task<T> 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<T> 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<NavigationEntry> relations = Database.Entry(old).Collections
.Concat(Database.Entry(old).Navigations);
foreach (NavigationEntry navigation in relations)
if (navigation.Metadata.PropertyInfo.GetCustomAttribute<EditableRelation>() != 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<T> IRepository<T>.CreateIfNotExists(T item)
Task<T> IRepository<T>.CreateIfNotExists(T item, bool silentFail)
{
TInternal obj = item as TInternal ?? new TInternal();
if (!(item is TInternal))
Utility.Assign(obj, item);
return CreateIfNotExists(obj).Cast<T>()
return CreateIfNotExists(obj, silentFail).Cast<T>()
.Then(x => item.ID = x.ID);
}

View File

@ -100,7 +100,13 @@ namespace Kyoo.Controllers
}
public override Task<LibraryItem> Create(LibraryItem obj) => throw new InvalidOperationException();
public override Task<LibraryItem> CreateIfNotExists(LibraryItem obj) => throw new InvalidOperationException();
public override Task<LibraryItem> CreateIfNotExists(LibraryItem obj, bool silentFail = false)
{
if (silentFail)
return Task.FromResult<LibraryItem>(default);
throw new InvalidOperationException();
}
public override Task<LibraryItem> 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();

View File

@ -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<Show>();
}

View File

@ -36,7 +36,7 @@ namespace Kyoo.Controllers
await _database.DisposeAsync();
}
public Task<Track> Get(string slug, StreamType type = StreamType.Unknow)
public Task<Track> Get(string slug, StreamType type = StreamType.Unknown)
{
Match match = Regex.Match(slug,
@"(?<show>.*)-s(?<season>\d+)e(?<episode>\d+)\.(?<language>.{0,3})(?<forced>-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

View File

@ -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<string> tasksToQueue = _tasks.Where(x => x.scheduledDate <= DateTime.Now)
.Select(x => x.task.Slug).ToList();
IEnumerable<string> tasksToQueue = _tasks.Where(x => x.scheduledDate <= DateTime.Now)
.Select(x => x.task.Slug);
foreach (string task in tasksToQueue)
StartTask(task);
}

View File

@ -41,7 +41,7 @@ namespace Kyoo.Controllers.TranscoderLink
for (int i = 0; i < arrayLength; i++)
{
Stream stream = Marshal.PtrToStructure<Stream>(streamsPtr);
if (stream!.Type != StreamType.Unknow)
if (stream!.Type != StreamType.Unknown)
{
tracks[j] = new Track(stream);
j++;

View File

@ -6,7 +6,7 @@ namespace Kyoo.Models
{
public class LibraryDE : Library
{
[JsonIgnore] [NotMergable] public virtual ICollection<ProviderLink> ProviderLinks { get; set; }
[EditableRelation] [JsonIgnore] [NotMergable] public virtual ICollection<ProviderLink> ProviderLinks { get; set; }
[ExpressionRewrite(nameof(ProviderLinks), nameof(ProviderLink.Provider))]
public override IEnumerable<ProviderID> Providers
{

View File

@ -6,7 +6,7 @@ namespace Kyoo.Models
{
public class ShowDE : Show
{
[JsonReadOnly] [NotMergable] public virtual ICollection<GenreLink> GenreLinks { get; set; }
[EditableRelation] [JsonReadOnly] [NotMergable] public virtual ICollection<GenreLink> GenreLinks { get; set; }
[ExpressionRewrite(nameof(GenreLinks), nameof(GenreLink.Genre))]
public override IEnumerable<Genre> Genres
{

View File

@ -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<DatabaseContext>(options =>