Reworking metadata handling and adding it to Collections and Studios

This commit is contained in:
Zoe Roux 2021-07-26 17:00:23 +02:00
parent 3262343c4a
commit 2812c9cacf
24 changed files with 260 additions and 167 deletions

View File

@ -590,10 +590,10 @@ namespace Kyoo.Controllers
/// <param name="limit">Pagination information (where to start and how many to get)</param>
/// <typeparam name="T">The type of metadata to retrieve</typeparam>
/// <returns>A filtered list of external ids.</returns>
Task<ICollection<MetadataID<T>>> GetMetadataID<T>(Expression<Func<MetadataID<T>, bool>> where = null,
Sort<MetadataID<T>> sort = default,
Task<ICollection<MetadataID>> GetMetadataID<T>(Expression<Func<MetadataID, bool>> where = null,
Sort<MetadataID> sort = default,
Pagination limit = default)
where T : class, IResource;
where T : class, IMetadata;
/// <summary>
/// Get a list of external ids that match all filters
@ -602,11 +602,11 @@ namespace Kyoo.Controllers
/// <param name="sort">A sort by expression</param>
/// <param name="limit">Pagination information (where to start and how many to get)</param>
/// <returns>A filtered list of external ids.</returns>
Task<ICollection<MetadataID<T>>> GetMetadataID<T>([Optional] Expression<Func<MetadataID<T>, bool>> where,
Expression<Func<MetadataID<T>, object>> sort,
Task<ICollection<MetadataID>> GetMetadataID<T>([Optional] Expression<Func<MetadataID, bool>> where,
Expression<Func<MetadataID, object>> sort,
Pagination limit = default
) where T : class, IResource
=> GetMetadataID(where, new Sort<MetadataID<T>>(sort), limit);
) where T : class, IMetadata
=> GetMetadataID<T>(where, new Sort<MetadataID>(sort), limit);
}
/// <summary>

View File

@ -231,7 +231,12 @@ namespace Kyoo.Controllers
.Then(x => l.Collections = x),
(Collection c, nameof(Library.Shows)) => ShowRepository
(Collection c, nameof(Collection.ExternalIDs)) => SetRelation(c,
ProviderRepository.GetMetadataID<Collection>(x => x.ResourceID == obj.ID),
(x, y) => x.ExternalIDs = y,
(x, y) => { x.ResourceID = y.ID; }),
(Collection c, nameof(Collection.Shows)) => ShowRepository
.GetAll(x => x.Collections.Any(y => y.ID == obj.ID))
.Then(x => c.Shows = x),
@ -241,9 +246,9 @@ namespace Kyoo.Controllers
(Show s, nameof(Show.ExternalIDs)) => SetRelation(s,
ProviderRepository.GetMetadataID<Show>(x => x.FirstID == obj.ID),
ProviderRepository.GetMetadataID<Show>(x => x.ResourceID == obj.ID),
(x, y) => x.ExternalIDs = y,
(x, y) => { x.First = y; x.FirstID = y.ID; }),
(x, y) => { x.ResourceID = y.ID; }),
(Show s, nameof(Show.Genres)) => GenreRepository
.GetAll(x => x.Shows.Any(y => y.ID == obj.ID))
@ -281,9 +286,9 @@ namespace Kyoo.Controllers
(Season s, nameof(Season.ExternalIDs)) => SetRelation(s,
ProviderRepository.GetMetadataID<Season>(x => x.FirstID == obj.ID),
ProviderRepository.GetMetadataID<Season>(x => x.ResourceID == obj.ID),
(x, y) => x.ExternalIDs = y,
(x, y) => { x.First = y; x.FirstID = y.ID; }),
(x, y) => { x.ResourceID = y.ID; }),
(Season s, nameof(Season.Episodes)) => SetRelation(s,
EpisodeRepository.GetAll(x => x.Season.ID == obj.ID),
@ -300,9 +305,9 @@ namespace Kyoo.Controllers
(Episode e, nameof(Episode.ExternalIDs)) => SetRelation(e,
ProviderRepository.GetMetadataID<Episode>(x => x.FirstID == obj.ID),
ProviderRepository.GetMetadataID<Episode>(x => x.ResourceID == obj.ID),
(x, y) => x.ExternalIDs = y,
(x, y) => { x.First = y; x.FirstID = y.ID; }),
(x, y) => { x.ResourceID = y.ID; }),
(Episode e, nameof(Episode.Tracks)) => SetRelation(e,
TrackRepository.GetAll(x => x.Episode.ID == obj.ID),
@ -344,11 +349,16 @@ namespace Kyoo.Controllers
.GetAll(x => x.Studio.ID == obj.ID)
.Then(x => s.Shows = x),
(Studio s, nameof(Studio.ExternalIDs)) => SetRelation(s,
ProviderRepository.GetMetadataID<Studio>(x => x.ResourceID == obj.ID),
(x, y) => x.ExternalIDs = y,
(x, y) => { x.ResourceID = y.ID; }),
(People p, nameof(People.ExternalIDs)) => SetRelation(p,
ProviderRepository.GetMetadataID<People>(x => x.FirstID == obj.ID),
ProviderRepository.GetMetadataID<People>(x => x.ResourceID == obj.ID),
(x, y) => x.ExternalIDs = y,
(x, y) => { x.First = y; x.FirstID = y.ID; }),
(x, y) => { x.ResourceID = y.ID; }),
(People p, nameof(People.Roles)) => PeopleRepository
.GetFromPeople(obj.ID)

View File

@ -1,15 +1,34 @@
using System;
using System.Linq.Expressions;
using Kyoo.Models.Attributes;
namespace Kyoo.Models
{
/// <summary>
/// ID and link of an item on an external provider.
/// </summary>
/// <typeparam name="T"></typeparam>
public class MetadataID<T> : Link<T, Provider>
where T : class, IResource
public class MetadataID
{
/// <summary>
/// The ID of the resource which possess the metadata.
/// </summary>
[SerializeIgnore] public int ResourceID { get; set; }
/// <summary>
/// The name of the resource type. This is only used internally to discriminate types.
/// </summary>
[SerializeIgnore] public string ResourceType { get; set; }
/// <summary>
/// The ID of the provider.
/// </summary>
[SerializeIgnore] public int ProviderID { get; set; }
/// <summary>
/// The provider that can do something with this ID.
/// </summary>
public Provider Provider { get; set; }
/// <summary>
/// The ID of the resource on the external provider.
/// </summary>
@ -20,20 +39,14 @@ namespace Kyoo.Models
/// </summary>
public string Link { get; set; }
/// <summary>
/// A shortcut to access the provider of this metadata.
/// Unlike the <see cref="Link{T, T2}.Second"/> property, this is serializable.
/// </summary>
public Provider Provider => Second;
/// <summary>
/// The expression to retrieve the unique ID of a MetadataID. This is an aggregate of the two resources IDs.
/// </summary>
public new static Expression<Func<MetadataID<T>, object>> PrimaryKey
public static Expression<Func<MetadataID, object>> PrimaryKey
{
get
{
return x => new {First = x.FirstID, Second = x.SecondID};
return x => new {First = x.ResourceID, Second = x.ProviderID, Type = x.ResourceType};
}
}
}

View File

@ -8,7 +8,7 @@ namespace Kyoo.Models
/// A class representing collections of <see cref="Show"/>.
/// A collection can also be stored in a <see cref="Library"/>.
/// </summary>
public class Collection : IResource
public class Collection : IResource, IMetadata
{
/// <inheritdoc />
public int ID { get; set; }
@ -43,6 +43,9 @@ namespace Kyoo.Models
/// </summary>
[LoadableRelation] public ICollection<Library> Libraries { get; set; }
/// <inheritdoc />
public ICollection<MetadataID> ExternalIDs { get; set; }
#if ENABLE_INTERNAL_LINKS
/// <summary>

View File

@ -10,7 +10,7 @@ namespace Kyoo.Models
/// <summary>
/// A class to represent a single show's episode.
/// </summary>
public class Episode : IResource
public class Episode : IResource, IMetadata
{
/// <inheritdoc />
public int ID { get; set; }
@ -121,10 +121,8 @@ namespace Kyoo.Models
/// </summary>
public DateTime? ReleaseDate { get; set; }
/// <summary>
/// The link to metadata providers that this episode has. See <see cref="MetadataID{T}"/> for more information.
/// </summary>
[EditableRelation] [LoadableRelation] public ICollection<MetadataID<Episode>> ExternalIDs { get; set; }
/// <inheritdoc />
public ICollection<MetadataID> ExternalIDs { get; set; }
/// <summary>
/// The list of tracks this episode has. This lists video, audio and subtitles available.

View File

@ -0,0 +1,16 @@
using System.Collections.Generic;
using Kyoo.Models.Attributes;
namespace Kyoo.Models
{
/// <summary>
/// An interface applied to resources containing external metadata.
/// </summary>
public interface IMetadata
{
/// <summary>
/// The link to metadata providers that this show has. See <see cref="MetadataID"/> for more information.
/// </summary>
[EditableRelation] [LoadableRelation] public ICollection<MetadataID> ExternalIDs { get; set; }
}
}

View File

@ -6,7 +6,7 @@ namespace Kyoo.Models
/// <summary>
/// An actor, voice actor, writer, animator, somebody who worked on a <see cref="Show"/>.
/// </summary>
public class People : IResource
public class People : IResource, IMetadata
{
/// <inheritdoc />
public int ID { get; set; }
@ -26,10 +26,8 @@ namespace Kyoo.Models
/// </summary>
[SerializeAs("{HOST}/api/people/{Slug}/poster")] public string Poster { get; set; }
/// <summary>
/// The link to metadata providers that this person has. See <see cref="MetadataID{T}"/> for more information.
/// </summary>
[EditableRelation] [LoadableRelation] public ICollection<MetadataID<People>> ExternalIDs { get; set; }
/// <inheritdoc />
public ICollection<MetadataID> ExternalIDs { get; set; }
/// <summary>
/// The list of roles this person has played in. See <see cref="PeopleRole"/> for more information.

View File

@ -10,7 +10,7 @@ namespace Kyoo.Models
/// <summary>
/// A season of a <see cref="Show"/>.
/// </summary>
public class Season : IResource
public class Season : IResource, IMetadata
{
/// <inheritdoc />
public int ID { get; set; }
@ -81,10 +81,8 @@ namespace Kyoo.Models
/// </summary>
[SerializeAs("{HOST}/api/seasons/{Slug}/thumb")] public string Poster { get; set; }
/// <summary>
/// The link to metadata providers that this episode has. See <see cref="MetadataID{T}"/> for more information.
/// </summary>
[EditableRelation] [LoadableRelation] public ICollection<MetadataID<Season>> ExternalIDs { get; set; }
/// <inheritdoc />
public ICollection<MetadataID> ExternalIDs { get; set; }
/// <summary>
/// The list of episodes that this season contains.

View File

@ -11,7 +11,7 @@ namespace Kyoo.Models
/// <summary>
/// A series or a movie.
/// </summary>
public class Show : IResource, IOnMerge
public class Show : IResource, IMetadata, IOnMerge
{
/// <inheritdoc />
public int ID { get; set; }
@ -89,10 +89,8 @@ namespace Kyoo.Models
/// </summary>
public bool IsMovie { get; set; }
/// <summary>
/// The link to metadata providers that this show has. See <see cref="MetadataID{T}"/> for more information.
/// </summary>
[EditableRelation] [LoadableRelation] public ICollection<MetadataID<Show>> ExternalIDs { get; set; }
/// <inheritdoc />
public ICollection<MetadataID> ExternalIDs { get; set; }
/// <summary>
/// The ID of the Studio that made this show.
@ -157,19 +155,16 @@ namespace Kyoo.Models
/// </summary>
/// <remarks>This method will never return anything if the <see cref="ExternalIDs"/> are not loaded.</remarks>
/// <param name="provider">The slug of the provider</param>
/// <returns>The <see cref="MetadataID{T}.DataID"/> field of the asked provider.</returns>
/// <returns>The <see cref="MetadataID.DataID"/> field of the asked provider.</returns>
[CanBeNull]
public string GetID(string provider)
{
return ExternalIDs?.FirstOrDefault(x => x.Second.Slug == provider)?.DataID;
return ExternalIDs?.FirstOrDefault(x => x.Provider.Slug == provider)?.DataID;
}
/// <inheritdoc />
public void OnMerge(object merged)
{
if (ExternalIDs != null)
foreach (MetadataID<Show> id in ExternalIDs)
id.First = this;
if (People != null)
foreach (PeopleRole link in People)
link.Show = this;

View File

@ -6,7 +6,7 @@ namespace Kyoo.Models
/// <summary>
/// A studio that make shows.
/// </summary>
public class Studio : IResource
public class Studio : IResource, IMetadata
{
/// <inheritdoc />
public int ID { get; set; }
@ -24,6 +24,9 @@ namespace Kyoo.Models
/// </summary>
[LoadableRelation] public ICollection<Show> Shows { get; set; }
/// <inheritdoc />
public ICollection<MetadataID> ExternalIDs { get; set; }
/// <summary>
/// Create a new, empty, <see cref="Studio"/>.
/// </summary>

View File

@ -61,6 +61,10 @@ namespace Kyoo
/// </summary>
public DbSet<Provider> Providers { get; set; }
/// <summary>
/// All metadata ids, not discriminated by type. See <see cref="MetadataID"/>.
/// </summary>
public DbSet<MetadataID> MetadataIDs { get; set; }
/// <summary>
/// The list of registered users.
/// </summary>
public DbSet<User> Users { get; set; }
@ -83,17 +87,6 @@ namespace Kyoo
/// </remarks>
public DbSet<LibraryItem> LibraryItems { get; set; }
/// <summary>
/// Get all metadataIDs (ExternalIDs) of a given resource. See <see cref="MetadataID{T}"/>.
/// </summary>
/// <typeparam name="T">The metadata of this type will be returned.</typeparam>
/// <returns>A queryable of metadata ids for a type.</returns>
public DbSet<MetadataID<T>> MetadataIds<T>()
where T : class, IResource
{
return Set<MetadataID<T>>();
}
/// <summary>
/// Get a generic link between two resource types.
/// </summary>
@ -132,6 +125,22 @@ namespace Kyoo
optionsBuilder.UseQueryTrackingBehavior(QueryTrackingBehavior.NoTracking);
}
/// <summary>
/// Build the metadata model for the given type.
/// </summary>
/// <param name="modelBuilder">The database model builder</param>
/// <typeparam name="T">The type to add metadata to.</typeparam>
private void _HasMetadata<T>(ModelBuilder modelBuilder)
where T : class, IMetadata
{
modelBuilder.Entity<T>()
.HasMany(x => x.ExternalIDs)
.WithOne()
.HasForeignKey(x => x.ResourceID)
.OnDelete(DeleteBehavior.Cascade);
}
/// <summary>
/// Set database parameters to support every types of Kyoo.
/// </summary>
@ -234,42 +243,20 @@ namespace Kyoo
.WithMany(x => x.ShowLinks),
y => y.HasKey(Link<User, Show>.PrimaryKey));
modelBuilder.Entity<MetadataID<Show>>()
.HasKey(MetadataID<Show>.PrimaryKey);
modelBuilder.Entity<MetadataID<Show>>()
.HasOne(x => x.First)
.WithMany(x => x.ExternalIDs)
.OnDelete(DeleteBehavior.Cascade);
modelBuilder.Entity<MetadataID<Season>>()
.HasKey(MetadataID<Season>.PrimaryKey);
modelBuilder.Entity<MetadataID<Season>>()
.HasOne(x => x.First)
.WithMany(x => x.ExternalIDs)
.OnDelete(DeleteBehavior.Cascade);
modelBuilder.Entity<MetadataID<Episode>>()
.HasKey(MetadataID<Episode>.PrimaryKey);
modelBuilder.Entity<MetadataID<Episode>>()
.HasOne(x => x.First)
.WithMany(x => x.ExternalIDs)
.OnDelete(DeleteBehavior.Cascade);
modelBuilder.Entity<MetadataID<People>>()
.HasKey(MetadataID<People>.PrimaryKey);
modelBuilder.Entity<MetadataID<People>>()
.HasOne(x => x.First)
.WithMany(x => x.ExternalIDs)
.OnDelete(DeleteBehavior.Cascade);
modelBuilder.Entity<MetadataID>()
.HasKey(MetadataID.PrimaryKey);
modelBuilder.Entity<MetadataID>()
.Property(x => x.ResourceType)
.IsRequired();
modelBuilder.Entity<MetadataID>()
.HasDiscriminator(x => x.ResourceType);
modelBuilder.Entity<MetadataID<Show>>().HasOne(x => x.Second).WithMany()
.OnDelete(DeleteBehavior.Cascade);
modelBuilder.Entity<MetadataID<Season>>().HasOne(x => x.Second).WithMany()
.OnDelete(DeleteBehavior.Cascade);
modelBuilder.Entity<MetadataID<Episode>>().HasOne(x => x.Second).WithMany()
.OnDelete(DeleteBehavior.Cascade);
modelBuilder.Entity<MetadataID<People>>().HasOne(x => x.Second).WithMany()
.OnDelete(DeleteBehavior.Cascade);
modelBuilder.Entity<MetadataID<Show>>().HasOne(x => x.Second).WithMany()
.OnDelete(DeleteBehavior.Cascade);
_HasMetadata<Collection>(modelBuilder);
_HasMetadata<Show>(modelBuilder);
_HasMetadata<Season>(modelBuilder);
_HasMetadata<Episode>(modelBuilder);
_HasMetadata<People>(modelBuilder);
_HasMetadata<Studio>(modelBuilder);
modelBuilder.Entity<WatchedEpisode>()
.HasKey(x => new {First = x.FirstID, Second = x.SecondID});

View File

@ -22,9 +22,9 @@ namespace Kyoo.Tests
ProviderRepository provider = new(_database);
LibraryRepository library = new(_database, provider);
CollectionRepository collection = new(_database);
CollectionRepository collection = new(_database, provider);
GenreRepository genre = new(_database);
StudioRepository studio = new(_database);
StudioRepository studio = new(_database, provider);
PeopleRepository people = new(_database, provider,
new Lazy<IShowRepository>(() => LibraryManager.ShowRepository));
ShowRepository show = new(_database, studio, people, genre, provider);

View File

@ -149,10 +149,9 @@ namespace Kyoo.Tests.Database
Show value = await _repository.Get(TestSample.Get<Show>().Slug);
value.ExternalIDs = new[]
{
new MetadataID<Show>()
new MetadataID
{
First = value,
Second = new Provider("test", "test.png"),
Provider = new Provider("test", "test.png"),
DataID = "1234"
}
};
@ -160,19 +159,19 @@ namespace Kyoo.Tests.Database
Assert.Equal(value.Slug, edited.Slug);
Assert.Equal(
value.ExternalIDs.Select(x => new {x.DataID, x.Second.Slug}),
edited.ExternalIDs.Select(x => new {x.DataID, x.Second.Slug}));
value.ExternalIDs.Select(x => new {x.DataID, x.Provider.Slug}),
edited.ExternalIDs.Select(x => new {x.DataID, x.Provider.Slug}));
await using DatabaseContext database = Repositories.Context.New();
Show show = await database.Shows
.Include(x => x.ExternalIDs)
.ThenInclude(x => x.Second)
.ThenInclude(x => x.Provider)
.FirstAsync();
Assert.Equal(value.Slug, show.Slug);
Assert.Equal(
value.ExternalIDs.Select(x => new {x.DataID, x.Second.Slug}),
show.ExternalIDs.Select(x => new {x.DataID, x.Second.Slug}));
value.ExternalIDs.Select(x => new {x.DataID, x.Provider.Slug}),
show.ExternalIDs.Select(x => new {x.DataID, x.Provider.Slug}));
}
[Fact]
@ -209,10 +208,9 @@ namespace Kyoo.Tests.Database
expected.Slug = "created-relation-test";
expected.ExternalIDs = new[]
{
new MetadataID<Show>
new MetadataID
{
First = expected,
Second = new Provider("provider", "provider.png"),
Provider = new Provider("provider", "provider.png"),
DataID = "ID"
}
};

View File

@ -48,9 +48,9 @@ namespace Kyoo.TheMovieDb
.ToArray(),
ExternalIDs = new []
{
new MetadataID<Show>
new MetadataID
{
Second = provider,
Provider = provider,
Link = $"https://www.themoviedb.org/movie/{movie.Id}",
DataID = movie.Id.ToString()
}
@ -94,9 +94,9 @@ namespace Kyoo.TheMovieDb
.ToArray(),
ExternalIDs = new []
{
new MetadataID<Show>
new MetadataID
{
Second = provider,
Provider = provider,
Link = $"https://www.themoviedb.org/movie/{tv.Id}",
DataID = tv.Id.ToString()
}
@ -145,9 +145,9 @@ namespace Kyoo.TheMovieDb
IsMovie = true,
ExternalIDs = new []
{
new MetadataID<Show>
new MetadataID
{
Second = provider,
Provider = provider,
Link = $"https://www.themoviedb.org/movie/{movie.Id}",
DataID = movie.Id.ToString()
}
@ -178,9 +178,9 @@ namespace Kyoo.TheMovieDb
IsMovie = true,
ExternalIDs = new []
{
new MetadataID<Show>
new MetadataID
{
Second = provider,
Provider = provider,
Link = $"https://www.themoviedb.org/movie/{tv.Id}",
DataID = tv.Id.ToString()
}
@ -205,9 +205,9 @@ namespace Kyoo.TheMovieDb
Poster = cast.ProfilePath != null ? $"https://image.tmdb.org/t/p/original{cast.ProfilePath}" : null,
ExternalIDs = new[]
{
new MetadataID<People>
new MetadataID
{
Second = provider,
Provider = provider,
DataID = cast.Id.ToString(),
Link = $"https://www.themoviedb.org/person/{cast.Id}"
}
@ -235,9 +235,9 @@ namespace Kyoo.TheMovieDb
Poster = cast.ProfilePath != null ? $"https://image.tmdb.org/t/p/original{cast.ProfilePath}" : null,
ExternalIDs = new[]
{
new MetadataID<People>
new MetadataID
{
Second = provider,
Provider = provider,
DataID = cast.Id.ToString(),
Link = $"https://www.themoviedb.org/person/{cast.Id}"
}
@ -265,9 +265,9 @@ namespace Kyoo.TheMovieDb
Poster = crew.ProfilePath != null ? $"https://image.tmdb.org/t/p/original{crew.ProfilePath}" : null,
ExternalIDs = new[]
{
new MetadataID<People>
new MetadataID
{
Second = provider,
Provider = provider,
DataID = crew.Id.ToString(),
Link = $"https://www.themoviedb.org/person/{crew.Id}"
}

View File

@ -58,11 +58,11 @@ namespace Kyoo.TheTvdb
Poster = result.Poster != null ? $"https://www.thetvdb.com{result.Poster}" : null,
ExternalIDs = new[]
{
new MetadataID<Show>
new MetadataID
{
DataID = result.Id.ToString(),
Link = $"https://www.thetvdb.com/series/{result.Slug}",
Second = provider
Provider = provider
}
}
};
@ -89,11 +89,11 @@ namespace Kyoo.TheTvdb
Genres = series.Genre.Select(y => new Genre(y)).ToList(),
ExternalIDs = new[]
{
new MetadataID<Show>
new MetadataID
{
DataID = series.Id.ToString(),
Link = $"https://www.thetvdb.com/series/{series.Slug}",
Second = provider
Provider = provider
}
}
};
@ -116,11 +116,11 @@ namespace Kyoo.TheTvdb
Poster = actor.Image != null ? $"https://www.thetvdb.com/banners/{actor.Image}" : null,
ExternalIDs = new []
{
new MetadataID<People>()
new MetadataID
{
DataID = actor.Id.ToString(),
Link = $"https://www.thetvdb.com/people/{actor.Id}",
Second = provider
Provider = provider
}
}
},
@ -147,11 +147,11 @@ namespace Kyoo.TheTvdb
Thumb = episode.Filename != null ? $"https://www.thetvdb.com/banners/{episode.Filename}" : null,
ExternalIDs = new[]
{
new MetadataID<Episode>
new MetadataID
{
DataID = episode.Id.ToString(),
Link = $"https://www.thetvdb.com/series/{episode.SeriesId}/episodes/{episode.Id}",
Second = provider
Provider = provider
}
}
};

View File

@ -18,6 +18,11 @@ namespace Kyoo.Controllers
/// </summary>
private readonly DatabaseContext _database;
/// <summary>
/// A provider repository to handle externalID creation and deletion
/// </summary>
private readonly IProviderRepository _providers;
/// <inheritdoc />
protected override Expression<Func<Collection, object>> DefaultSort => x => x.Name;
@ -25,10 +30,12 @@ namespace Kyoo.Controllers
/// Create a new <see cref="CollectionRepository"/>.
/// </summary>
/// <param name="database">The database handle to use</param>
public CollectionRepository(DatabaseContext database)
/// /// <param name="providers">A provider repository</param>
public CollectionRepository(DatabaseContext database, IProviderRepository providers)
: base(database)
{
_database = database;
_providers = providers;
}
/// <inheritdoc />
@ -46,10 +53,36 @@ namespace Kyoo.Controllers
{
await base.Create(obj);
_database.Entry(obj).State = EntityState.Added;
obj.ExternalIDs.ForEach(x => _database.Entry(x).State = EntityState.Added);
await _database.SaveChangesAsync($"Trying to insert a duplicated collection (slug {obj.Slug} already exists).");
return obj;
}
/// <inheritdoc />
protected override async Task Validate(Collection resource)
{
await base.Validate(resource);
await resource.ExternalIDs.ForEachAsync(async x =>
{
x.Provider = await _providers.CreateIfNotExists(x.Provider);
x.ProviderID = x.Provider.ID;
x.ResourceType = nameof(Collection);
_database.Entry(x.Provider).State = EntityState.Detached;
});
}
/// <inheritdoc />
protected override async Task EditRelations(Collection resource, Collection changed, bool resetOld)
{
if (changed.ExternalIDs != null || resetOld)
{
await Database.Entry(resource).Collection(x => x.ExternalIDs).LoadAsync();
resource.ExternalIDs = changed.ExternalIDs;
}
await base.EditRelations(resource, changed, resetOld);
}
/// <inheritdoc />
public override async Task Delete(Collection obj)
{

View File

@ -160,9 +160,10 @@ namespace Kyoo.Controllers
await base.Validate(resource);
await resource.ExternalIDs.ForEachAsync(async x =>
{
x.Second = await _providers.CreateIfNotExists(x.Second);
x.SecondID = x.Second.ID;
_database.Entry(x.Second).State = EntityState.Detached;
x.Provider = await _providers.CreateIfNotExists(x.Provider);
x.ProviderID = x.Provider.ID;
x.ResourceType = nameof(Episode);
_database.Entry(x.Provider).State = EntityState.Detached;
});
}

View File

@ -73,9 +73,10 @@ namespace Kyoo.Controllers
await base.Validate(resource);
await resource.ExternalIDs.ForEachAsync(async id =>
{
id.Second = await _providers.CreateIfNotExists(id.Second);
id.SecondID = id.Second.ID;
_database.Entry(id.Second).State = EntityState.Detached;
id.Provider = await _providers.CreateIfNotExists(id.Provider);
id.ProviderID = id.Provider.ID;
id.ResourceType = nameof(People);
_database.Entry(id.Provider).State = EntityState.Detached;
});
await resource.Roles.ForEachAsync(async role =>
{

View File

@ -9,21 +9,18 @@ using Microsoft.EntityFrameworkCore;
namespace Kyoo.Controllers
{
/// <summary>
/// A local repository to handle providers.
/// A local repository to handle providers.
/// </summary>
public class ProviderRepository : LocalRepository<Provider>, IProviderRepository
{
/// <summary>
/// The database handle
/// The database handle
/// </summary>
private readonly DatabaseContext _database;
/// <inheritdoc />
protected override Expression<Func<Provider, object>> DefaultSort => x => x.Slug;
/// <summary>
/// Create a new <see cref="ProviderRepository"/>.
/// Create a new <see cref="ProviderRepository" />.
/// </summary>
/// <param name="database">The database handle</param>
public ProviderRepository(DatabaseContext database)
@ -32,6 +29,9 @@ namespace Kyoo.Controllers
_database = database;
}
/// <inheritdoc />
protected override Expression<Func<Provider, object>> DefaultSort => x => x.Slug;
/// <inheritdoc />
public override async Task<ICollection<Provider>> Search(string query)
{
@ -47,7 +47,8 @@ namespace Kyoo.Controllers
{
await base.Create(obj);
_database.Entry(obj).State = EntityState.Added;
await _database.SaveChangesAsync($"Trying to insert a duplicated provider (slug {obj.Slug} already exists).");
await _database.SaveChangesAsync("Trying to insert a duplicated provider " +
$"(slug {obj.Slug} already exists).");
return obj;
}
@ -62,14 +63,18 @@ namespace Kyoo.Controllers
}
/// <inheritdoc />
public Task<ICollection<MetadataID<T>>> GetMetadataID<T>(Expression<Func<MetadataID<T>, bool>> where = null,
Sort<MetadataID<T>> sort = default,
public Task<ICollection<MetadataID>> GetMetadataID<T>(Expression<Func<MetadataID, bool>> where = null,
Sort<MetadataID> sort = default,
Pagination limit = default)
where T : class, IResource
where T : class, IMetadata
{
return ApplyFilters(_database.MetadataIds<T>().Include(y => y.Second),
x => _database.MetadataIds<T>().FirstOrDefaultAsync(y => y.FirstID == x),
x => x.FirstID,
string discriminator = typeof(T).Name;
return ApplyFilters(_database.MetadataIDs
.Include(y => y.Provider)
.Where(x => x.ResourceType == discriminator),
x => _database.MetadataIDs.FirstOrDefaultAsync(y => y.ResourceID == x
&& y.ResourceType == discriminator),
x => x.ResourceID,
where,
sort,
limit);

View File

@ -106,9 +106,10 @@ namespace Kyoo.Controllers
await base.Validate(resource);
await resource.ExternalIDs.ForEachAsync(async id =>
{
id.Second = await _providers.CreateIfNotExists(id.Second);
id.SecondID = id.Second.ID;
_database.Entry(id.Second).State = EntityState.Detached;
id.Provider = await _providers.CreateIfNotExists(id.Provider);
id.ProviderID = id.Provider.ID;
id.ResourceType = nameof(Season);
_database.Entry(id.Provider).State = EntityState.Detached;
});
}

View File

@ -101,9 +101,10 @@ namespace Kyoo.Controllers
});
await resource.ExternalIDs.ForEachAsync(async id =>
{
id.Second = await _providers.CreateIfNotExists(id.Second);
id.SecondID = id.Second.ID;
_database.Entry(id.Second).State = EntityState.Detached;
id.Provider = await _providers.CreateIfNotExists(id.Provider);
id.ProviderID = id.Provider.ID;
id.ResourceType = nameof(Show);
_database.Entry(id.Provider).State = EntityState.Detached;
});
await resource.People.ForEachAsync(async role =>
{

View File

@ -18,6 +18,11 @@ namespace Kyoo.Controllers
/// </summary>
private readonly DatabaseContext _database;
/// <summary>
/// A provider repository to handle externalID creation and deletion
/// </summary>
private readonly IProviderRepository _providers;
/// <inheritdoc />
protected override Expression<Func<Studio, object>> DefaultSort => x => x.Name;
@ -26,10 +31,12 @@ namespace Kyoo.Controllers
/// Create a new <see cref="StudioRepository"/>.
/// </summary>
/// <param name="database">The database handle</param>
public StudioRepository(DatabaseContext database)
/// <param name="providers">A provider repository</param>
public StudioRepository(DatabaseContext database, IProviderRepository providers)
: base(database)
{
_database = database;
_providers = providers;
}
/// <inheritdoc />
@ -51,6 +58,31 @@ namespace Kyoo.Controllers
return obj;
}
/// <inheritdoc />
protected override async Task Validate(Studio resource)
{
await base.Validate(resource);
await resource.ExternalIDs.ForEachAsync(async x =>
{
x.Provider = await _providers.CreateIfNotExists(x.Provider);
x.ProviderID = x.Provider.ID;
x.ResourceType = nameof(Studio);
_database.Entry(x.Provider).State = EntityState.Detached;
});
}
/// <inheritdoc />
protected override async Task EditRelations(Studio resource, Studio changed, bool resetOld)
{
if (changed.ExternalIDs != null || resetOld)
{
await Database.Entry(resource).Collection(x => x.ExternalIDs).LoadAsync();
resource.ExternalIDs = changed.ExternalIDs;
}
await base.EditRelations(resource, changed, resetOld);
}
/// <inheritdoc />
public override async Task Delete(Studio obj)
{