mirror of
https://github.com/zoriya/Kyoo.git
synced 2025-07-09 03:04:20 -04:00
Merge pull request #32 from AnonymusRaccoon/the-moviedb
Adding TheMovieDB provider
This commit is contained in:
commit
387bf4c34d
4
.github/workflows/tests.yml
vendored
4
.github/workflows/tests.yml
vendored
@ -24,7 +24,7 @@ jobs:
|
|||||||
- name: Build
|
- name: Build
|
||||||
run: |
|
run: |
|
||||||
dotnet build --no-restore '-p:SkipWebApp=true;SkipTranscoder=true' -p:CopyLocalLockFileAssemblies=true
|
dotnet build --no-restore '-p:SkipWebApp=true;SkipTranscoder=true' -p:CopyLocalLockFileAssemblies=true
|
||||||
cp ./Kyoo.Common/bin/Debug/net5.0/Microsoft.Extensions.DependencyInjection.Abstractions.dll ./Kyoo.Tests/bin/Debug/net5.0/
|
cp ./Kyoo.Common/bin/Debug/net5.0/Microsoft.Extensions.DependencyInjection.Abstractions.dll ./tests/Kyoo.Tests/bin/Debug/net5.0/
|
||||||
- name: Test
|
- name: Test
|
||||||
run: dotnet test --no-build '-p:CollectCoverage=true;CoverletOutputFormat=opencover'
|
run: dotnet test --no-build '-p:CollectCoverage=true;CoverletOutputFormat=opencover'
|
||||||
env:
|
env:
|
||||||
@ -33,7 +33,7 @@ jobs:
|
|||||||
POSTGRES_PASSWORD: postgres
|
POSTGRES_PASSWORD: postgres
|
||||||
- name: Sanitize coverage output
|
- name: Sanitize coverage output
|
||||||
if: ${{ always() }}
|
if: ${{ always() }}
|
||||||
run: sed -i "s'$(pwd)'.'" Kyoo.Tests/coverage.opencover.xml
|
run: sed -i "s'$(pwd)'.'" tests/Kyoo.Tests/coverage.opencover.xml
|
||||||
- name: Upload coverage report
|
- name: Upload coverage report
|
||||||
if: ${{ always() }}
|
if: ${{ always() }}
|
||||||
uses: actions/upload-artifact@v2
|
uses: actions/upload-artifact@v2
|
||||||
|
@ -2,11 +2,25 @@ using System.Collections.Generic;
|
|||||||
using System.IO;
|
using System.IO;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using JetBrains.Annotations;
|
using JetBrains.Annotations;
|
||||||
using Kyoo.Models;
|
|
||||||
using Microsoft.AspNetCore.Mvc;
|
using Microsoft.AspNetCore.Mvc;
|
||||||
|
|
||||||
namespace Kyoo.Controllers
|
namespace Kyoo.Controllers
|
||||||
{
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// A class wrapping a value that will be set after the completion of the task it is related to.
|
||||||
|
/// </summary>
|
||||||
|
/// <remarks>
|
||||||
|
/// This class replace the use of an out parameter on a task since tasks and out can't be combined.
|
||||||
|
/// </remarks>
|
||||||
|
/// <typeparam name="T">The type of the value</typeparam>
|
||||||
|
public class AsyncRef<T>
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// The value that will be set before the completion of the task.
|
||||||
|
/// </summary>
|
||||||
|
public T Value { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// A service to abstract the file system to allow custom file systems (like distant file systems or external providers)
|
/// A service to abstract the file system to allow custom file systems (like distant file systems or external providers)
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@ -43,6 +57,16 @@ namespace Kyoo.Controllers
|
|||||||
/// <returns>A reader to read the file.</returns>
|
/// <returns>A reader to read the file.</returns>
|
||||||
public Task<Stream> GetReader([NotNull] string path);
|
public Task<Stream> GetReader([NotNull] string path);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Read a file present at <paramref name="path"/>. The reader can be used in an arbitrary context.
|
||||||
|
/// To return files from an http endpoint, use <see cref="FileResult"/>.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="path">The path of the file</param>
|
||||||
|
/// <param name="mime">The mime type of the opened file.</param>
|
||||||
|
/// <exception cref="FileNotFoundException">If the file could not be found.</exception>
|
||||||
|
/// <returns>A reader to read the file.</returns>
|
||||||
|
public Task<Stream> GetReader([NotNull] string path, AsyncRef<string> mime);
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Create a new file at <paramref name="path"></paramref>.
|
/// Create a new file at <paramref name="path"></paramref>.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@ -81,12 +105,13 @@ namespace Kyoo.Controllers
|
|||||||
public Task<bool> Exists([NotNull] string path);
|
public Task<bool> Exists([NotNull] string path);
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Get the extra directory of a show.
|
/// Get the extra directory of a resource <typeparamref name="T"/>.
|
||||||
/// This method is in this system to allow a filesystem to use a different metadata policy for one.
|
/// This method is in this system to allow a filesystem to use a different metadata policy for one.
|
||||||
/// It can be useful if the filesystem is readonly.
|
/// It can be useful if the filesystem is readonly.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="show">The show to proceed</param>
|
/// <param name="resource">The resource to proceed</param>
|
||||||
/// <returns>The extra directory of the show</returns>
|
/// <typeparam name="T">The type of the resource.</typeparam>
|
||||||
public string GetExtraDirectory([NotNull] Show show);
|
/// <returns>The extra directory of the resource.</returns>
|
||||||
|
public Task<string> GetExtraDirectory<T>([NotNull] T resource);
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -225,42 +225,51 @@ namespace Kyoo.Controllers
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="obj">The source object.</param>
|
/// <param name="obj">The source object.</param>
|
||||||
/// <param name="member">A getter function for the member to load</param>
|
/// <param name="member">A getter function for the member to load</param>
|
||||||
|
/// <param name="force">
|
||||||
|
/// <c>true</c> if you want to load the relation even if it is not null, <c>false</c> otherwise.
|
||||||
|
/// </param>
|
||||||
/// <typeparam name="T">The type of the source object</typeparam>
|
/// <typeparam name="T">The type of the source object</typeparam>
|
||||||
/// <typeparam name="T2">The related resource's type</typeparam>
|
/// <typeparam name="T2">The related resource's type</typeparam>
|
||||||
/// <returns>The param <see cref="obj"/></returns>
|
/// <returns>The param <see cref="obj"/></returns>
|
||||||
/// <seealso cref="Load{T,T2}(T,System.Linq.Expressions.Expression{System.Func{T,System.Collections.Generic.ICollection{T2}}})"/>
|
/// <seealso cref="Load{T,T2}(T, System.Linq.Expressions.Expression{System.Func{T,System.Collections.Generic.ICollection{T2}}}, bool)"/>
|
||||||
/// <seealso cref="Load{T}(T, System.String)"/>
|
/// <seealso cref="Load{T}(T, System.String, bool)"/>
|
||||||
/// <seealso cref="Load(IResource, string)"/>
|
/// <seealso cref="Load(IResource, string, bool)"/>
|
||||||
Task<T> Load<T, T2>([NotNull] T obj, Expression<Func<T, T2>> member)
|
Task<T> Load<T, T2>([NotNull] T obj, Expression<Func<T, T2>> member, bool force = false)
|
||||||
where T : class, IResource
|
where T : class, IResource
|
||||||
where T2 : class, IResource, new();
|
where T2 : class, IResource;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Load a collection of related resource
|
/// Load a collection of related resource
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="obj">The source object.</param>
|
/// <param name="obj">The source object.</param>
|
||||||
/// <param name="member">A getter function for the member to load</param>
|
/// <param name="member">A getter function for the member to load</param>
|
||||||
|
/// <param name="force">
|
||||||
|
/// <c>true</c> if you want to load the relation even if it is not null, <c>false</c> otherwise.
|
||||||
|
/// </param>
|
||||||
/// <typeparam name="T">The type of the source object</typeparam>
|
/// <typeparam name="T">The type of the source object</typeparam>
|
||||||
/// <typeparam name="T2">The related resource's type</typeparam>
|
/// <typeparam name="T2">The related resource's type</typeparam>
|
||||||
/// <returns>The param <see cref="obj"/></returns>
|
/// <returns>The param <see cref="obj"/></returns>
|
||||||
/// <seealso cref="Load{T,T2}(T,System.Linq.Expressions.Expression{System.Func{T,T2}})"/>
|
/// <seealso cref="Load{T,T2}(T, System.Linq.Expressions.Expression{System.Func{T,T2}}, bool)"/>
|
||||||
/// <seealso cref="Load{T}(T, System.String)"/>
|
/// <seealso cref="Load{T}(T, System.String, bool)"/>
|
||||||
/// <seealso cref="Load(IResource, string)"/>
|
/// <seealso cref="Load(IResource, string, bool)"/>
|
||||||
Task<T> Load<T, T2>([NotNull] T obj, Expression<Func<T, ICollection<T2>>> member)
|
Task<T> Load<T, T2>([NotNull] T obj, Expression<Func<T, ICollection<T2>>> member, bool force = false)
|
||||||
where T : class, IResource
|
where T : class, IResource
|
||||||
where T2 : class, new();
|
where T2 : class;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Load a related resource by it's name
|
/// Load a related resource by it's name
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="obj">The source object.</param>
|
/// <param name="obj">The source object.</param>
|
||||||
/// <param name="memberName">The name of the resource to load (case sensitive)</param>
|
/// <param name="memberName">The name of the resource to load (case sensitive)</param>
|
||||||
|
/// <param name="force">
|
||||||
|
/// <c>true</c> if you want to load the relation even if it is not null, <c>false</c> otherwise.
|
||||||
|
/// </param>
|
||||||
/// <typeparam name="T">The type of the source object</typeparam>
|
/// <typeparam name="T">The type of the source object</typeparam>
|
||||||
/// <returns>The param <see cref="obj"/></returns>
|
/// <returns>The param <see cref="obj"/></returns>
|
||||||
/// <seealso cref="Load{T,T2}(T,System.Linq.Expressions.Expression{System.Func{T,T2}})"/>
|
/// <seealso cref="Load{T,T2}(T, System.Linq.Expressions.Expression{System.Func{T,T2}}, bool)"/>
|
||||||
/// <seealso cref="Load{T,T2}(T,System.Linq.Expressions.Expression{System.Func{T,System.Collections.Generic.ICollection{T2}}})"/>
|
/// <seealso cref="Load{T,T2}(T, System.Linq.Expressions.Expression{System.Func{T,System.Collections.Generic.ICollection{T2}}}, bool)"/>
|
||||||
/// <seealso cref="Load(IResource, string)"/>
|
/// <seealso cref="Load(IResource, string, bool)"/>
|
||||||
Task<T> Load<T>([NotNull] T obj, string memberName)
|
Task<T> Load<T>([NotNull] T obj, string memberName, bool force = false)
|
||||||
where T : class, IResource;
|
where T : class, IResource;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@ -268,10 +277,13 @@ namespace Kyoo.Controllers
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="obj">The source object.</param>
|
/// <param name="obj">The source object.</param>
|
||||||
/// <param name="memberName">The name of the resource to load (case sensitive)</param>
|
/// <param name="memberName">The name of the resource to load (case sensitive)</param>
|
||||||
/// <seealso cref="Load{T,T2}(T,System.Linq.Expressions.Expression{System.Func{T,T2}})"/>
|
/// <param name="force">
|
||||||
/// <seealso cref="Load{T,T2}(T,System.Linq.Expressions.Expression{System.Func{T,System.Collections.Generic.ICollection{T2}}})"/>
|
/// <c>true</c> if you want to load the relation even if it is not null, <c>false</c> otherwise.
|
||||||
/// <seealso cref="Load{T}(T, System.String)"/>
|
/// </param>
|
||||||
Task Load([NotNull] IResource obj, string memberName);
|
/// <seealso cref="Load{T,T2}(T, System.Linq.Expressions.Expression{System.Func{T,T2}}, bool)"/>
|
||||||
|
/// <seealso cref="Load{T,T2}(T, System.Linq.Expressions.Expression{System.Func{T,System.Collections.Generic.ICollection{T2}}}, bool)"/>
|
||||||
|
/// <seealso cref="Load{T}(T, System.String, bool)"/>
|
||||||
|
Task Load([NotNull] IResource obj, string memberName, bool force = false);
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Get items (A wrapper arround shows or collections) from a library.
|
/// Get items (A wrapper arround shows or collections) from a library.
|
||||||
|
@ -590,10 +590,10 @@ namespace Kyoo.Controllers
|
|||||||
/// <param name="limit">Pagination information (where to start and how many to get)</param>
|
/// <param name="limit">Pagination information (where to start and how many to get)</param>
|
||||||
/// <typeparam name="T">The type of metadata to retrieve</typeparam>
|
/// <typeparam name="T">The type of metadata to retrieve</typeparam>
|
||||||
/// <returns>A filtered list of external ids.</returns>
|
/// <returns>A filtered list of external ids.</returns>
|
||||||
Task<ICollection<MetadataID<T>>> GetMetadataID<T>(Expression<Func<MetadataID<T>, bool>> where = null,
|
Task<ICollection<MetadataID>> GetMetadataID<T>(Expression<Func<MetadataID, bool>> where = null,
|
||||||
Sort<MetadataID<T>> sort = default,
|
Sort<MetadataID> sort = default,
|
||||||
Pagination limit = default)
|
Pagination limit = default)
|
||||||
where T : class, IResource;
|
where T : class, IMetadata;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Get a list of external ids that match all filters
|
/// 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="sort">A sort by expression</param>
|
||||||
/// <param name="limit">Pagination information (where to start and how many to get)</param>
|
/// <param name="limit">Pagination information (where to start and how many to get)</param>
|
||||||
/// <returns>A filtered list of external ids.</returns>
|
/// <returns>A filtered list of external ids.</returns>
|
||||||
Task<ICollection<MetadataID<T>>> GetMetadataID<T>([Optional] Expression<Func<MetadataID<T>, bool>> where,
|
Task<ICollection<MetadataID>> GetMetadataID<T>([Optional] Expression<Func<MetadataID, bool>> where,
|
||||||
Expression<Func<MetadataID<T>, object>> sort,
|
Expression<Func<MetadataID, object>> sort,
|
||||||
Pagination limit = default
|
Pagination limit = default
|
||||||
) where T : class, IResource
|
) where T : class, IMetadata
|
||||||
=> GetMetadataID(where, new Sort<MetadataID<T>>(sort), limit);
|
=> GetMetadataID<T>(where, new Sort<MetadataID>(sort), limit);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
@ -1,5 +1,4 @@
|
|||||||
using System;
|
using Kyoo.Models;
|
||||||
using Kyoo.Models;
|
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using JetBrains.Annotations;
|
using JetBrains.Annotations;
|
||||||
|
|
||||||
@ -23,37 +22,17 @@ namespace Kyoo.Controllers
|
|||||||
/// <typeparam name="T">The type of the item</typeparam>
|
/// <typeparam name="T">The type of the item</typeparam>
|
||||||
/// <returns><c>true</c> if an image has been downloaded, <c>false</c> otherwise.</returns>
|
/// <returns><c>true</c> if an image has been downloaded, <c>false</c> otherwise.</returns>
|
||||||
Task<bool> DownloadImages<T>([NotNull] T item, bool alwaysDownload = false)
|
Task<bool> DownloadImages<T>([NotNull] T item, bool alwaysDownload = false)
|
||||||
where T : IResource;
|
where T : IThumbnails;
|
||||||
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Retrieve the local path of the poster of the given item.
|
/// Retrieve the local path of an image of the given item.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="item">The item to retrieve the poster from.</param>
|
/// <param name="item">The item to retrieve the poster from.</param>
|
||||||
|
/// <param name="imageID">The ID of the image. See <see cref="Images"/> for values.</param>
|
||||||
/// <typeparam name="T">The type of the item</typeparam>
|
/// <typeparam name="T">The type of the item</typeparam>
|
||||||
/// <exception cref="NotSupportedException">If the type does not have a poster</exception>
|
/// <returns>The path of the image for the given resource or null if it does not exists.</returns>
|
||||||
/// <returns>The path of the poster for the given resource (it might or might not exists).</returns>
|
Task<string> GetImagePath<T>([NotNull] T item, int imageID)
|
||||||
Task<string> GetPoster<T>([NotNull] T item)
|
where T : IThumbnails;
|
||||||
where T : IResource;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Retrieve the local path of the logo of the given item.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="item">The item to retrieve the logo from.</param>
|
|
||||||
/// <typeparam name="T">The type of the item</typeparam>
|
|
||||||
/// <exception cref="NotSupportedException">If the type does not have a logo</exception>
|
|
||||||
/// <returns>The path of the logo for the given resource (it might or might not exists).</returns>
|
|
||||||
Task<string> GetLogo<T>([NotNull] T item)
|
|
||||||
where T : IResource;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Retrieve the local path of the thumbnail of the given item.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="item">The item to retrieve the thumbnail from.</param>
|
|
||||||
/// <typeparam name="T">The type of the item</typeparam>
|
|
||||||
/// <exception cref="NotSupportedException">If the type does not have a thumbnail</exception>
|
|
||||||
/// <returns>The path of the thumbnail for the given resource (it might or might not exists).</returns>
|
|
||||||
Task<string> GetThumbnail<T>([NotNull] T item)
|
|
||||||
where T : IResource;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -162,34 +162,6 @@ namespace Kyoo.Controllers
|
|||||||
return await EpisodeRepository.GetOrDefault(showSlug, seasonNumber, episodeNumber);
|
return await EpisodeRepository.GetOrDefault(showSlug, seasonNumber, episodeNumber);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <inheritdoc />
|
|
||||||
public Task<T> Load<T, T2>(T obj, Expression<Func<T, T2>> member)
|
|
||||||
where T : class, IResource
|
|
||||||
where T2 : class, IResource, new()
|
|
||||||
{
|
|
||||||
if (member == null)
|
|
||||||
throw new ArgumentNullException(nameof(member));
|
|
||||||
return Load(obj, Utility.GetPropertyName(member));
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <inheritdoc />
|
|
||||||
public Task<T> Load<T, T2>(T obj, Expression<Func<T, ICollection<T2>>> member)
|
|
||||||
where T : class, IResource
|
|
||||||
where T2 : class, new()
|
|
||||||
{
|
|
||||||
if (member == null)
|
|
||||||
throw new ArgumentNullException(nameof(member));
|
|
||||||
return Load(obj, Utility.GetPropertyName(member));
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <inheritdoc />
|
|
||||||
public async Task<T> Load<T>(T obj, string memberName)
|
|
||||||
where T : class, IResource
|
|
||||||
{
|
|
||||||
await Load(obj as IResource, memberName);
|
|
||||||
return obj;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Set relations between to objects.
|
/// Set relations between to objects.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@ -211,11 +183,46 @@ namespace Kyoo.Controllers
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
public Task Load(IResource obj, string memberName)
|
public Task<T> Load<T, T2>(T obj, Expression<Func<T, T2>> member, bool force = false)
|
||||||
|
where T : class, IResource
|
||||||
|
where T2 : class, IResource
|
||||||
|
{
|
||||||
|
if (member == null)
|
||||||
|
throw new ArgumentNullException(nameof(member));
|
||||||
|
return Load(obj, Utility.GetPropertyName(member), force);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public Task<T> Load<T, T2>(T obj, Expression<Func<T, ICollection<T2>>> member, bool force = false)
|
||||||
|
where T : class, IResource
|
||||||
|
where T2 : class
|
||||||
|
{
|
||||||
|
if (member == null)
|
||||||
|
throw new ArgumentNullException(nameof(member));
|
||||||
|
return Load(obj, Utility.GetPropertyName(member), force);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public async Task<T> Load<T>(T obj, string memberName, bool force = false)
|
||||||
|
where T : class, IResource
|
||||||
|
{
|
||||||
|
await Load(obj as IResource, memberName, force);
|
||||||
|
return obj;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public Task Load(IResource obj, string memberName, bool force = false)
|
||||||
{
|
{
|
||||||
if (obj == null)
|
if (obj == null)
|
||||||
throw new ArgumentNullException(nameof(obj));
|
throw new ArgumentNullException(nameof(obj));
|
||||||
|
|
||||||
|
object existingValue = obj.GetType()
|
||||||
|
.GetProperties()
|
||||||
|
.FirstOrDefault(x => string.Equals(x.Name, memberName, StringComparison.InvariantCultureIgnoreCase))
|
||||||
|
?.GetValue(obj);
|
||||||
|
if (existingValue != null && !force)
|
||||||
|
return Task.CompletedTask;
|
||||||
|
|
||||||
return (obj, member: memberName) switch
|
return (obj, member: memberName) switch
|
||||||
{
|
{
|
||||||
(Library l, nameof(Library.Providers)) => ProviderRepository
|
(Library l, nameof(Library.Providers)) => ProviderRepository
|
||||||
@ -231,7 +238,12 @@ namespace Kyoo.Controllers
|
|||||||
.Then(x => l.Collections = x),
|
.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))
|
.GetAll(x => x.Collections.Any(y => y.ID == obj.ID))
|
||||||
.Then(x => c.Shows = x),
|
.Then(x => c.Shows = x),
|
||||||
|
|
||||||
@ -241,9 +253,9 @@ namespace Kyoo.Controllers
|
|||||||
|
|
||||||
|
|
||||||
(Show s, nameof(Show.ExternalIDs)) => SetRelation(s,
|
(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.ExternalIDs = y,
|
||||||
(x, y) => { x.First = y; x.FirstID = y.ID; }),
|
(x, y) => { x.ResourceID = y.ID; }),
|
||||||
|
|
||||||
(Show s, nameof(Show.Genres)) => GenreRepository
|
(Show s, nameof(Show.Genres)) => GenreRepository
|
||||||
.GetAll(x => x.Shows.Any(y => y.ID == obj.ID))
|
.GetAll(x => x.Shows.Any(y => y.ID == obj.ID))
|
||||||
@ -281,9 +293,9 @@ namespace Kyoo.Controllers
|
|||||||
|
|
||||||
|
|
||||||
(Season s, nameof(Season.ExternalIDs)) => SetRelation(s,
|
(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.ExternalIDs = y,
|
||||||
(x, y) => { x.First = y; x.FirstID = y.ID; }),
|
(x, y) => { x.ResourceID = y.ID; }),
|
||||||
|
|
||||||
(Season s, nameof(Season.Episodes)) => SetRelation(s,
|
(Season s, nameof(Season.Episodes)) => SetRelation(s,
|
||||||
EpisodeRepository.GetAll(x => x.Season.ID == obj.ID),
|
EpisodeRepository.GetAll(x => x.Season.ID == obj.ID),
|
||||||
@ -300,9 +312,9 @@ namespace Kyoo.Controllers
|
|||||||
|
|
||||||
|
|
||||||
(Episode e, nameof(Episode.ExternalIDs)) => SetRelation(e,
|
(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.ExternalIDs = y,
|
||||||
(x, y) => { x.First = y; x.FirstID = y.ID; }),
|
(x, y) => { x.ResourceID = y.ID; }),
|
||||||
|
|
||||||
(Episode e, nameof(Episode.Tracks)) => SetRelation(e,
|
(Episode e, nameof(Episode.Tracks)) => SetRelation(e,
|
||||||
TrackRepository.GetAll(x => x.Episode.ID == obj.ID),
|
TrackRepository.GetAll(x => x.Episode.ID == obj.ID),
|
||||||
@ -344,11 +356,16 @@ namespace Kyoo.Controllers
|
|||||||
.GetAll(x => x.Studio.ID == obj.ID)
|
.GetAll(x => x.Studio.ID == obj.ID)
|
||||||
.Then(x => s.Shows = x),
|
.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,
|
(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.ExternalIDs = y,
|
||||||
(x, y) => { x.First = y; x.FirstID = y.ID; }),
|
(x, y) => { x.ResourceID = y.ID; }),
|
||||||
|
|
||||||
(People p, nameof(People.Roles)) => PeopleRepository
|
(People p, nameof(People.Roles)) => PeopleRepository
|
||||||
.GetFromPeople(obj.ID)
|
.GetFromPeople(obj.ID)
|
||||||
|
@ -16,13 +16,11 @@
|
|||||||
<IncludeSymbols>true</IncludeSymbols>
|
<IncludeSymbols>true</IncludeSymbols>
|
||||||
<SymbolPackageFormat>snupkg</SymbolPackageFormat>
|
<SymbolPackageFormat>snupkg</SymbolPackageFormat>
|
||||||
<LangVersion>default</LangVersion>
|
<LangVersion>default</LangVersion>
|
||||||
|
|
||||||
<DefineConstants>ENABLE_INTERNAL_LINKS</DefineConstants>
|
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<PackageReference Include="Autofac" Version="6.2.0" />
|
<PackageReference Include="Autofac" Version="6.2.0" />
|
||||||
<PackageReference Include="JetBrains.Annotations" Version="2021.1.0" />
|
<PackageReference Include="JetBrains.Annotations" Version="2021.2.0" />
|
||||||
<PackageReference Include="Microsoft.AspNetCore.Mvc.Abstractions" Version="2.2.0" />
|
<PackageReference Include="Microsoft.AspNetCore.Mvc.Abstractions" Version="2.2.0" />
|
||||||
<PackageReference Include="Microsoft.Extensions.Configuration.Abstractions" Version="5.0.0" />
|
<PackageReference Include="Microsoft.Extensions.Configuration.Abstractions" Version="5.0.0" />
|
||||||
<PackageReference Include="Microsoft.Extensions.DependencyInjection.Abstractions" Version="5.0.0" />
|
<PackageReference Include="Microsoft.Extensions.DependencyInjection.Abstractions" Version="5.0.0" />
|
||||||
|
@ -1,13 +0,0 @@
|
|||||||
using System;
|
|
||||||
using JetBrains.Annotations;
|
|
||||||
using Kyoo.Models.Attributes;
|
|
||||||
|
|
||||||
namespace Kyoo.Common.Models.Attributes
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// An attribute to mark Link properties on resource.
|
|
||||||
/// </summary>
|
|
||||||
[AttributeUsage(AttributeTargets.Property)]
|
|
||||||
[MeansImplicitUse]
|
|
||||||
public class LinkAttribute : SerializeIgnoreAttribute { }
|
|
||||||
}
|
|
@ -1,4 +1,5 @@
|
|||||||
using System;
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
using System.Linq.Expressions;
|
using System.Linq.Expressions;
|
||||||
using Kyoo.Models.Attributes;
|
using Kyoo.Models.Attributes;
|
||||||
|
|
||||||
@ -18,7 +19,7 @@ namespace Kyoo.Models
|
|||||||
/// A type union between <see cref="Show"/> and <see cref="Collection"/>.
|
/// A type union between <see cref="Show"/> and <see cref="Collection"/>.
|
||||||
/// This is used to list content put inside a library.
|
/// This is used to list content put inside a library.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public class LibraryItem : IResource
|
public class LibraryItem : IResource, IThumbnails
|
||||||
{
|
{
|
||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
public int ID { get; set; }
|
public int ID { get; set; }
|
||||||
@ -53,12 +54,16 @@ namespace Kyoo.Models
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public DateTime? EndAir { get; set; }
|
public DateTime? EndAir { get; set; }
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public Dictionary<int, string> Images { get; set; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The path of this item's poster.
|
/// The path of this item's poster.
|
||||||
/// By default, the http path for this poster is returned from the public API.
|
/// By default, the http path for this poster is returned from the public API.
|
||||||
/// This can be disabled using the internal query flag.
|
/// This can be disabled using the internal query flag.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[SerializeAs("{HOST}/api/{Type:l}/{Slug}/poster")] public string Poster { get; set; }
|
[SerializeAs("{HOST}/api/{Type:l}/{Slug}/poster")]
|
||||||
|
public string Poster => Images?.GetValueOrDefault(Models.Images.Poster);
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The type of this item (ether a collection, a show or a movie).
|
/// The type of this item (ether a collection, a show or a movie).
|
||||||
@ -84,7 +89,7 @@ namespace Kyoo.Models
|
|||||||
Status = show.Status;
|
Status = show.Status;
|
||||||
StartAir = show.StartAir;
|
StartAir = show.StartAir;
|
||||||
EndAir = show.EndAir;
|
EndAir = show.EndAir;
|
||||||
Poster = show.Poster;
|
Images = show.Images;
|
||||||
Type = show.IsMovie ? ItemType.Movie : ItemType.Show;
|
Type = show.IsMovie ? ItemType.Movie : ItemType.Show;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -101,7 +106,7 @@ namespace Kyoo.Models
|
|||||||
Status = Models.Status.Unknown;
|
Status = Models.Status.Unknown;
|
||||||
StartAir = null;
|
StartAir = null;
|
||||||
EndAir = null;
|
EndAir = null;
|
||||||
Poster = collection.Poster;
|
Images = collection.Images;
|
||||||
Type = ItemType.Collection;
|
Type = ItemType.Collection;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -117,7 +122,7 @@ namespace Kyoo.Models
|
|||||||
Status = x.Status,
|
Status = x.Status,
|
||||||
StartAir = x.StartAir,
|
StartAir = x.StartAir,
|
||||||
EndAir = x.EndAir,
|
EndAir = x.EndAir,
|
||||||
Poster= x.Poster,
|
Images = x.Images,
|
||||||
Type = x.IsMovie ? ItemType.Movie : ItemType.Show
|
Type = x.IsMovie ? ItemType.Movie : ItemType.Show
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -133,7 +138,7 @@ namespace Kyoo.Models
|
|||||||
Status = Models.Status.Unknown,
|
Status = Models.Status.Unknown,
|
||||||
StartAir = null,
|
StartAir = null,
|
||||||
EndAir = null,
|
EndAir = null,
|
||||||
Poster = x.Poster,
|
Images = x.Images,
|
||||||
Type = ItemType.Collection
|
Type = ItemType.Collection
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
@ -1,163 +0,0 @@
|
|||||||
using System;
|
|
||||||
using System.Linq.Expressions;
|
|
||||||
using Kyoo.Models.Attributes;
|
|
||||||
|
|
||||||
namespace Kyoo.Models
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// A class representing a link between two resources.
|
|
||||||
/// </summary>
|
|
||||||
/// <remarks>
|
|
||||||
/// Links should only be used on the data layer and not on other application code.
|
|
||||||
/// </remarks>
|
|
||||||
public class Link
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// The ID of the first item of the link.
|
|
||||||
/// The first item of the link should be the one to own the link.
|
|
||||||
/// </summary>
|
|
||||||
public int FirstID { get; set; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// The ID of the second item of this link
|
|
||||||
/// The second item of the link should be the owned resource.
|
|
||||||
/// </summary>
|
|
||||||
public int SecondID { get; set; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Create a new typeless <see cref="Link"/>.
|
|
||||||
/// </summary>
|
|
||||||
public Link() {}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Create a new typeless <see cref="Link"/> with two IDs.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="firstID">The ID of the first resource</param>
|
|
||||||
/// <param name="secondID">The ID of the second resource</param>
|
|
||||||
public Link(int firstID, int secondID)
|
|
||||||
{
|
|
||||||
FirstID = firstID;
|
|
||||||
SecondID = secondID;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Create a new typeless <see cref="Link"/> between two resources.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="first">The first resource</param>
|
|
||||||
/// <param name="second">The second resource</param>
|
|
||||||
public Link(IResource first, IResource second)
|
|
||||||
{
|
|
||||||
FirstID = first.ID;
|
|
||||||
SecondID = second.ID;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Create a new typed link between two resources.
|
|
||||||
/// This method can be used instead of the constructor to make use of generic parameters deduction.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="first">The first resource</param>
|
|
||||||
/// <param name="second">The second resource</param>
|
|
||||||
/// <typeparam name="T">The type of the first resource</typeparam>
|
|
||||||
/// <typeparam name="T2">The type of the second resource</typeparam>
|
|
||||||
/// <returns>A newly created typed link with both resources</returns>
|
|
||||||
public static Link<T, T2> Create<T, T2>(T first, T2 second)
|
|
||||||
where T : class, IResource
|
|
||||||
where T2 : class, IResource
|
|
||||||
{
|
|
||||||
return new(first, second);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Create a new typed link between two resources without storing references to resources.
|
|
||||||
/// This is the same as <see cref="Create{T,T2}"/> but this method does not set <see cref="Link{T1,T2}.First"/>
|
|
||||||
/// and <see cref="Link{T1,T2}.Second"/> fields. Only IDs are stored and not references.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="first">The first resource</param>
|
|
||||||
/// <param name="second">The second resource</param>
|
|
||||||
/// <typeparam name="T">The type of the first resource</typeparam>
|
|
||||||
/// <typeparam name="T2">The type of the second resource</typeparam>
|
|
||||||
/// <returns>A newly created typed link with both resources</returns>
|
|
||||||
public static Link<T, T2> UCreate<T, T2>(T first, T2 second)
|
|
||||||
where T : class, IResource
|
|
||||||
where T2 : class, IResource
|
|
||||||
{
|
|
||||||
return new(first, second, true);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// The expression to retrieve the unique ID of a Link. This is an aggregate of the two resources IDs.
|
|
||||||
/// </summary>
|
|
||||||
public static Expression<Func<Link, object>> PrimaryKey
|
|
||||||
{
|
|
||||||
get
|
|
||||||
{
|
|
||||||
return x => new {First = x.FirstID, Second = x.SecondID};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// A strongly typed link between two resources.
|
|
||||||
/// </summary>
|
|
||||||
/// <typeparam name="T1">The type of the first resource</typeparam>
|
|
||||||
/// <typeparam name="T2">The type of the second resource</typeparam>
|
|
||||||
public class Link<T1, T2> : Link
|
|
||||||
where T1 : class, IResource
|
|
||||||
where T2 : class, IResource
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// A reference of the first resource.
|
|
||||||
/// </summary>
|
|
||||||
[SerializeIgnore] public T1 First { get; set; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// A reference to the second resource.
|
|
||||||
/// </summary>
|
|
||||||
[SerializeIgnore] public T2 Second { get; set; }
|
|
||||||
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Create a new, empty, typed <see cref="Link{T1,T2}"/>.
|
|
||||||
/// </summary>
|
|
||||||
public Link() {}
|
|
||||||
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Create a new typed link with two resources.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="first">The first resource</param>
|
|
||||||
/// <param name="second">The second resource</param>
|
|
||||||
/// <param name="privateItems">
|
|
||||||
/// True if no reference to resources should be kept, false otherwise.
|
|
||||||
/// The default is false (references are kept).
|
|
||||||
/// </param>
|
|
||||||
public Link(T1 first, T2 second, bool privateItems = false)
|
|
||||||
: base(first, second)
|
|
||||||
{
|
|
||||||
if (privateItems)
|
|
||||||
return;
|
|
||||||
First = first;
|
|
||||||
Second = second;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Create a new typed link with IDs only.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="firstID">The ID of the first resource</param>
|
|
||||||
/// <param name="secondID">The ID of the second resource</param>
|
|
||||||
public Link(int firstID, int secondID)
|
|
||||||
: base(firstID, secondID)
|
|
||||||
{ }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// The expression to retrieve the unique ID of a typed Link. This is an aggregate of the two resources IDs.
|
|
||||||
/// </summary>
|
|
||||||
public new static Expression<Func<Link<T1, T2>, object>> PrimaryKey
|
|
||||||
{
|
|
||||||
get
|
|
||||||
{
|
|
||||||
return x => new {First = x.FirstID, Second = x.SecondID};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,15 +1,29 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Linq.Expressions;
|
using System.Linq.Expressions;
|
||||||
|
using Kyoo.Models.Attributes;
|
||||||
|
|
||||||
namespace Kyoo.Models
|
namespace Kyoo.Models
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// ID and link of an item on an external provider.
|
/// ID and link of an item on an external provider.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <typeparam name="T"></typeparam>
|
public class MetadataID
|
||||||
public class MetadataID<T> : Link<T, Provider>
|
|
||||||
where T : class, IResource
|
|
||||||
{
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// The ID of the resource which possess the metadata.
|
||||||
|
/// </summary>
|
||||||
|
[SerializeIgnore] public int ResourceID { 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>
|
/// <summary>
|
||||||
/// The ID of the resource on the external provider.
|
/// The ID of the resource on the external provider.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@ -20,21 +34,12 @@ namespace Kyoo.Models
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public string Link { get; set; }
|
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>
|
/// <summary>
|
||||||
/// The expression to retrieve the unique ID of a MetadataID. This is an aggregate of the two resources IDs.
|
/// The expression to retrieve the unique ID of a MetadataID. This is an aggregate of the two resources IDs.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public new static Expression<Func<MetadataID<T>, object>> PrimaryKey
|
public static Expression<Func<MetadataID, object>> PrimaryKey
|
||||||
{
|
{
|
||||||
get
|
get { return x => new { First = x.ResourceID, Second = x.ProviderID }; }
|
||||||
{
|
|
||||||
return x => new {First = x.FirstID, Second = x.SecondID};
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -17,8 +17,8 @@ namespace Kyoo.Models
|
|||||||
public string Slug => ForPeople ? Show.Slug : People.Slug;
|
public string Slug => ForPeople ? Show.Slug : People.Slug;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Should this role be used as a Show substitute (the value is <c>false</c>) or
|
/// Should this role be used as a Show substitute (the value is <c>true</c>) or
|
||||||
/// as a People substitute (the value is <c>true</c>).
|
/// as a People substitute (the value is <c>false</c>).
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public bool ForPeople { get; set; }
|
public bool ForPeople { get; set; }
|
||||||
|
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
using System.Collections.Generic;
|
using System;
|
||||||
using Kyoo.Common.Models.Attributes;
|
using System.Collections.Generic;
|
||||||
using Kyoo.Models.Attributes;
|
using Kyoo.Models.Attributes;
|
||||||
|
|
||||||
namespace Kyoo.Models
|
namespace Kyoo.Models
|
||||||
@ -8,7 +8,7 @@ namespace Kyoo.Models
|
|||||||
/// A class representing collections of <see cref="Show"/>.
|
/// A class representing collections of <see cref="Show"/>.
|
||||||
/// A collection can also be stored in a <see cref="Library"/>.
|
/// A collection can also be stored in a <see cref="Library"/>.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public class Collection : IResource
|
public class Collection : IResource, IMetadata, IThumbnails
|
||||||
{
|
{
|
||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
public int ID { get; set; }
|
public int ID { get; set; }
|
||||||
@ -21,12 +21,17 @@ namespace Kyoo.Models
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public string Name { get; set; }
|
public string Name { get; set; }
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public Dictionary<int, string> Images { get; set; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The path of this poster.
|
/// The path of this poster.
|
||||||
/// By default, the http path for this poster is returned from the public API.
|
/// By default, the http path for this poster is returned from the public API.
|
||||||
/// This can be disabled using the internal query flag.
|
/// This can be disabled using the internal query flag.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[SerializeAs("{HOST}/api/collection/{Slug}/poster")] public string Poster { get; set; }
|
[SerializeAs("{HOST}/api/collection/{Slug}/poster")]
|
||||||
|
[Obsolete("Use Images instead of this, this is only kept for the API response.")]
|
||||||
|
public string Poster => Images?.GetValueOrDefault(Models.Images.Poster);
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The description of this collection.
|
/// The description of this collection.
|
||||||
@ -43,17 +48,7 @@ namespace Kyoo.Models
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
[LoadableRelation] public ICollection<Library> Libraries { get; set; }
|
[LoadableRelation] public ICollection<Library> Libraries { get; set; }
|
||||||
|
|
||||||
#if ENABLE_INTERNAL_LINKS
|
/// <inheritdoc />
|
||||||
|
[EditableRelation] [LoadableRelation] public ICollection<MetadataID> ExternalIDs { get; set; }
|
||||||
/// <summary>
|
|
||||||
/// The internal link between this collection and shows in the <see cref="Shows"/> list.
|
|
||||||
/// </summary>
|
|
||||||
[Link] public ICollection<Link<Collection, Show>> ShowLinks { get; set; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// The internal link between this collection and libraries in the <see cref="Libraries"/> list.
|
|
||||||
/// </summary>
|
|
||||||
[Link] public ICollection<Link<Library, Collection>> LibraryLinks { get; set; }
|
|
||||||
#endif
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -10,7 +10,7 @@ namespace Kyoo.Models
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// A class to represent a single show's episode.
|
/// A class to represent a single show's episode.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public class Episode : IResource
|
public class Episode : IResource, IMetadata, IThumbnails
|
||||||
{
|
{
|
||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
public int ID { get; set; }
|
public int ID { get; set; }
|
||||||
@ -74,9 +74,13 @@ namespace Kyoo.Models
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
[SerializeIgnore] public int? SeasonID { get; set; }
|
[SerializeIgnore] public int? SeasonID { get; set; }
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The season that contains this episode. This must be explicitly loaded via a call to <see cref="ILibraryManager.Load"/>.
|
/// The season that contains this episode.
|
||||||
/// This can be null if the season is unknown and the episode is only identified by it's <see cref="AbsoluteNumber"/>.
|
/// This must be explicitly loaded via a call to <see cref="ILibraryManager.Load"/>.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
/// <remarks>
|
||||||
|
/// This can be null if the season is unknown and the episode is only identified
|
||||||
|
/// by it's <see cref="AbsoluteNumber"/>.
|
||||||
|
/// </remarks>
|
||||||
[LoadableRelation(nameof(SeasonID))] public Season Season { get; set; }
|
[LoadableRelation(nameof(SeasonID))] public Season Season { get; set; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@ -85,7 +89,7 @@ namespace Kyoo.Models
|
|||||||
public int? SeasonNumber { get; set; }
|
public int? SeasonNumber { get; set; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The number of this episode is it's season.
|
/// The number of this episode in it's season.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public int? EpisodeNumber { get; set; }
|
public int? EpisodeNumber { get; set; }
|
||||||
|
|
||||||
@ -99,12 +103,17 @@ namespace Kyoo.Models
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
[SerializeIgnore] public string Path { get; set; }
|
[SerializeIgnore] public string Path { get; set; }
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public Dictionary<int, string> Images { get; set; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The path of this episode's thumbnail.
|
/// The path of this episode's thumbnail.
|
||||||
/// By default, the http path for the thumbnail is returned from the public API.
|
/// By default, the http path for the thumbnail is returned from the public API.
|
||||||
/// This can be disabled using the internal query flag.
|
/// This can be disabled using the internal query flag.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[SerializeAs("{HOST}/api/episodes/{Slug}/thumb")] public string Thumb { get; set; }
|
[SerializeAs("{HOST}/api/episodes/{Slug}/thumbnail")]
|
||||||
|
[Obsolete("Use Images instead of this, this is only kept for the API response.")]
|
||||||
|
public string Thumb => Images?.GetValueOrDefault(Models.Images.Thumbnail);
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The title of this episode.
|
/// The title of this episode.
|
||||||
@ -121,10 +130,8 @@ namespace Kyoo.Models
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public DateTime? ReleaseDate { get; set; }
|
public DateTime? ReleaseDate { get; set; }
|
||||||
|
|
||||||
/// <summary>
|
/// <inheritdoc />
|
||||||
/// The link to metadata providers that this episode has. See <see cref="MetadataID{T}"/> for more information.
|
[EditableRelation] [LoadableRelation] public ICollection<MetadataID> ExternalIDs { get; set; }
|
||||||
/// </summary>
|
|
||||||
[EditableRelation] [LoadableRelation] public ICollection<MetadataID<Episode>> ExternalIDs { get; set; }
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The list of tracks this episode has. This lists video, audio and subtitles available.
|
/// The list of tracks this episode has. This lists video, audio and subtitles available.
|
||||||
|
@ -1,5 +1,4 @@
|
|||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using Kyoo.Common.Models.Attributes;
|
|
||||||
using Kyoo.Models.Attributes;
|
using Kyoo.Models.Attributes;
|
||||||
|
|
||||||
namespace Kyoo.Models
|
namespace Kyoo.Models
|
||||||
@ -25,13 +24,6 @@ namespace Kyoo.Models
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
[LoadableRelation] public ICollection<Show> Shows { get; set; }
|
[LoadableRelation] public ICollection<Show> Shows { get; set; }
|
||||||
|
|
||||||
#if ENABLE_INTERNAL_LINKS
|
|
||||||
/// <summary>
|
|
||||||
/// The internal link between this genre and shows in the <see cref="Shows"/> list.
|
|
||||||
/// </summary>
|
|
||||||
[Link] public ICollection<Link<Show, Genre>> ShowLinks { get; set; }
|
|
||||||
#endif
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Create a new, empty <see cref="Genre"/>.
|
/// Create a new, empty <see cref="Genre"/>.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
79
Kyoo.Common/Models/Resources/Interfaces/IMetadata.cs
Normal file
79
Kyoo.Common/Models/Resources/Interfaces/IMetadata.cs
Normal file
@ -0,0 +1,79 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using JetBrains.Annotations;
|
||||||
|
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; }
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// A static class containing extensions method for every <see cref="IMetadata"/> class.
|
||||||
|
/// This allow one to use metadata more easily.
|
||||||
|
/// </summary>
|
||||||
|
public static class MetadataExtension
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Retrieve the internal provider's ID of an item using it's provider slug.
|
||||||
|
/// </summary>
|
||||||
|
/// <remarks>
|
||||||
|
/// This method will never return anything if the <see cref="IMetadata.ExternalIDs"/> are not loaded.
|
||||||
|
/// </remarks>
|
||||||
|
/// <param name="self">An instance of <see cref="IMetadata"/> to retrieve the ID from.</param>
|
||||||
|
/// <param name="provider">The slug of the provider</param>
|
||||||
|
/// <returns>The <see cref="MetadataID.DataID"/> field of the asked provider.</returns>
|
||||||
|
[CanBeNull]
|
||||||
|
public static string GetID(this IMetadata self, string provider)
|
||||||
|
{
|
||||||
|
return self.ExternalIDs?.FirstOrDefault(x => x.Provider.Slug == provider)?.DataID;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Retrieve the internal provider's ID of an item using it's provider slug.
|
||||||
|
/// If the ID could be found, it is converted to the <typeparamref name="T"/> type and <c>true</c> is returned.
|
||||||
|
/// </summary>
|
||||||
|
/// <remarks>
|
||||||
|
/// This method will never succeed if the <see cref="IMetadata.ExternalIDs"/> are not loaded.
|
||||||
|
/// </remarks>
|
||||||
|
/// <param name="self">An instance of <see cref="IMetadata"/> to retrieve the ID from.</param>
|
||||||
|
/// <param name="provider">The slug of the provider</param>
|
||||||
|
/// <param name="id">
|
||||||
|
/// The <see cref="MetadataID.DataID"/> field of the asked provider parsed
|
||||||
|
/// and converted to the <typeparamref name="T"/> type.
|
||||||
|
/// It is only relevant if this method returns <c>true</c>.
|
||||||
|
/// </param>
|
||||||
|
/// <typeparam name="T">The type to convert the <see cref="MetadataID.DataID"/> to.</typeparam>
|
||||||
|
/// <returns><c>true</c> if this method succeeded, <c>false</c> otherwise.</returns>
|
||||||
|
public static bool TryGetID<T>(this IMetadata self, string provider, out T id)
|
||||||
|
{
|
||||||
|
string dataID = self.ExternalIDs?.FirstOrDefault(x => x.Provider.Slug == provider)?.DataID;
|
||||||
|
if (dataID == null)
|
||||||
|
{
|
||||||
|
id = default;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
id = (T)Convert.ChangeType(dataID, typeof(T));
|
||||||
|
}
|
||||||
|
catch
|
||||||
|
{
|
||||||
|
id = default;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
49
Kyoo.Common/Models/Resources/Interfaces/IThumbnails.cs
Normal file
49
Kyoo.Common/Models/Resources/Interfaces/IThumbnails.cs
Normal file
@ -0,0 +1,49 @@
|
|||||||
|
using System.Collections.Generic;
|
||||||
|
using Kyoo.Controllers;
|
||||||
|
|
||||||
|
namespace Kyoo.Models
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// An interface representing items that contains images (like posters, thumbnails, logo, banners...)
|
||||||
|
/// </summary>
|
||||||
|
public interface IThumbnails
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// The list of images mapped to a certain index.
|
||||||
|
/// The string value should be a path supported by the <see cref="IFileSystem"/>.
|
||||||
|
/// </summary>
|
||||||
|
/// <remarks>
|
||||||
|
/// An arbitrary index should not be used, instead use indexes from <see cref="Models.Images"/>
|
||||||
|
/// </remarks>
|
||||||
|
public Dictionary<int, string> Images { get; set; }
|
||||||
|
|
||||||
|
// TODO remove Posters properties add them via the json serializer for every IThumbnails
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// A class containing constant values for images. To be used as index of a <see cref="IThumbnails.Images"/>.
|
||||||
|
/// </summary>
|
||||||
|
public static class Images
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// A poster is a 9/16 format image with the cover of the resource.
|
||||||
|
/// </summary>
|
||||||
|
public const int Poster = 0;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// A thumbnail is a 16/9 format image, it could ether be used as a background or as a preview but it usually
|
||||||
|
/// is not an official image.
|
||||||
|
/// </summary>
|
||||||
|
public const int Thumbnail = 1;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// A logo is a small image representing the resource.
|
||||||
|
/// </summary>
|
||||||
|
public const int Logo = 2;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// A video of a few minutes that tease the content.
|
||||||
|
/// </summary>
|
||||||
|
public const int Trailer = 3;
|
||||||
|
}
|
||||||
|
}
|
@ -1,5 +1,4 @@
|
|||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using Kyoo.Common.Models.Attributes;
|
|
||||||
using Kyoo.Models.Attributes;
|
using Kyoo.Models.Attributes;
|
||||||
|
|
||||||
namespace Kyoo.Models
|
namespace Kyoo.Models
|
||||||
@ -39,22 +38,5 @@ namespace Kyoo.Models
|
|||||||
/// The list of collections in this library.
|
/// The list of collections in this library.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[LoadableRelation] public ICollection<Collection> Collections { get; set; }
|
[LoadableRelation] public ICollection<Collection> Collections { get; set; }
|
||||||
|
|
||||||
#if ENABLE_INTERNAL_LINKS
|
|
||||||
/// <summary>
|
|
||||||
/// The internal link between this library and provider in the <see cref="Providers"/> list.
|
|
||||||
/// </summary>
|
|
||||||
[Link] public ICollection<Link<Library, Provider>> ProviderLinks { get; set; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// The internal link between this library and shows in the <see cref="Shows"/> list.
|
|
||||||
/// </summary>
|
|
||||||
[Link] public ICollection<Link<Library, Show>> ShowLinks { get; set; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// The internal link between this library and collection in the <see cref="Collections"/> list.
|
|
||||||
/// </summary>
|
|
||||||
[Link] public ICollection<Link<Library, Collection>> CollectionLinks { get; set; }
|
|
||||||
#endif
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
using System.Collections.Generic;
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
using Kyoo.Models.Attributes;
|
using Kyoo.Models.Attributes;
|
||||||
|
|
||||||
namespace Kyoo.Models
|
namespace Kyoo.Models
|
||||||
@ -6,7 +7,7 @@ namespace Kyoo.Models
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// An actor, voice actor, writer, animator, somebody who worked on a <see cref="Show"/>.
|
/// An actor, voice actor, writer, animator, somebody who worked on a <see cref="Show"/>.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public class People : IResource
|
public class People : IResource, IMetadata, IThumbnails
|
||||||
{
|
{
|
||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
public int ID { get; set; }
|
public int ID { get; set; }
|
||||||
@ -19,17 +20,20 @@ namespace Kyoo.Models
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public string Name { get; set; }
|
public string Name { get; set; }
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public Dictionary<int, string> Images { get; set; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The path of this poster.
|
/// The path of this poster.
|
||||||
/// By default, the http path for this poster is returned from the public API.
|
/// By default, the http path for this poster is returned from the public API.
|
||||||
/// This can be disabled using the internal query flag.
|
/// This can be disabled using the internal query flag.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[SerializeAs("{HOST}/api/people/{Slug}/poster")] public string Poster { get; set; }
|
[SerializeAs("{HOST}/api/people/{Slug}/poster")]
|
||||||
|
[Obsolete("Use Images instead of this, this is only kept for the API response.")]
|
||||||
|
public string Poster => Images?.GetValueOrDefault(Models.Images.Poster);
|
||||||
|
|
||||||
/// <summary>
|
/// <inheritdoc />
|
||||||
/// The link to metadata providers that this person has. See <see cref="MetadataID{T}"/> for more information.
|
[EditableRelation] [LoadableRelation] public ICollection<MetadataID> ExternalIDs { get; set; }
|
||||||
/// </summary>
|
|
||||||
[EditableRelation] [LoadableRelation] public ICollection<MetadataID<People>> ExternalIDs { get; set; }
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The list of roles this person has played in. See <see cref="PeopleRole"/> for more information.
|
/// The list of roles this person has played in. See <see cref="PeopleRole"/> for more information.
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using Kyoo.Common.Models.Attributes;
|
|
||||||
using Kyoo.Controllers;
|
using Kyoo.Controllers;
|
||||||
using Kyoo.Models.Attributes;
|
using Kyoo.Models.Attributes;
|
||||||
|
|
||||||
@ -9,7 +9,7 @@ namespace Kyoo.Models
|
|||||||
/// This class contains metadata about <see cref="IMetadataProvider"/>.
|
/// This class contains metadata about <see cref="IMetadataProvider"/>.
|
||||||
/// You can have providers even if you don't have the corresponding <see cref="IMetadataProvider"/>.
|
/// You can have providers even if you don't have the corresponding <see cref="IMetadataProvider"/>.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public class Provider : IResource
|
public class Provider : IResource, IThumbnails
|
||||||
{
|
{
|
||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
public int ID { get; set; }
|
public int ID { get; set; }
|
||||||
@ -22,30 +22,23 @@ namespace Kyoo.Models
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public string Name { get; set; }
|
public string Name { get; set; }
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public Dictionary<int, string> Images { get; set; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The path of this provider's logo.
|
/// The path of this provider's logo.
|
||||||
/// By default, the http path for this logo is returned from the public API.
|
/// By default, the http path for this logo is returned from the public API.
|
||||||
/// This can be disabled using the internal query flag.
|
/// This can be disabled using the internal query flag.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[SerializeAs("{HOST}/api/providers/{Slug}/logo")] public string Logo { get; set; }
|
[SerializeAs("{HOST}/api/providers/{Slug}/logo")]
|
||||||
|
[Obsolete("Use Images instead of this, this is only kept for the API response.")]
|
||||||
/// <summary>
|
public string Logo => Images?.GetValueOrDefault(Models.Images.Logo);
|
||||||
/// The extension of the logo. This is used for http responses.
|
|
||||||
/// </summary>
|
|
||||||
[SerializeIgnore] public string LogoExtension { get; set; }
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The list of libraries that uses this provider.
|
/// The list of libraries that uses this provider.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[LoadableRelation] public ICollection<Library> Libraries { get; set; }
|
[LoadableRelation] public ICollection<Library> Libraries { get; set; }
|
||||||
|
|
||||||
#if ENABLE_INTERNAL_LINKS
|
|
||||||
/// <summary>
|
|
||||||
/// The internal link between this provider and libraries in the <see cref="Libraries"/> list.
|
|
||||||
/// </summary>
|
|
||||||
[Link] public ICollection<Link<Library, Provider>> LibraryLinks { get; set; }
|
|
||||||
#endif
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Create a new, default, <see cref="Provider"/>
|
/// Create a new, default, <see cref="Provider"/>
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@ -61,7 +54,10 @@ namespace Kyoo.Models
|
|||||||
{
|
{
|
||||||
Slug = Utility.ToSlug(name);
|
Slug = Utility.ToSlug(name);
|
||||||
Name = name;
|
Name = name;
|
||||||
Logo = logo;
|
Images = new Dictionary<int, string>
|
||||||
|
{
|
||||||
|
[Models.Images.Logo] = logo
|
||||||
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -10,7 +10,7 @@ namespace Kyoo.Models
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// A season of a <see cref="Show"/>.
|
/// A season of a <see cref="Show"/>.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public class Season : IResource
|
public class Season : IResource, IMetadata, IThumbnails
|
||||||
{
|
{
|
||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
public int ID { get; set; }
|
public int ID { get; set; }
|
||||||
@ -45,7 +45,8 @@ namespace Kyoo.Models
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
[SerializeIgnore] public int ShowID { get; set; }
|
[SerializeIgnore] public int ShowID { get; set; }
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The show that contains this season. This must be explicitly loaded via a call to <see cref="ILibraryManager.Load"/>.
|
/// The show that contains this season.
|
||||||
|
/// This must be explicitly loaded via a call to <see cref="ILibraryManager.Load"/>.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[LoadableRelation(nameof(ShowID))] public Show Show { get; set; }
|
[LoadableRelation(nameof(ShowID))] public Show Show { get; set; }
|
||||||
|
|
||||||
@ -74,17 +75,20 @@ namespace Kyoo.Models
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public DateTime? EndDate { get; set; }
|
public DateTime? EndDate { get; set; }
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public Dictionary<int, string> Images { get; set; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The path of this poster.
|
/// The path of this poster.
|
||||||
/// By default, the http path for this poster is returned from the public API.
|
/// By default, the http path for this poster is returned from the public API.
|
||||||
/// This can be disabled using the internal query flag.
|
/// This can be disabled using the internal query flag.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[SerializeAs("{HOST}/api/seasons/{Slug}/thumb")] public string Poster { get; set; }
|
[SerializeAs("{HOST}/api/seasons/{Slug}/thumb")]
|
||||||
|
[Obsolete("Use Images instead of this, this is only kept for the API response.")]
|
||||||
|
public string Poster => Images?.GetValueOrDefault(Models.Images.Poster);
|
||||||
|
|
||||||
/// <summary>
|
/// <inheritdoc />
|
||||||
/// The link to metadata providers that this episode has. See <see cref="MetadataID{T}"/> for more information.
|
[EditableRelation] [LoadableRelation] public ICollection<MetadataID> ExternalIDs { get; set; }
|
||||||
/// </summary>
|
|
||||||
[EditableRelation] [LoadableRelation] public ICollection<MetadataID<Season>> ExternalIDs { get; set; }
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The list of episodes that this season contains.
|
/// The list of episodes that this season contains.
|
||||||
|
@ -1,8 +1,5 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Linq;
|
|
||||||
using JetBrains.Annotations;
|
|
||||||
using Kyoo.Common.Models.Attributes;
|
|
||||||
using Kyoo.Controllers;
|
using Kyoo.Controllers;
|
||||||
using Kyoo.Models.Attributes;
|
using Kyoo.Models.Attributes;
|
||||||
|
|
||||||
@ -11,7 +8,7 @@ namespace Kyoo.Models
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// A series or a movie.
|
/// A series or a movie.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public class Show : IResource, IOnMerge
|
public class Show : IResource, IMetadata, IOnMerge, IThumbnails
|
||||||
{
|
{
|
||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
public int ID { get; set; }
|
public int ID { get; set; }
|
||||||
@ -49,7 +46,8 @@ namespace Kyoo.Models
|
|||||||
/// An URL to a trailer. This could be any path supported by the <see cref="IFileSystem"/>.
|
/// An URL to a trailer. This could be any path supported by the <see cref="IFileSystem"/>.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// TODO for now, this is set to a youtube url. It should be cached and converted to a local file.
|
/// TODO for now, this is set to a youtube url. It should be cached and converted to a local file.
|
||||||
public string TrailerUrl { get; set; }
|
[Obsolete("Use Images instead of this, this is only kept for the API response.")]
|
||||||
|
public string TrailerUrl => Images?.GetValueOrDefault(Models.Images.Trailer);
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The date this show started airing. It can be null if this is unknown.
|
/// The date this show started airing. It can be null if this is unknown.
|
||||||
@ -63,43 +61,51 @@ namespace Kyoo.Models
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public DateTime? EndAir { get; set; }
|
public DateTime? EndAir { get; set; }
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public Dictionary<int, string> Images { get; set; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The path of this show's poster.
|
/// The path of this show's poster.
|
||||||
/// By default, the http path for this poster is returned from the public API.
|
/// By default, the http path for this poster is returned from the public API.
|
||||||
/// This can be disabled using the internal query flag.
|
/// This can be disabled using the internal query flag.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[SerializeAs("{HOST}/api/shows/{Slug}/poster")] public string Poster { get; set; }
|
[SerializeAs("{HOST}/api/shows/{Slug}/poster")]
|
||||||
|
[Obsolete("Use Images instead of this, this is only kept for the API response.")]
|
||||||
|
public string Poster => Images?.GetValueOrDefault(Models.Images.Poster);
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The path of this show's logo.
|
/// The path of this show's logo.
|
||||||
/// By default, the http path for this logo is returned from the public API.
|
/// By default, the http path for this logo is returned from the public API.
|
||||||
/// This can be disabled using the internal query flag.
|
/// This can be disabled using the internal query flag.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[SerializeAs("{HOST}/api/shows/{Slug}/logo")] public string Logo { get; set; }
|
[SerializeAs("{HOST}/api/shows/{Slug}/logo")]
|
||||||
|
[Obsolete("Use Images instead of this, this is only kept for the API response.")]
|
||||||
|
public string Logo => Images?.GetValueOrDefault(Models.Images.Logo);
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The path of this show's backdrop.
|
/// The path of this show's backdrop.
|
||||||
/// By default, the http path for this backdrop is returned from the public API.
|
/// By default, the http path for this backdrop is returned from the public API.
|
||||||
/// This can be disabled using the internal query flag.
|
/// This can be disabled using the internal query flag.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[SerializeAs("{HOST}/api/shows/{Slug}/backdrop")] public string Backdrop { get; set; }
|
[SerializeAs("{HOST}/api/shows/{Slug}/backdrop")]
|
||||||
|
[Obsolete("Use Images instead of this, this is only kept for the API response.")]
|
||||||
|
public string Backdrop => Images?.GetValueOrDefault(Models.Images.Thumbnail);
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// True if this show represent a movie, false otherwise.
|
/// True if this show represent a movie, false otherwise.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public bool IsMovie { get; set; }
|
public bool IsMovie { get; set; }
|
||||||
|
|
||||||
/// <summary>
|
/// <inheritdoc />
|
||||||
/// The link to metadata providers that this show has. See <see cref="MetadataID{T}"/> for more information.
|
[EditableRelation] [LoadableRelation] public ICollection<MetadataID> ExternalIDs { get; set; }
|
||||||
/// </summary>
|
|
||||||
[EditableRelation] [LoadableRelation] public ICollection<MetadataID<Show>> ExternalIDs { get; set; }
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The ID of the Studio that made this show.
|
/// The ID of the Studio that made this show.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[SerializeIgnore] public int? StudioID { get; set; }
|
[SerializeIgnore] public int? StudioID { get; set; }
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The Studio that made this show. This must be explicitly loaded via a call to <see cref="ILibraryManager.Load"/>.
|
/// The Studio that made this show.
|
||||||
|
/// This must be explicitly loaded via a call to <see cref="ILibraryManager.Load"/>.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[LoadableRelation(nameof(StudioID))] [EditableRelation] public Studio Studio { get; set; }
|
[LoadableRelation(nameof(StudioID))] [EditableRelation] public Studio Studio { get; set; }
|
||||||
|
|
||||||
@ -135,41 +141,9 @@ namespace Kyoo.Models
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
[LoadableRelation] public ICollection<Collection> Collections { get; set; }
|
[LoadableRelation] public ICollection<Collection> Collections { get; set; }
|
||||||
|
|
||||||
#if ENABLE_INTERNAL_LINKS
|
|
||||||
/// <summary>
|
|
||||||
/// The internal link between this show and libraries in the <see cref="Libraries"/> list.
|
|
||||||
/// </summary>
|
|
||||||
[Link] public ICollection<Link<Library, Show>> LibraryLinks { get; set; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// The internal link between this show and collections in the <see cref="Collections"/> list.
|
|
||||||
/// </summary>
|
|
||||||
[Link] public ICollection<Link<Collection, Show>> CollectionLinks { get; set; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// The internal link between this show and genres in the <see cref="Genres"/> list.
|
|
||||||
/// </summary>
|
|
||||||
[Link] public ICollection<Link<Show, Genre>> GenreLinks { get; set; }
|
|
||||||
#endif
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Retrieve the internal provider's ID of a show using it's provider slug.
|
|
||||||
/// </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>
|
|
||||||
[CanBeNull]
|
|
||||||
public string GetID(string provider)
|
|
||||||
{
|
|
||||||
return ExternalIDs?.FirstOrDefault(x => x.Second.Slug == provider)?.DataID;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
public void OnMerge(object merged)
|
public void OnMerge(object merged)
|
||||||
{
|
{
|
||||||
if (ExternalIDs != null)
|
|
||||||
foreach (MetadataID<Show> id in ExternalIDs)
|
|
||||||
id.First = this;
|
|
||||||
if (People != null)
|
if (People != null)
|
||||||
foreach (PeopleRole link in People)
|
foreach (PeopleRole link in People)
|
||||||
link.Show = this;
|
link.Show = this;
|
||||||
|
@ -6,7 +6,7 @@ namespace Kyoo.Models
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// A studio that make shows.
|
/// A studio that make shows.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public class Studio : IResource
|
public class Studio : IResource, IMetadata
|
||||||
{
|
{
|
||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
public int ID { get; set; }
|
public int ID { get; set; }
|
||||||
@ -24,6 +24,9 @@ namespace Kyoo.Models
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
[LoadableRelation] public ICollection<Show> Shows { get; set; }
|
[LoadableRelation] public ICollection<Show> Shows { get; set; }
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
[EditableRelation] [LoadableRelation] public ICollection<MetadataID> ExternalIDs { get; set; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Create a new, empty, <see cref="Studio"/>.
|
/// Create a new, empty, <see cref="Studio"/>.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
@ -1,12 +1,11 @@
|
|||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using Kyoo.Common.Models.Attributes;
|
|
||||||
|
|
||||||
namespace Kyoo.Models
|
namespace Kyoo.Models
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// A single user of the app.
|
/// A single user of the app.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public class User : IResource
|
public class User : IResource, IThumbnails
|
||||||
{
|
{
|
||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
public int ID { get; set; }
|
public int ID { get; set; }
|
||||||
@ -39,6 +38,9 @@ namespace Kyoo.Models
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public Dictionary<string, string> ExtraData { get; set; }
|
public Dictionary<string, string> ExtraData { get; set; }
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public Dictionary<int, string> Images { get; set; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The list of shows the user has finished.
|
/// The list of shows the user has finished.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@ -48,20 +50,28 @@ namespace Kyoo.Models
|
|||||||
/// The list of episodes the user is watching (stopped in progress or the next episode of the show)
|
/// The list of episodes the user is watching (stopped in progress or the next episode of the show)
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public ICollection<WatchedEpisode> CurrentlyWatching { get; set; }
|
public ICollection<WatchedEpisode> CurrentlyWatching { get; set; }
|
||||||
|
|
||||||
#if ENABLE_INTERNAL_LINKS
|
|
||||||
/// <summary>
|
|
||||||
/// Links between Users and Shows.
|
|
||||||
/// </summary>
|
|
||||||
[Link] public ICollection<Link<User, Show>> ShowLinks { get; set; }
|
|
||||||
#endif
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Metadata of episode currently watching by an user
|
/// Metadata of episode currently watching by an user
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public class WatchedEpisode : Link<User, Episode>
|
public class WatchedEpisode
|
||||||
{
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// The ID of the user that started watching this episode.
|
||||||
|
/// </summary>
|
||||||
|
public int UserID { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The ID of the episode started.
|
||||||
|
/// </summary>
|
||||||
|
public int EpisodeID { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The <see cref="Episode"/> started.
|
||||||
|
/// </summary>
|
||||||
|
public Episode Episode { get; set; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Where the player has stopped watching the episode (between 0 and 100).
|
/// Where the player has stopped watching the episode (between 0 and 100).
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
@ -22,12 +22,13 @@ namespace Kyoo
|
|||||||
/// <param name="second">The second enumerable to merge, if items from this list are equals to one from the first, they are not kept</param>
|
/// <param name="second">The second enumerable to merge, if items from this list are equals to one from the first, they are not kept</param>
|
||||||
/// <param name="isEqual">Equality function to compare items. If this is null, duplicated elements are kept</param>
|
/// <param name="isEqual">Equality function to compare items. If this is null, duplicated elements are kept</param>
|
||||||
/// <returns>The two list merged as an array</returns>
|
/// <returns>The two list merged as an array</returns>
|
||||||
public static T[] MergeLists<T>(IEnumerable<T> first,
|
[ContractAnnotation("first:notnull => notnull; second:notnull => notnull", true)]
|
||||||
IEnumerable<T> second,
|
public static T[] MergeLists<T>([CanBeNull] IEnumerable<T> first,
|
||||||
Func<T, T, bool> isEqual = null)
|
[CanBeNull] IEnumerable<T> second,
|
||||||
|
[CanBeNull] Func<T, T, bool> isEqual = null)
|
||||||
{
|
{
|
||||||
if (first == null)
|
if (first == null)
|
||||||
return second.ToArray();
|
return second?.ToArray();
|
||||||
if (second == null)
|
if (second == null)
|
||||||
return first.ToArray();
|
return first.ToArray();
|
||||||
if (isEqual == null)
|
if (isEqual == null)
|
||||||
@ -36,6 +37,98 @@ namespace Kyoo
|
|||||||
return list.Concat(second.Where(x => !list.Any(y => isEqual(x, y)))).ToArray();
|
return list.Concat(second.Where(x => !list.Any(y => isEqual(x, y)))).ToArray();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Merge two dictionary, if the same key is found on both dictionary, the values of the first one is kept.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="first">The first dictionary to merge</param>
|
||||||
|
/// <param name="second">The second dictionary to merge</param>
|
||||||
|
/// <typeparam name="T">The type of the keys in dictionaries</typeparam>
|
||||||
|
/// <typeparam name="T2">The type of values in the dictionaries</typeparam>
|
||||||
|
/// <returns>The first dictionary with the missing elements of <paramref name="second"/>.</returns>
|
||||||
|
/// <seealso cref="MergeDictionaries{T,T2}(System.Collections.Generic.IDictionary{T,T2},System.Collections.Generic.IDictionary{T,T2},out bool)"/>
|
||||||
|
[ContractAnnotation("first:notnull => notnull; second:notnull => notnull", true)]
|
||||||
|
public static IDictionary<T, T2> MergeDictionaries<T, T2>([CanBeNull] IDictionary<T, T2> first,
|
||||||
|
[CanBeNull] IDictionary<T, T2> second)
|
||||||
|
{
|
||||||
|
return MergeDictionaries(first, second, out bool _);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Merge two dictionary, if the same key is found on both dictionary, the values of the first one is kept.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="first">The first dictionary to merge</param>
|
||||||
|
/// <param name="second">The second dictionary to merge</param>
|
||||||
|
/// <param name="hasChanged">
|
||||||
|
/// <c>true</c> if a new items has been added to the dictionary, <c>false</c> otherwise.
|
||||||
|
/// </param>
|
||||||
|
/// <typeparam name="T">The type of the keys in dictionaries</typeparam>
|
||||||
|
/// <typeparam name="T2">The type of values in the dictionaries</typeparam>
|
||||||
|
/// <returns>The first dictionary with the missing elements of <paramref name="second"/>.</returns>
|
||||||
|
[ContractAnnotation("first:notnull => notnull; second:notnull => notnull", true)]
|
||||||
|
public static IDictionary<T, T2> MergeDictionaries<T, T2>([CanBeNull] IDictionary<T, T2> first,
|
||||||
|
[CanBeNull] IDictionary<T, T2> second,
|
||||||
|
out bool hasChanged)
|
||||||
|
{
|
||||||
|
if (first == null)
|
||||||
|
{
|
||||||
|
hasChanged = true;
|
||||||
|
return second;
|
||||||
|
}
|
||||||
|
|
||||||
|
hasChanged = false;
|
||||||
|
if (second == null)
|
||||||
|
return first;
|
||||||
|
foreach ((T key, T2 value) in second)
|
||||||
|
hasChanged |= first.TryAdd(key, value);
|
||||||
|
return first;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Merge two dictionary, if the same key is found on both dictionary, the values of the second one is kept.
|
||||||
|
/// </summary>
|
||||||
|
/// <remarks>
|
||||||
|
/// The only difference in this function compared to
|
||||||
|
/// <see cref="MergeDictionaries{T,T2}(System.Collections.Generic.IDictionary{T,T2},System.Collections.Generic.IDictionary{T,T2}, out bool)"/>
|
||||||
|
/// is the way <paramref name="hasChanged"/> is calculated and the order of the arguments.
|
||||||
|
/// <code>
|
||||||
|
/// MergeDictionaries(first, second);
|
||||||
|
/// </code>
|
||||||
|
/// will do the same thing as
|
||||||
|
/// <code>
|
||||||
|
/// CompleteDictionaries(second, first, out bool _);
|
||||||
|
/// </code>
|
||||||
|
/// </remarks>
|
||||||
|
/// <param name="first">The first dictionary to merge</param>
|
||||||
|
/// <param name="second">The second dictionary to merge</param>
|
||||||
|
/// <param name="hasChanged">
|
||||||
|
/// <c>true</c> if a new items has been added to the dictionary, <c>false</c> otherwise.
|
||||||
|
/// </param>
|
||||||
|
/// <typeparam name="T">The type of the keys in dictionaries</typeparam>
|
||||||
|
/// <typeparam name="T2">The type of values in the dictionaries</typeparam>
|
||||||
|
/// <returns>
|
||||||
|
/// A dictionary with the missing elements of <paramref name="second"/>
|
||||||
|
/// set to those of <paramref name="first"/>.
|
||||||
|
/// </returns>
|
||||||
|
[ContractAnnotation("first:notnull => notnull; second:notnull => notnull", true)]
|
||||||
|
public static IDictionary<T, T2> CompleteDictionaries<T, T2>([CanBeNull] IDictionary<T, T2> first,
|
||||||
|
[CanBeNull] IDictionary<T, T2> second,
|
||||||
|
out bool hasChanged)
|
||||||
|
{
|
||||||
|
if (first == null)
|
||||||
|
{
|
||||||
|
hasChanged = true;
|
||||||
|
return second;
|
||||||
|
}
|
||||||
|
|
||||||
|
hasChanged = false;
|
||||||
|
if (second == null)
|
||||||
|
return first;
|
||||||
|
hasChanged = second.Any(x => !x.Value.Equals(first[x.Key]));
|
||||||
|
foreach ((T key, T2 value) in first)
|
||||||
|
second.TryAdd(key, value);
|
||||||
|
return second;
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Set every fields of first to those of second. Ignore fields marked with the <see cref="NotMergeableAttribute"/> attribute
|
/// Set every fields of first to those of second. Ignore fields marked with the <see cref="NotMergeableAttribute"/> attribute
|
||||||
/// At the end, the OnMerge method of first will be called if first is a <see cref="IOnMerge"/>
|
/// At the end, the OnMerge method of first will be called if first is a <see cref="IOnMerge"/>
|
||||||
@ -63,16 +156,34 @@ namespace Kyoo
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Set every default values of first to the value of second. ex: {id: 0, slug: "test"}, {id: 4, slug: "foo"} -> {id: 4, slug: "test"}.
|
/// Set every non-default values of seconds to the corresponding property of second.
|
||||||
|
/// Dictionaries are handled like anonymous objects with a property per key/pair value
|
||||||
|
/// (see
|
||||||
|
/// <see cref="MergeDictionaries{T,T2}(System.Collections.Generic.IDictionary{T,T2},System.Collections.Generic.IDictionary{T,T2})"/>
|
||||||
|
/// for more details).
|
||||||
/// At the end, the OnMerge method of first will be called if first is a <see cref="IOnMerge"/>
|
/// At the end, the OnMerge method of first will be called if first is a <see cref="IOnMerge"/>
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="first">The object to complete</param>
|
/// <remarks>
|
||||||
/// <param name="second">Missing fields of first will be completed by fields of this item. If second is null, the function no-op.</param>
|
/// This does the opposite of <see cref="Merge{T}"/>.
|
||||||
/// <param name="where">Filter fields that will be merged</param>
|
/// </remarks>
|
||||||
|
/// <example>
|
||||||
|
/// {id: 0, slug: "test"}, {id: 4, slug: "foo"} -> {id: 4, slug: "foo"}
|
||||||
|
/// </example>
|
||||||
|
/// <param name="first">
|
||||||
|
/// The object to complete
|
||||||
|
/// </param>
|
||||||
|
/// <param name="second">
|
||||||
|
/// Missing fields of first will be completed by fields of this item. If second is null, the function no-op.
|
||||||
|
/// </param>
|
||||||
|
/// <param name="where">
|
||||||
|
/// Filter fields that will be merged
|
||||||
|
/// </param>
|
||||||
/// <typeparam name="T">Fields of T will be completed</typeparam>
|
/// <typeparam name="T">Fields of T will be completed</typeparam>
|
||||||
/// <returns><see cref="first"/></returns>
|
/// <returns><see cref="first"/></returns>
|
||||||
/// <exception cref="ArgumentNullException">If first is null</exception>
|
/// <exception cref="ArgumentNullException">If first is null</exception>
|
||||||
public static T Complete<T>([NotNull] T first, [CanBeNull] T second, Func<PropertyInfo, bool> where = null)
|
public static T Complete<T>([NotNull] T first,
|
||||||
|
[CanBeNull] T second,
|
||||||
|
[InstantHandle] Func<PropertyInfo, bool> where = null)
|
||||||
{
|
{
|
||||||
if (first == null)
|
if (first == null)
|
||||||
throw new ArgumentNullException(nameof(first));
|
throw new ArgumentNullException(nameof(first));
|
||||||
@ -93,7 +204,26 @@ namespace Kyoo
|
|||||||
object defaultValue = property.GetCustomAttribute<DefaultValueAttribute>()?.Value
|
object defaultValue = property.GetCustomAttribute<DefaultValueAttribute>()?.Value
|
||||||
?? property.PropertyType.GetClrDefault();
|
?? property.PropertyType.GetClrDefault();
|
||||||
|
|
||||||
if (value?.Equals(defaultValue) == false && value != property.GetValue(first))
|
if (value?.Equals(defaultValue) != false || value.Equals(property.GetValue(first)))
|
||||||
|
continue;
|
||||||
|
if (Utility.IsOfGenericType(property.PropertyType, typeof(IDictionary<,>)))
|
||||||
|
{
|
||||||
|
Type[] dictionaryTypes = Utility.GetGenericDefinition(property.PropertyType, typeof(IDictionary<,>))
|
||||||
|
.GenericTypeArguments;
|
||||||
|
object[] parameters = {
|
||||||
|
property.GetValue(first),
|
||||||
|
value,
|
||||||
|
false
|
||||||
|
};
|
||||||
|
object newDictionary = Utility.RunGenericMethod<object>(
|
||||||
|
typeof(Merger),
|
||||||
|
nameof(CompleteDictionaries),
|
||||||
|
dictionaryTypes,
|
||||||
|
parameters);
|
||||||
|
if ((bool)parameters[2])
|
||||||
|
property.SetValue(first, newDictionary);
|
||||||
|
}
|
||||||
|
else
|
||||||
property.SetValue(first, value);
|
property.SetValue(first, value);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -103,17 +233,28 @@ namespace Kyoo
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// An advanced <see cref="Complete{T}"/> function.
|
|
||||||
/// This will set missing values of <see cref="first"/> to the corresponding values of <see cref="second"/>.
|
/// This will set missing values of <see cref="first"/> to the corresponding values of <see cref="second"/>.
|
||||||
/// Enumerable will be merged (concatenated).
|
/// Enumerable will be merged (concatenated) and Dictionaries too.
|
||||||
/// At the end, the OnMerge method of first will be called if first is a <see cref="IOnMerge"/>.
|
/// At the end, the OnMerge method of first will be called if first is a <see cref="IOnMerge"/>.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="first">The object to complete</param>
|
/// <example>
|
||||||
/// <param name="second">Missing fields of first will be completed by fields of this item. If second is null, the function no-op.</param>
|
/// {id: 0, slug: "test"}, {id: 4, slug: "foo"} -> {id: 4, slug: "test"}
|
||||||
|
/// </example>
|
||||||
|
/// <param name="first">
|
||||||
|
/// The object to complete
|
||||||
|
/// </param>
|
||||||
|
/// <param name="second">
|
||||||
|
/// Missing fields of first will be completed by fields of this item. If second is null, the function no-op.
|
||||||
|
/// </param>
|
||||||
|
/// <param name="where">
|
||||||
|
/// Filter fields that will be merged
|
||||||
|
/// </param>
|
||||||
/// <typeparam name="T">Fields of T will be merged</typeparam>
|
/// <typeparam name="T">Fields of T will be merged</typeparam>
|
||||||
/// <returns><see cref="first"/></returns>
|
/// <returns><see cref="first"/></returns>
|
||||||
[ContractAnnotation("first:notnull => notnull; second:notnull => notnull", true)]
|
[ContractAnnotation("first:notnull => notnull; second:notnull => notnull", true)]
|
||||||
public static T Merge<T>([CanBeNull] T first, [CanBeNull] T second)
|
public static T Merge<T>([CanBeNull] T first,
|
||||||
|
[CanBeNull] T second,
|
||||||
|
[InstantHandle] Func<PropertyInfo, bool> where = null)
|
||||||
{
|
{
|
||||||
if (first == null)
|
if (first == null)
|
||||||
return second;
|
return second;
|
||||||
@ -125,6 +266,9 @@ namespace Kyoo
|
|||||||
.Where(x => x.CanRead && x.CanWrite
|
.Where(x => x.CanRead && x.CanWrite
|
||||||
&& Attribute.GetCustomAttribute(x, typeof(NotMergeableAttribute)) == null);
|
&& Attribute.GetCustomAttribute(x, typeof(NotMergeableAttribute)) == null);
|
||||||
|
|
||||||
|
if (where != null)
|
||||||
|
properties = properties.Where(where);
|
||||||
|
|
||||||
foreach (PropertyInfo property in properties)
|
foreach (PropertyInfo property in properties)
|
||||||
{
|
{
|
||||||
object oldValue = property.GetValue(first);
|
object oldValue = property.GetValue(first);
|
||||||
@ -133,6 +277,23 @@ namespace Kyoo
|
|||||||
|
|
||||||
if (oldValue?.Equals(defaultValue) != false)
|
if (oldValue?.Equals(defaultValue) != false)
|
||||||
property.SetValue(first, newValue);
|
property.SetValue(first, newValue);
|
||||||
|
else if (Utility.IsOfGenericType(property.PropertyType, typeof(IDictionary<,>)))
|
||||||
|
{
|
||||||
|
Type[] dictionaryTypes = Utility.GetGenericDefinition(property.PropertyType, typeof(IDictionary<,>))
|
||||||
|
.GenericTypeArguments;
|
||||||
|
object[] parameters = {
|
||||||
|
oldValue,
|
||||||
|
newValue,
|
||||||
|
false
|
||||||
|
};
|
||||||
|
object newDictionary = Utility.RunGenericMethod<object>(
|
||||||
|
typeof(Merger),
|
||||||
|
nameof(MergeDictionaries),
|
||||||
|
dictionaryTypes,
|
||||||
|
parameters);
|
||||||
|
if ((bool)parameters[2])
|
||||||
|
property.SetValue(first, newDictionary);
|
||||||
|
}
|
||||||
else if (typeof(IEnumerable).IsAssignableFrom(property.PropertyType)
|
else if (typeof(IEnumerable).IsAssignableFrom(property.PropertyType)
|
||||||
&& property.PropertyType != typeof(string))
|
&& property.PropertyType != typeof(string))
|
||||||
{
|
{
|
||||||
|
@ -325,7 +325,7 @@ namespace Kyoo
|
|||||||
if (types.Length < 1)
|
if (types.Length < 1)
|
||||||
throw new ArgumentException($"The {nameof(types)} array is empty. At least one type is needed.");
|
throw new ArgumentException($"The {nameof(types)} array is empty. At least one type is needed.");
|
||||||
MethodInfo method = GetMethod(owner, BindingFlags.Static, methodName, types, args);
|
MethodInfo method = GetMethod(owner, BindingFlags.Static, methodName, types, args);
|
||||||
return (T)method.MakeGenericMethod(types).Invoke(null, args.ToArray());
|
return (T)method.MakeGenericMethod(types).Invoke(null, args);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
@ -1,8 +1,10 @@
|
|||||||
using System;
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Linq.Expressions;
|
using System.Linq.Expressions;
|
||||||
using System.Threading;
|
using System.Threading;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
|
using JetBrains.Annotations;
|
||||||
using Kyoo.Controllers;
|
using Kyoo.Controllers;
|
||||||
using Kyoo.Models;
|
using Kyoo.Models;
|
||||||
using Kyoo.Models.Exceptions;
|
using Kyoo.Models.Exceptions;
|
||||||
@ -60,6 +62,7 @@ namespace Kyoo
|
|||||||
/// All providers of Kyoo. See <see cref="Provider"/>.
|
/// All providers of Kyoo. See <see cref="Provider"/>.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public DbSet<Provider> Providers { get; set; }
|
public DbSet<Provider> Providers { get; set; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The list of registered users.
|
/// The list of registered users.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@ -84,28 +87,34 @@ namespace Kyoo
|
|||||||
public DbSet<LibraryItem> LibraryItems { get; set; }
|
public DbSet<LibraryItem> LibraryItems { get; set; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Get all metadataIDs (ExternalIDs) of a given resource. See <see cref="MetadataID{T}"/>.
|
/// Get all metadataIDs (ExternalIDs) of a given resource. See <see cref="MetadataID"/>.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <typeparam name="T">The metadata of this type will be returned.</typeparam>
|
/// <typeparam name="T">The metadata of this type will be returned.</typeparam>
|
||||||
/// <returns>A queryable of metadata ids for a type.</returns>
|
/// <returns>A queryable of metadata ids for a type.</returns>
|
||||||
public DbSet<MetadataID<T>> MetadataIds<T>()
|
public DbSet<MetadataID> MetadataIds<T>()
|
||||||
where T : class, IResource
|
where T : class, IMetadata
|
||||||
{
|
{
|
||||||
return Set<MetadataID<T>>();
|
return Set<MetadataID>(MetadataName<T>());
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Get a generic link between two resource types.
|
/// Add a many to many link between two resources.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <remarks>Types are order dependant. You can't inverse the order. Please always put the owner first.</remarks>
|
/// <remarks>Types are order dependant. You can't inverse the order. Please always put the owner first.</remarks>
|
||||||
|
/// <param name="first">The ID of the first resource.</param>
|
||||||
|
/// <param name="second">The ID of the second resource.</param>
|
||||||
/// <typeparam name="T1">The first resource type of the relation. It is the owner of the second</typeparam>
|
/// <typeparam name="T1">The first resource type of the relation. It is the owner of the second</typeparam>
|
||||||
/// <typeparam name="T2">The second resource type of the relation. It is the contained resource.</typeparam>
|
/// <typeparam name="T2">The second resource type of the relation. It is the contained resource.</typeparam>
|
||||||
/// <returns>All links between the two types.</returns>
|
public async Task AddLinks<T1, T2>(int first, int second)
|
||||||
public DbSet<Link<T1, T2>> Links<T1, T2>()
|
|
||||||
where T1 : class, IResource
|
where T1 : class, IResource
|
||||||
where T2 : class, IResource
|
where T2 : class, IResource
|
||||||
{
|
{
|
||||||
return Set<Link<T1, T2>>();
|
await Set<Dictionary<string, object>>(LinkName<T1, T2>())
|
||||||
|
.AddAsync(new Dictionary<string, object>
|
||||||
|
{
|
||||||
|
[LinkNameFk<T1>()] = first,
|
||||||
|
[LinkNameFk<T2>()] = second
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@ -122,6 +131,32 @@ namespace Kyoo
|
|||||||
: base(options)
|
: base(options)
|
||||||
{ }
|
{ }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Get the name of the metadata table of the given type.
|
||||||
|
/// </summary>
|
||||||
|
/// <typeparam name="T">The type related to the metadata</typeparam>
|
||||||
|
/// <returns>The name of the table containing the metadata.</returns>
|
||||||
|
protected abstract string MetadataName<T>()
|
||||||
|
where T : IMetadata;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Get the name of the link table of the two given types.
|
||||||
|
/// </summary>
|
||||||
|
/// <typeparam name="T">The owner type of the relation</typeparam>
|
||||||
|
/// <typeparam name="T2">The child type of the relation</typeparam>
|
||||||
|
/// <returns>The name of the table containing the links.</returns>
|
||||||
|
protected abstract string LinkName<T, T2>()
|
||||||
|
where T : IResource
|
||||||
|
where T2 : IResource;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Get the name of a link's foreign key.
|
||||||
|
/// </summary>
|
||||||
|
/// <typeparam name="T">The type that will be accessible via the navigation</typeparam>
|
||||||
|
/// <returns>The name of the foreign key for the given resource.</returns>
|
||||||
|
protected abstract string LinkNameFk<T>()
|
||||||
|
where T : IResource;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Set basic configurations (like preventing query tracking)
|
/// Set basic configurations (like preventing query tracking)
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@ -132,6 +167,58 @@ namespace Kyoo
|
|||||||
optionsBuilder.UseQueryTrackingBehavior(QueryTrackingBehavior.NoTracking);
|
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.SharedTypeEntity<MetadataID>(MetadataName<T>())
|
||||||
|
.HasKey(MetadataID.PrimaryKey);
|
||||||
|
|
||||||
|
modelBuilder.SharedTypeEntity<MetadataID>(MetadataName<T>())
|
||||||
|
.HasOne<T>()
|
||||||
|
.WithMany(x => x.ExternalIDs)
|
||||||
|
.HasForeignKey(x => x.ResourceID)
|
||||||
|
.OnDelete(DeleteBehavior.Cascade);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Create a many to many relationship between the two entities.
|
||||||
|
/// The resulting relationship will have an available <see cref="AddLinks{T1,T2}"/> method.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="modelBuilder">The database model builder</param>
|
||||||
|
/// <param name="firstNavigation">The first navigation expression from T to T2</param>
|
||||||
|
/// <param name="secondNavigation">The second navigation expression from T2 to T</param>
|
||||||
|
/// <typeparam name="T">The owning type of the relationship</typeparam>
|
||||||
|
/// <typeparam name="T2">The owned type of the relationship</typeparam>
|
||||||
|
private void _HasManyToMany<T, T2>(ModelBuilder modelBuilder,
|
||||||
|
Expression<Func<T, IEnumerable<T2>>> firstNavigation,
|
||||||
|
Expression<Func<T2, IEnumerable<T>>> secondNavigation)
|
||||||
|
where T : class, IResource
|
||||||
|
where T2 : class, IResource
|
||||||
|
{
|
||||||
|
modelBuilder.Entity<T2>()
|
||||||
|
.HasMany(secondNavigation)
|
||||||
|
.WithMany(firstNavigation)
|
||||||
|
.UsingEntity<Dictionary<string, object>>(
|
||||||
|
LinkName<T, T2>(),
|
||||||
|
x => x
|
||||||
|
.HasOne<T>()
|
||||||
|
.WithMany()
|
||||||
|
.HasForeignKey(LinkNameFk<T>())
|
||||||
|
.OnDelete(DeleteBehavior.Cascade),
|
||||||
|
x => x
|
||||||
|
.HasOne<T2>()
|
||||||
|
.WithMany()
|
||||||
|
.HasForeignKey(LinkNameFk<T2>())
|
||||||
|
.OnDelete(DeleteBehavior.Cascade)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Set database parameters to support every types of Kyoo.
|
/// Set database parameters to support every types of Kyoo.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@ -140,6 +227,9 @@ namespace Kyoo
|
|||||||
{
|
{
|
||||||
base.OnModelCreating(modelBuilder);
|
base.OnModelCreating(modelBuilder);
|
||||||
|
|
||||||
|
modelBuilder.Entity<PeopleRole>()
|
||||||
|
.Ignore(x => x.ForPeople);
|
||||||
|
|
||||||
modelBuilder.Entity<Show>()
|
modelBuilder.Entity<Show>()
|
||||||
.HasMany(x => x.Seasons)
|
.HasMany(x => x.Seasons)
|
||||||
.WithOne(x => x.Show)
|
.WithOne(x => x.Show)
|
||||||
@ -162,117 +252,26 @@ namespace Kyoo
|
|||||||
.WithMany(x => x.Shows)
|
.WithMany(x => x.Shows)
|
||||||
.OnDelete(DeleteBehavior.SetNull);
|
.OnDelete(DeleteBehavior.SetNull);
|
||||||
|
|
||||||
modelBuilder.Entity<Provider>()
|
_HasManyToMany<Library, Provider>(modelBuilder, x => x.Providers, x => x.Libraries);
|
||||||
.HasMany(x => x.Libraries)
|
_HasManyToMany<Library, Collection>(modelBuilder, x => x.Collections, x => x.Libraries);
|
||||||
.WithMany(x => x.Providers)
|
_HasManyToMany<Library, Show>(modelBuilder, x => x.Shows, x => x.Libraries);
|
||||||
.UsingEntity<Link<Library, Provider>>(
|
_HasManyToMany<Collection, Show>(modelBuilder, x => x.Shows, x => x.Collections);
|
||||||
y => y
|
_HasManyToMany<Show, Genre>(modelBuilder, x => x.Genres, x => x.Shows);
|
||||||
.HasOne(x => x.First)
|
|
||||||
.WithMany(x => x.ProviderLinks),
|
|
||||||
y => y
|
|
||||||
.HasOne(x => x.Second)
|
|
||||||
.WithMany(x => x.LibraryLinks),
|
|
||||||
y => y.HasKey(Link<Library, Provider>.PrimaryKey));
|
|
||||||
|
|
||||||
modelBuilder.Entity<Collection>()
|
|
||||||
.HasMany(x => x.Libraries)
|
|
||||||
.WithMany(x => x.Collections)
|
|
||||||
.UsingEntity<Link<Library, Collection>>(
|
|
||||||
y => y
|
|
||||||
.HasOne(x => x.First)
|
|
||||||
.WithMany(x => x.CollectionLinks),
|
|
||||||
y => y
|
|
||||||
.HasOne(x => x.Second)
|
|
||||||
.WithMany(x => x.LibraryLinks),
|
|
||||||
y => y.HasKey(Link<Library, Collection>.PrimaryKey));
|
|
||||||
|
|
||||||
modelBuilder.Entity<Show>()
|
|
||||||
.HasMany(x => x.Libraries)
|
|
||||||
.WithMany(x => x.Shows)
|
|
||||||
.UsingEntity<Link<Library, Show>>(
|
|
||||||
y => y
|
|
||||||
.HasOne(x => x.First)
|
|
||||||
.WithMany(x => x.ShowLinks),
|
|
||||||
y => y
|
|
||||||
.HasOne(x => x.Second)
|
|
||||||
.WithMany(x => x.LibraryLinks),
|
|
||||||
y => y.HasKey(Link<Library, Show>.PrimaryKey));
|
|
||||||
|
|
||||||
modelBuilder.Entity<Show>()
|
|
||||||
.HasMany(x => x.Collections)
|
|
||||||
.WithMany(x => x.Shows)
|
|
||||||
.UsingEntity<Link<Collection, Show>>(
|
|
||||||
y => y
|
|
||||||
.HasOne(x => x.First)
|
|
||||||
.WithMany(x => x.ShowLinks),
|
|
||||||
y => y
|
|
||||||
.HasOne(x => x.Second)
|
|
||||||
.WithMany(x => x.CollectionLinks),
|
|
||||||
y => y.HasKey(Link<Collection, Show>.PrimaryKey));
|
|
||||||
|
|
||||||
modelBuilder.Entity<Genre>()
|
|
||||||
.HasMany(x => x.Shows)
|
|
||||||
.WithMany(x => x.Genres)
|
|
||||||
.UsingEntity<Link<Show, Genre>>(
|
|
||||||
y => y
|
|
||||||
.HasOne(x => x.First)
|
|
||||||
.WithMany(x => x.GenreLinks),
|
|
||||||
y => y
|
|
||||||
.HasOne(x => x.Second)
|
|
||||||
.WithMany(x => x.ShowLinks),
|
|
||||||
y => y.HasKey(Link<Show, Genre>.PrimaryKey));
|
|
||||||
|
|
||||||
modelBuilder.Entity<User>()
|
modelBuilder.Entity<User>()
|
||||||
.HasMany(x => x.Watched)
|
.HasMany(x => x.Watched)
|
||||||
.WithMany("users")
|
.WithMany("Users")
|
||||||
.UsingEntity<Link<User, Show>>(
|
.UsingEntity(x => x.ToTable(LinkName<User, Show>()));
|
||||||
y => y
|
|
||||||
.HasOne(x => x.Second)
|
|
||||||
.WithMany(),
|
|
||||||
y => y
|
|
||||||
.HasOne(x => x.First)
|
|
||||||
.WithMany(x => x.ShowLinks),
|
|
||||||
y => y.HasKey(Link<User, Show>.PrimaryKey));
|
|
||||||
|
|
||||||
modelBuilder.Entity<MetadataID<Show>>()
|
_HasMetadata<Collection>(modelBuilder);
|
||||||
.HasKey(MetadataID<Show>.PrimaryKey);
|
_HasMetadata<Show>(modelBuilder);
|
||||||
modelBuilder.Entity<MetadataID<Show>>()
|
_HasMetadata<Season>(modelBuilder);
|
||||||
.HasOne(x => x.First)
|
_HasMetadata<Episode>(modelBuilder);
|
||||||
.WithMany(x => x.ExternalIDs)
|
_HasMetadata<People>(modelBuilder);
|
||||||
.OnDelete(DeleteBehavior.Cascade);
|
_HasMetadata<Studio>(modelBuilder);
|
||||||
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<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);
|
|
||||||
|
|
||||||
modelBuilder.Entity<WatchedEpisode>()
|
modelBuilder.Entity<WatchedEpisode>()
|
||||||
.HasKey(x => new {First = x.FirstID, Second = x.SecondID});
|
.HasKey(x => new { User = x.UserID, Episode = x.EpisodeID });
|
||||||
|
|
||||||
modelBuilder.Entity<Collection>().Property(x => x.Slug).IsRequired();
|
modelBuilder.Entity<Collection>().Property(x => x.Slug).IsRequired();
|
||||||
modelBuilder.Entity<Genre>().Property(x => x.Slug).IsRequired();
|
modelBuilder.Entity<Genre>().Property(x => x.Slug).IsRequired();
|
||||||
@ -505,6 +504,23 @@ namespace Kyoo
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Return the first resource with the given slug that is currently tracked by this context.
|
||||||
|
/// This allow one to limit redundant calls to <see cref="IRepository{T}.CreateIfNotExists"/> during the
|
||||||
|
/// same transaction and prevent fails from EF when two same entities are being tracked.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="slug">The slug of the resource to check</param>
|
||||||
|
/// <typeparam name="T">The type of entity to check</typeparam>
|
||||||
|
/// <returns>The local entity representing the resource with the given slug if it exists or null.</returns>
|
||||||
|
[CanBeNull]
|
||||||
|
public T LocalEntity<T>(string slug)
|
||||||
|
where T : class, IResource
|
||||||
|
{
|
||||||
|
return ChangeTracker.Entries<T>()
|
||||||
|
.FirstOrDefault(x => x.Entity.Slug == slug)
|
||||||
|
?.Entity;
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Check if the exception is a duplicated exception.
|
/// Check if the exception is a duplicated exception.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@ -517,14 +533,12 @@ namespace Kyoo
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
private void DiscardChanges()
|
private void DiscardChanges()
|
||||||
{
|
{
|
||||||
foreach (EntityEntry entry in ChangeTracker.Entries().Where(x => x.State != EntityState.Unchanged
|
foreach (EntityEntry entry in ChangeTracker.Entries().Where(x => x.State != EntityState.Detached))
|
||||||
&& x.State != EntityState.Detached))
|
|
||||||
{
|
{
|
||||||
entry.State = EntityState.Detached;
|
entry.State = EntityState.Detached;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Perform a case insensitive like operation.
|
/// Perform a case insensitive like operation.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
@ -234,16 +234,23 @@ namespace Kyoo.Controllers
|
|||||||
finally
|
finally
|
||||||
{
|
{
|
||||||
Database.ChangeTracker.LazyLoadingEnabled = lazyLoading;
|
Database.ChangeTracker.LazyLoadingEnabled = lazyLoading;
|
||||||
|
Database.ChangeTracker.Clear();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// An overridable method to edit relation of a resource.
|
/// An overridable method to edit relation of a resource.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="resource">The non edited resource</param>
|
/// <param name="resource">
|
||||||
/// <param name="changed">The new version of <see cref="resource"/>. This item will be saved on the databse and replace <see cref="resource"/></param>
|
/// The non edited resource
|
||||||
/// <param name="resetOld">A boolean to indicate if all values of resource should be discarded or not.</param>
|
/// </param>
|
||||||
/// <returns></returns>
|
/// <param name="changed">
|
||||||
|
/// The new version of <see cref="resource"/>.
|
||||||
|
/// This item will be saved on the database and replace <see cref="resource"/>
|
||||||
|
/// </param>
|
||||||
|
/// <param name="resetOld">
|
||||||
|
/// A boolean to indicate if all values of resource should be discarded or not.
|
||||||
|
/// </param>
|
||||||
protected virtual Task EditRelations(T resource, T changed, bool resetOld)
|
protected virtual Task EditRelations(T resource, T changed, bool resetOld)
|
||||||
{
|
{
|
||||||
return Validate(resource);
|
return Validate(resource);
|
||||||
@ -254,7 +261,9 @@ namespace Kyoo.Controllers
|
|||||||
/// It is also called on the default implementation of <see cref="EditRelations"/>
|
/// It is also called on the default implementation of <see cref="EditRelations"/>
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="resource">The resource that will be saved</param>
|
/// <param name="resource">The resource that will be saved</param>
|
||||||
/// <exception cref="ArgumentException">You can throw this if the resource is illegal and should not be saved.</exception>
|
/// <exception cref="ArgumentException">
|
||||||
|
/// You can throw this if the resource is illegal and should not be saved.
|
||||||
|
/// </exception>
|
||||||
protected virtual Task Validate(T resource)
|
protected virtual Task Validate(T resource)
|
||||||
{
|
{
|
||||||
if (typeof(T).GetProperty(nameof(resource.Slug))!.GetCustomAttribute<ComputedAttribute>() != null)
|
if (typeof(T).GetProperty(nameof(resource.Slug))!.GetCustomAttribute<ComputedAttribute>() != null)
|
||||||
|
File diff suppressed because it is too large
Load Diff
@ -23,7 +23,7 @@ namespace Kyoo.Postgresql.Migrations
|
|||||||
.Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn),
|
.Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn),
|
||||||
slug = table.Column<string>(type: "text", nullable: false),
|
slug = table.Column<string>(type: "text", nullable: false),
|
||||||
name = table.Column<string>(type: "text", nullable: true),
|
name = table.Column<string>(type: "text", nullable: true),
|
||||||
poster = table.Column<string>(type: "text", nullable: true),
|
images = table.Column<Dictionary<int, string>>(type: "jsonb", nullable: true),
|
||||||
overview = table.Column<string>(type: "text", nullable: true)
|
overview = table.Column<string>(type: "text", nullable: true)
|
||||||
},
|
},
|
||||||
constraints: table =>
|
constraints: table =>
|
||||||
@ -68,7 +68,7 @@ namespace Kyoo.Postgresql.Migrations
|
|||||||
.Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn),
|
.Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn),
|
||||||
slug = table.Column<string>(type: "text", nullable: false),
|
slug = table.Column<string>(type: "text", nullable: false),
|
||||||
name = table.Column<string>(type: "text", nullable: true),
|
name = table.Column<string>(type: "text", nullable: true),
|
||||||
poster = table.Column<string>(type: "text", nullable: true)
|
images = table.Column<Dictionary<int, string>>(type: "jsonb", nullable: true)
|
||||||
},
|
},
|
||||||
constraints: table =>
|
constraints: table =>
|
||||||
{
|
{
|
||||||
@ -83,8 +83,7 @@ namespace Kyoo.Postgresql.Migrations
|
|||||||
.Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn),
|
.Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn),
|
||||||
slug = table.Column<string>(type: "text", nullable: false),
|
slug = table.Column<string>(type: "text", nullable: false),
|
||||||
name = table.Column<string>(type: "text", nullable: true),
|
name = table.Column<string>(type: "text", nullable: true),
|
||||||
logo = table.Column<string>(type: "text", nullable: true),
|
images = table.Column<Dictionary<int, string>>(type: "jsonb", nullable: true)
|
||||||
logo_extension = table.Column<string>(type: "text", nullable: true)
|
|
||||||
},
|
},
|
||||||
constraints: table =>
|
constraints: table =>
|
||||||
{
|
{
|
||||||
@ -116,7 +115,8 @@ namespace Kyoo.Postgresql.Migrations
|
|||||||
email = table.Column<string>(type: "text", nullable: true),
|
email = table.Column<string>(type: "text", nullable: true),
|
||||||
password = table.Column<string>(type: "text", nullable: true),
|
password = table.Column<string>(type: "text", nullable: true),
|
||||||
permissions = table.Column<string[]>(type: "text[]", nullable: true),
|
permissions = table.Column<string[]>(type: "text[]", nullable: true),
|
||||||
extra_data = table.Column<Dictionary<string, string>>(type: "jsonb", nullable: true)
|
extra_data = table.Column<Dictionary<string, string>>(type: "jsonb", nullable: true),
|
||||||
|
images = table.Column<Dictionary<int, string>>(type: "jsonb", nullable: true)
|
||||||
},
|
},
|
||||||
constraints: table =>
|
constraints: table =>
|
||||||
{
|
{
|
||||||
@ -127,71 +127,97 @@ namespace Kyoo.Postgresql.Migrations
|
|||||||
name: "link_library_collection",
|
name: "link_library_collection",
|
||||||
columns: table => new
|
columns: table => new
|
||||||
{
|
{
|
||||||
first_id = table.Column<int>(type: "integer", nullable: false),
|
collection_id = table.Column<int>(type: "integer", nullable: false),
|
||||||
second_id = table.Column<int>(type: "integer", nullable: false)
|
library_id = table.Column<int>(type: "integer", nullable: false)
|
||||||
},
|
},
|
||||||
constraints: table =>
|
constraints: table =>
|
||||||
{
|
{
|
||||||
table.PrimaryKey("pk_link_library_collection", x => new { x.first_id, x.second_id });
|
table.PrimaryKey("pk_link_library_collection", x => new { x.collection_id, x.library_id });
|
||||||
table.ForeignKey(
|
table.ForeignKey(
|
||||||
name: "fk_link_library_collection_collections_second_id",
|
name: "fk_link_library_collection_collections_collection_id",
|
||||||
column: x => x.second_id,
|
column: x => x.collection_id,
|
||||||
principalTable: "collections",
|
principalTable: "collections",
|
||||||
principalColumn: "id",
|
principalColumn: "id",
|
||||||
onDelete: ReferentialAction.Cascade);
|
onDelete: ReferentialAction.Cascade);
|
||||||
table.ForeignKey(
|
table.ForeignKey(
|
||||||
name: "fk_link_library_collection_libraries_first_id",
|
name: "fk_link_library_collection_libraries_library_id",
|
||||||
column: x => x.first_id,
|
column: x => x.library_id,
|
||||||
principalTable: "libraries",
|
principalTable: "libraries",
|
||||||
principalColumn: "id",
|
principalColumn: "id",
|
||||||
onDelete: ReferentialAction.Cascade);
|
onDelete: ReferentialAction.Cascade);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
migrationBuilder.CreateTable(
|
||||||
|
name: "collection_metadata_id",
|
||||||
|
columns: table => new
|
||||||
|
{
|
||||||
|
resource_id = table.Column<int>(type: "integer", nullable: false),
|
||||||
|
provider_id = table.Column<int>(type: "integer", nullable: false),
|
||||||
|
data_id = table.Column<string>(type: "text", nullable: true),
|
||||||
|
link = table.Column<string>(type: "text", nullable: true)
|
||||||
|
},
|
||||||
|
constraints: table =>
|
||||||
|
{
|
||||||
|
table.PrimaryKey("pk_collection_metadata_id", x => new { x.resource_id, x.provider_id });
|
||||||
|
table.ForeignKey(
|
||||||
|
name: "fk_collection_metadata_id_collections_collection_id",
|
||||||
|
column: x => x.resource_id,
|
||||||
|
principalTable: "collections",
|
||||||
|
principalColumn: "id",
|
||||||
|
onDelete: ReferentialAction.Cascade);
|
||||||
|
table.ForeignKey(
|
||||||
|
name: "fk_collection_metadata_id_providers_provider_id",
|
||||||
|
column: x => x.provider_id,
|
||||||
|
principalTable: "providers",
|
||||||
|
principalColumn: "id",
|
||||||
|
onDelete: ReferentialAction.Cascade);
|
||||||
|
});
|
||||||
|
|
||||||
migrationBuilder.CreateTable(
|
migrationBuilder.CreateTable(
|
||||||
name: "link_library_provider",
|
name: "link_library_provider",
|
||||||
columns: table => new
|
columns: table => new
|
||||||
{
|
{
|
||||||
first_id = table.Column<int>(type: "integer", nullable: false),
|
library_id = table.Column<int>(type: "integer", nullable: false),
|
||||||
second_id = table.Column<int>(type: "integer", nullable: false)
|
provider_id = table.Column<int>(type: "integer", nullable: false)
|
||||||
},
|
},
|
||||||
constraints: table =>
|
constraints: table =>
|
||||||
{
|
{
|
||||||
table.PrimaryKey("pk_link_library_provider", x => new { x.first_id, x.second_id });
|
table.PrimaryKey("pk_link_library_provider", x => new { x.library_id, x.provider_id });
|
||||||
table.ForeignKey(
|
table.ForeignKey(
|
||||||
name: "fk_link_library_provider_libraries_first_id",
|
name: "fk_link_library_provider_libraries_library_id",
|
||||||
column: x => x.first_id,
|
column: x => x.library_id,
|
||||||
principalTable: "libraries",
|
principalTable: "libraries",
|
||||||
principalColumn: "id",
|
principalColumn: "id",
|
||||||
onDelete: ReferentialAction.Cascade);
|
onDelete: ReferentialAction.Cascade);
|
||||||
table.ForeignKey(
|
table.ForeignKey(
|
||||||
name: "fk_link_library_provider_providers_second_id",
|
name: "fk_link_library_provider_providers_provider_id",
|
||||||
column: x => x.second_id,
|
column: x => x.provider_id,
|
||||||
principalTable: "providers",
|
principalTable: "providers",
|
||||||
principalColumn: "id",
|
principalColumn: "id",
|
||||||
onDelete: ReferentialAction.Cascade);
|
onDelete: ReferentialAction.Cascade);
|
||||||
});
|
});
|
||||||
|
|
||||||
migrationBuilder.CreateTable(
|
migrationBuilder.CreateTable(
|
||||||
name: "metadata_id_people",
|
name: "people_metadata_id",
|
||||||
columns: table => new
|
columns: table => new
|
||||||
{
|
{
|
||||||
first_id = table.Column<int>(type: "integer", nullable: false),
|
resource_id = table.Column<int>(type: "integer", nullable: false),
|
||||||
second_id = table.Column<int>(type: "integer", nullable: false),
|
provider_id = table.Column<int>(type: "integer", nullable: false),
|
||||||
data_id = table.Column<string>(type: "text", nullable: true),
|
data_id = table.Column<string>(type: "text", nullable: true),
|
||||||
link = table.Column<string>(type: "text", nullable: true)
|
link = table.Column<string>(type: "text", nullable: true)
|
||||||
},
|
},
|
||||||
constraints: table =>
|
constraints: table =>
|
||||||
{
|
{
|
||||||
table.PrimaryKey("pk_metadata_id_people", x => new { x.first_id, x.second_id });
|
table.PrimaryKey("pk_people_metadata_id", x => new { x.resource_id, x.provider_id });
|
||||||
table.ForeignKey(
|
table.ForeignKey(
|
||||||
name: "fk_metadata_id_people_people_first_id",
|
name: "fk_people_metadata_id_people_people_id",
|
||||||
column: x => x.first_id,
|
column: x => x.resource_id,
|
||||||
principalTable: "people",
|
principalTable: "people",
|
||||||
principalColumn: "id",
|
principalColumn: "id",
|
||||||
onDelete: ReferentialAction.Cascade);
|
onDelete: ReferentialAction.Cascade);
|
||||||
table.ForeignKey(
|
table.ForeignKey(
|
||||||
name: "fk_metadata_id_people_providers_second_id",
|
name: "fk_people_metadata_id_providers_provider_id",
|
||||||
column: x => x.second_id,
|
column: x => x.provider_id,
|
||||||
principalTable: "providers",
|
principalTable: "providers",
|
||||||
principalColumn: "id",
|
principalColumn: "id",
|
||||||
onDelete: ReferentialAction.Cascade);
|
onDelete: ReferentialAction.Cascade);
|
||||||
@ -209,12 +235,9 @@ namespace Kyoo.Postgresql.Migrations
|
|||||||
path = table.Column<string>(type: "text", nullable: true),
|
path = table.Column<string>(type: "text", nullable: true),
|
||||||
overview = table.Column<string>(type: "text", nullable: true),
|
overview = table.Column<string>(type: "text", nullable: true),
|
||||||
status = table.Column<Status>(type: "status", nullable: false),
|
status = table.Column<Status>(type: "status", nullable: false),
|
||||||
trailer_url = table.Column<string>(type: "text", nullable: true),
|
|
||||||
start_air = table.Column<DateTime>(type: "timestamp without time zone", nullable: true),
|
start_air = table.Column<DateTime>(type: "timestamp without time zone", nullable: true),
|
||||||
end_air = table.Column<DateTime>(type: "timestamp without time zone", nullable: true),
|
end_air = table.Column<DateTime>(type: "timestamp without time zone", nullable: true),
|
||||||
poster = table.Column<string>(type: "text", nullable: true),
|
images = table.Column<Dictionary<int, string>>(type: "jsonb", nullable: true),
|
||||||
logo = table.Column<string>(type: "text", nullable: true),
|
|
||||||
backdrop = table.Column<string>(type: "text", nullable: true),
|
|
||||||
is_movie = table.Column<bool>(type: "boolean", nullable: false),
|
is_movie = table.Column<bool>(type: "boolean", nullable: false),
|
||||||
studio_id = table.Column<int>(type: "integer", nullable: true)
|
studio_id = table.Column<int>(type: "integer", nullable: true)
|
||||||
},
|
},
|
||||||
@ -230,24 +253,50 @@ namespace Kyoo.Postgresql.Migrations
|
|||||||
});
|
});
|
||||||
|
|
||||||
migrationBuilder.CreateTable(
|
migrationBuilder.CreateTable(
|
||||||
name: "link_collection_show",
|
name: "studio_metadata_id",
|
||||||
columns: table => new
|
columns: table => new
|
||||||
{
|
{
|
||||||
first_id = table.Column<int>(type: "integer", nullable: false),
|
resource_id = table.Column<int>(type: "integer", nullable: false),
|
||||||
second_id = table.Column<int>(type: "integer", nullable: false)
|
provider_id = table.Column<int>(type: "integer", nullable: false),
|
||||||
|
data_id = table.Column<string>(type: "text", nullable: true),
|
||||||
|
link = table.Column<string>(type: "text", nullable: true)
|
||||||
},
|
},
|
||||||
constraints: table =>
|
constraints: table =>
|
||||||
{
|
{
|
||||||
table.PrimaryKey("pk_link_collection_show", x => new { x.first_id, x.second_id });
|
table.PrimaryKey("pk_studio_metadata_id", x => new { x.resource_id, x.provider_id });
|
||||||
table.ForeignKey(
|
table.ForeignKey(
|
||||||
name: "fk_link_collection_show_collections_first_id",
|
name: "fk_studio_metadata_id_providers_provider_id",
|
||||||
column: x => x.first_id,
|
column: x => x.provider_id,
|
||||||
|
principalTable: "providers",
|
||||||
|
principalColumn: "id",
|
||||||
|
onDelete: ReferentialAction.Cascade);
|
||||||
|
table.ForeignKey(
|
||||||
|
name: "fk_studio_metadata_id_studios_studio_id",
|
||||||
|
column: x => x.resource_id,
|
||||||
|
principalTable: "studios",
|
||||||
|
principalColumn: "id",
|
||||||
|
onDelete: ReferentialAction.Cascade);
|
||||||
|
});
|
||||||
|
|
||||||
|
migrationBuilder.CreateTable(
|
||||||
|
name: "link_collection_show",
|
||||||
|
columns: table => new
|
||||||
|
{
|
||||||
|
collection_id = table.Column<int>(type: "integer", nullable: false),
|
||||||
|
show_id = table.Column<int>(type: "integer", nullable: false)
|
||||||
|
},
|
||||||
|
constraints: table =>
|
||||||
|
{
|
||||||
|
table.PrimaryKey("pk_link_collection_show", x => new { x.collection_id, x.show_id });
|
||||||
|
table.ForeignKey(
|
||||||
|
name: "fk_link_collection_show_collections_collection_id",
|
||||||
|
column: x => x.collection_id,
|
||||||
principalTable: "collections",
|
principalTable: "collections",
|
||||||
principalColumn: "id",
|
principalColumn: "id",
|
||||||
onDelete: ReferentialAction.Cascade);
|
onDelete: ReferentialAction.Cascade);
|
||||||
table.ForeignKey(
|
table.ForeignKey(
|
||||||
name: "fk_link_collection_show_shows_second_id",
|
name: "fk_link_collection_show_shows_show_id",
|
||||||
column: x => x.second_id,
|
column: x => x.show_id,
|
||||||
principalTable: "shows",
|
principalTable: "shows",
|
||||||
principalColumn: "id",
|
principalColumn: "id",
|
||||||
onDelete: ReferentialAction.Cascade);
|
onDelete: ReferentialAction.Cascade);
|
||||||
@ -257,21 +306,21 @@ namespace Kyoo.Postgresql.Migrations
|
|||||||
name: "link_library_show",
|
name: "link_library_show",
|
||||||
columns: table => new
|
columns: table => new
|
||||||
{
|
{
|
||||||
first_id = table.Column<int>(type: "integer", nullable: false),
|
library_id = table.Column<int>(type: "integer", nullable: false),
|
||||||
second_id = table.Column<int>(type: "integer", nullable: false)
|
show_id = table.Column<int>(type: "integer", nullable: false)
|
||||||
},
|
},
|
||||||
constraints: table =>
|
constraints: table =>
|
||||||
{
|
{
|
||||||
table.PrimaryKey("pk_link_library_show", x => new { x.first_id, x.second_id });
|
table.PrimaryKey("pk_link_library_show", x => new { x.library_id, x.show_id });
|
||||||
table.ForeignKey(
|
table.ForeignKey(
|
||||||
name: "fk_link_library_show_libraries_first_id",
|
name: "fk_link_library_show_libraries_library_id",
|
||||||
column: x => x.first_id,
|
column: x => x.library_id,
|
||||||
principalTable: "libraries",
|
principalTable: "libraries",
|
||||||
principalColumn: "id",
|
principalColumn: "id",
|
||||||
onDelete: ReferentialAction.Cascade);
|
onDelete: ReferentialAction.Cascade);
|
||||||
table.ForeignKey(
|
table.ForeignKey(
|
||||||
name: "fk_link_library_show_shows_second_id",
|
name: "fk_link_library_show_shows_show_id",
|
||||||
column: x => x.second_id,
|
column: x => x.show_id,
|
||||||
principalTable: "shows",
|
principalTable: "shows",
|
||||||
principalColumn: "id",
|
principalColumn: "id",
|
||||||
onDelete: ReferentialAction.Cascade);
|
onDelete: ReferentialAction.Cascade);
|
||||||
@ -281,21 +330,21 @@ namespace Kyoo.Postgresql.Migrations
|
|||||||
name: "link_show_genre",
|
name: "link_show_genre",
|
||||||
columns: table => new
|
columns: table => new
|
||||||
{
|
{
|
||||||
first_id = table.Column<int>(type: "integer", nullable: false),
|
genre_id = table.Column<int>(type: "integer", nullable: false),
|
||||||
second_id = table.Column<int>(type: "integer", nullable: false)
|
show_id = table.Column<int>(type: "integer", nullable: false)
|
||||||
},
|
},
|
||||||
constraints: table =>
|
constraints: table =>
|
||||||
{
|
{
|
||||||
table.PrimaryKey("pk_link_show_genre", x => new { x.first_id, x.second_id });
|
table.PrimaryKey("pk_link_show_genre", x => new { x.genre_id, x.show_id });
|
||||||
table.ForeignKey(
|
table.ForeignKey(
|
||||||
name: "fk_link_show_genre_genres_second_id",
|
name: "fk_link_show_genre_genres_genre_id",
|
||||||
column: x => x.second_id,
|
column: x => x.genre_id,
|
||||||
principalTable: "genres",
|
principalTable: "genres",
|
||||||
principalColumn: "id",
|
principalColumn: "id",
|
||||||
onDelete: ReferentialAction.Cascade);
|
onDelete: ReferentialAction.Cascade);
|
||||||
table.ForeignKey(
|
table.ForeignKey(
|
||||||
name: "fk_link_show_genre_shows_first_id",
|
name: "fk_link_show_genre_shows_show_id",
|
||||||
column: x => x.first_id,
|
column: x => x.show_id,
|
||||||
principalTable: "shows",
|
principalTable: "shows",
|
||||||
principalColumn: "id",
|
principalColumn: "id",
|
||||||
onDelete: ReferentialAction.Cascade);
|
onDelete: ReferentialAction.Cascade);
|
||||||
@ -305,59 +354,32 @@ namespace Kyoo.Postgresql.Migrations
|
|||||||
name: "link_user_show",
|
name: "link_user_show",
|
||||||
columns: table => new
|
columns: table => new
|
||||||
{
|
{
|
||||||
first_id = table.Column<int>(type: "integer", nullable: false),
|
users_id = table.Column<int>(type: "integer", nullable: false),
|
||||||
second_id = table.Column<int>(type: "integer", nullable: false)
|
watched_id = table.Column<int>(type: "integer", nullable: false)
|
||||||
},
|
},
|
||||||
constraints: table =>
|
constraints: table =>
|
||||||
{
|
{
|
||||||
table.PrimaryKey("pk_link_user_show", x => new { x.first_id, x.second_id });
|
table.PrimaryKey("pk_link_user_show", x => new { x.users_id, x.watched_id });
|
||||||
table.ForeignKey(
|
table.ForeignKey(
|
||||||
name: "fk_link_user_show_shows_second_id",
|
name: "fk_link_user_show_shows_watched_id",
|
||||||
column: x => x.second_id,
|
column: x => x.watched_id,
|
||||||
principalTable: "shows",
|
principalTable: "shows",
|
||||||
principalColumn: "id",
|
principalColumn: "id",
|
||||||
onDelete: ReferentialAction.Cascade);
|
onDelete: ReferentialAction.Cascade);
|
||||||
table.ForeignKey(
|
table.ForeignKey(
|
||||||
name: "fk_link_user_show_users_first_id",
|
name: "fk_link_user_show_users_users_id",
|
||||||
column: x => x.first_id,
|
column: x => x.users_id,
|
||||||
principalTable: "users",
|
principalTable: "users",
|
||||||
principalColumn: "id",
|
principalColumn: "id",
|
||||||
onDelete: ReferentialAction.Cascade);
|
onDelete: ReferentialAction.Cascade);
|
||||||
});
|
});
|
||||||
|
|
||||||
migrationBuilder.CreateTable(
|
|
||||||
name: "metadata_id_show",
|
|
||||||
columns: table => new
|
|
||||||
{
|
|
||||||
first_id = table.Column<int>(type: "integer", nullable: false),
|
|
||||||
second_id = table.Column<int>(type: "integer", nullable: false),
|
|
||||||
data_id = table.Column<string>(type: "text", nullable: true),
|
|
||||||
link = table.Column<string>(type: "text", nullable: true)
|
|
||||||
},
|
|
||||||
constraints: table =>
|
|
||||||
{
|
|
||||||
table.PrimaryKey("pk_metadata_id_show", x => new { x.first_id, x.second_id });
|
|
||||||
table.ForeignKey(
|
|
||||||
name: "fk_metadata_id_show_providers_second_id",
|
|
||||||
column: x => x.second_id,
|
|
||||||
principalTable: "providers",
|
|
||||||
principalColumn: "id",
|
|
||||||
onDelete: ReferentialAction.Cascade);
|
|
||||||
table.ForeignKey(
|
|
||||||
name: "fk_metadata_id_show_shows_first_id",
|
|
||||||
column: x => x.first_id,
|
|
||||||
principalTable: "shows",
|
|
||||||
principalColumn: "id",
|
|
||||||
onDelete: ReferentialAction.Cascade);
|
|
||||||
});
|
|
||||||
|
|
||||||
migrationBuilder.CreateTable(
|
migrationBuilder.CreateTable(
|
||||||
name: "people_roles",
|
name: "people_roles",
|
||||||
columns: table => new
|
columns: table => new
|
||||||
{
|
{
|
||||||
id = table.Column<int>(type: "integer", nullable: false)
|
id = table.Column<int>(type: "integer", nullable: false)
|
||||||
.Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn),
|
.Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn),
|
||||||
for_people = table.Column<bool>(type: "boolean", nullable: false),
|
|
||||||
people_id = table.Column<int>(type: "integer", nullable: false),
|
people_id = table.Column<int>(type: "integer", nullable: false),
|
||||||
show_id = table.Column<int>(type: "integer", nullable: false),
|
show_id = table.Column<int>(type: "integer", nullable: false),
|
||||||
type = table.Column<string>(type: "text", nullable: true),
|
type = table.Column<string>(type: "text", nullable: true),
|
||||||
@ -393,7 +415,7 @@ namespace Kyoo.Postgresql.Migrations
|
|||||||
overview = table.Column<string>(type: "text", nullable: true),
|
overview = table.Column<string>(type: "text", nullable: true),
|
||||||
start_date = table.Column<DateTime>(type: "timestamp without time zone", nullable: true),
|
start_date = table.Column<DateTime>(type: "timestamp without time zone", nullable: true),
|
||||||
end_date = table.Column<DateTime>(type: "timestamp without time zone", nullable: true),
|
end_date = table.Column<DateTime>(type: "timestamp without time zone", nullable: true),
|
||||||
poster = table.Column<string>(type: "text", nullable: true)
|
images = table.Column<Dictionary<int, string>>(type: "jsonb", nullable: true)
|
||||||
},
|
},
|
||||||
constraints: table =>
|
constraints: table =>
|
||||||
{
|
{
|
||||||
@ -406,6 +428,32 @@ namespace Kyoo.Postgresql.Migrations
|
|||||||
onDelete: ReferentialAction.Cascade);
|
onDelete: ReferentialAction.Cascade);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
migrationBuilder.CreateTable(
|
||||||
|
name: "show_metadata_id",
|
||||||
|
columns: table => new
|
||||||
|
{
|
||||||
|
resource_id = table.Column<int>(type: "integer", nullable: false),
|
||||||
|
provider_id = table.Column<int>(type: "integer", nullable: false),
|
||||||
|
data_id = table.Column<string>(type: "text", nullable: true),
|
||||||
|
link = table.Column<string>(type: "text", nullable: true)
|
||||||
|
},
|
||||||
|
constraints: table =>
|
||||||
|
{
|
||||||
|
table.PrimaryKey("pk_show_metadata_id", x => new { x.resource_id, x.provider_id });
|
||||||
|
table.ForeignKey(
|
||||||
|
name: "fk_show_metadata_id_providers_provider_id",
|
||||||
|
column: x => x.provider_id,
|
||||||
|
principalTable: "providers",
|
||||||
|
principalColumn: "id",
|
||||||
|
onDelete: ReferentialAction.Cascade);
|
||||||
|
table.ForeignKey(
|
||||||
|
name: "fk_show_metadata_id_shows_show_id",
|
||||||
|
column: x => x.resource_id,
|
||||||
|
principalTable: "shows",
|
||||||
|
principalColumn: "id",
|
||||||
|
onDelete: ReferentialAction.Cascade);
|
||||||
|
});
|
||||||
|
|
||||||
migrationBuilder.CreateTable(
|
migrationBuilder.CreateTable(
|
||||||
name: "episodes",
|
name: "episodes",
|
||||||
columns: table => new
|
columns: table => new
|
||||||
@ -419,7 +467,7 @@ namespace Kyoo.Postgresql.Migrations
|
|||||||
episode_number = table.Column<int>(type: "integer", nullable: true),
|
episode_number = table.Column<int>(type: "integer", nullable: true),
|
||||||
absolute_number = table.Column<int>(type: "integer", nullable: true),
|
absolute_number = table.Column<int>(type: "integer", nullable: true),
|
||||||
path = table.Column<string>(type: "text", nullable: true),
|
path = table.Column<string>(type: "text", nullable: true),
|
||||||
thumb = table.Column<string>(type: "text", nullable: true),
|
images = table.Column<Dictionary<int, string>>(type: "jsonb", nullable: true),
|
||||||
title = table.Column<string>(type: "text", nullable: true),
|
title = table.Column<string>(type: "text", nullable: true),
|
||||||
overview = table.Column<string>(type: "text", nullable: true),
|
overview = table.Column<string>(type: "text", nullable: true),
|
||||||
release_date = table.Column<DateTime>(type: "timestamp without time zone", nullable: true)
|
release_date = table.Column<DateTime>(type: "timestamp without time zone", nullable: true)
|
||||||
@ -442,52 +490,52 @@ namespace Kyoo.Postgresql.Migrations
|
|||||||
});
|
});
|
||||||
|
|
||||||
migrationBuilder.CreateTable(
|
migrationBuilder.CreateTable(
|
||||||
name: "metadata_id_season",
|
name: "season_metadata_id",
|
||||||
columns: table => new
|
columns: table => new
|
||||||
{
|
{
|
||||||
first_id = table.Column<int>(type: "integer", nullable: false),
|
resource_id = table.Column<int>(type: "integer", nullable: false),
|
||||||
second_id = table.Column<int>(type: "integer", nullable: false),
|
provider_id = table.Column<int>(type: "integer", nullable: false),
|
||||||
data_id = table.Column<string>(type: "text", nullable: true),
|
data_id = table.Column<string>(type: "text", nullable: true),
|
||||||
link = table.Column<string>(type: "text", nullable: true)
|
link = table.Column<string>(type: "text", nullable: true)
|
||||||
},
|
},
|
||||||
constraints: table =>
|
constraints: table =>
|
||||||
{
|
{
|
||||||
table.PrimaryKey("pk_metadata_id_season", x => new { x.first_id, x.second_id });
|
table.PrimaryKey("pk_season_metadata_id", x => new { x.resource_id, x.provider_id });
|
||||||
table.ForeignKey(
|
table.ForeignKey(
|
||||||
name: "fk_metadata_id_season_providers_second_id",
|
name: "fk_season_metadata_id_providers_provider_id",
|
||||||
column: x => x.second_id,
|
column: x => x.provider_id,
|
||||||
principalTable: "providers",
|
principalTable: "providers",
|
||||||
principalColumn: "id",
|
principalColumn: "id",
|
||||||
onDelete: ReferentialAction.Cascade);
|
onDelete: ReferentialAction.Cascade);
|
||||||
table.ForeignKey(
|
table.ForeignKey(
|
||||||
name: "fk_metadata_id_season_seasons_first_id",
|
name: "fk_season_metadata_id_seasons_season_id",
|
||||||
column: x => x.first_id,
|
column: x => x.resource_id,
|
||||||
principalTable: "seasons",
|
principalTable: "seasons",
|
||||||
principalColumn: "id",
|
principalColumn: "id",
|
||||||
onDelete: ReferentialAction.Cascade);
|
onDelete: ReferentialAction.Cascade);
|
||||||
});
|
});
|
||||||
|
|
||||||
migrationBuilder.CreateTable(
|
migrationBuilder.CreateTable(
|
||||||
name: "metadata_id_episode",
|
name: "episode_metadata_id",
|
||||||
columns: table => new
|
columns: table => new
|
||||||
{
|
{
|
||||||
first_id = table.Column<int>(type: "integer", nullable: false),
|
resource_id = table.Column<int>(type: "integer", nullable: false),
|
||||||
second_id = table.Column<int>(type: "integer", nullable: false),
|
provider_id = table.Column<int>(type: "integer", nullable: false),
|
||||||
data_id = table.Column<string>(type: "text", nullable: true),
|
data_id = table.Column<string>(type: "text", nullable: true),
|
||||||
link = table.Column<string>(type: "text", nullable: true)
|
link = table.Column<string>(type: "text", nullable: true)
|
||||||
},
|
},
|
||||||
constraints: table =>
|
constraints: table =>
|
||||||
{
|
{
|
||||||
table.PrimaryKey("pk_metadata_id_episode", x => new { x.first_id, x.second_id });
|
table.PrimaryKey("pk_episode_metadata_id", x => new { x.resource_id, x.provider_id });
|
||||||
table.ForeignKey(
|
table.ForeignKey(
|
||||||
name: "fk_metadata_id_episode_episodes_first_id",
|
name: "fk_episode_metadata_id_episodes_episode_id",
|
||||||
column: x => x.first_id,
|
column: x => x.resource_id,
|
||||||
principalTable: "episodes",
|
principalTable: "episodes",
|
||||||
principalColumn: "id",
|
principalColumn: "id",
|
||||||
onDelete: ReferentialAction.Cascade);
|
onDelete: ReferentialAction.Cascade);
|
||||||
table.ForeignKey(
|
table.ForeignKey(
|
||||||
name: "fk_metadata_id_episode_providers_second_id",
|
name: "fk_episode_metadata_id_providers_provider_id",
|
||||||
column: x => x.second_id,
|
column: x => x.provider_id,
|
||||||
principalTable: "providers",
|
principalTable: "providers",
|
||||||
principalColumn: "id",
|
principalColumn: "id",
|
||||||
onDelete: ReferentialAction.Cascade);
|
onDelete: ReferentialAction.Cascade);
|
||||||
@ -526,33 +574,43 @@ namespace Kyoo.Postgresql.Migrations
|
|||||||
name: "watched_episodes",
|
name: "watched_episodes",
|
||||||
columns: table => new
|
columns: table => new
|
||||||
{
|
{
|
||||||
first_id = table.Column<int>(type: "integer", nullable: false),
|
user_id = table.Column<int>(type: "integer", nullable: false),
|
||||||
second_id = table.Column<int>(type: "integer", nullable: false),
|
episode_id = table.Column<int>(type: "integer", nullable: false),
|
||||||
watched_percentage = table.Column<int>(type: "integer", nullable: false)
|
watched_percentage = table.Column<int>(type: "integer", nullable: false)
|
||||||
},
|
},
|
||||||
constraints: table =>
|
constraints: table =>
|
||||||
{
|
{
|
||||||
table.PrimaryKey("pk_watched_episodes", x => new { x.first_id, x.second_id });
|
table.PrimaryKey("pk_watched_episodes", x => new { x.user_id, x.episode_id });
|
||||||
table.ForeignKey(
|
table.ForeignKey(
|
||||||
name: "fk_watched_episodes_episodes_second_id",
|
name: "fk_watched_episodes_episodes_episode_id",
|
||||||
column: x => x.second_id,
|
column: x => x.episode_id,
|
||||||
principalTable: "episodes",
|
principalTable: "episodes",
|
||||||
principalColumn: "id",
|
principalColumn: "id",
|
||||||
onDelete: ReferentialAction.Cascade);
|
onDelete: ReferentialAction.Cascade);
|
||||||
table.ForeignKey(
|
table.ForeignKey(
|
||||||
name: "fk_watched_episodes_users_first_id",
|
name: "fk_watched_episodes_users_user_id",
|
||||||
column: x => x.first_id,
|
column: x => x.user_id,
|
||||||
principalTable: "users",
|
principalTable: "users",
|
||||||
principalColumn: "id",
|
principalColumn: "id",
|
||||||
onDelete: ReferentialAction.Cascade);
|
onDelete: ReferentialAction.Cascade);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
migrationBuilder.CreateIndex(
|
||||||
|
name: "ix_collection_metadata_id_provider_id",
|
||||||
|
table: "collection_metadata_id",
|
||||||
|
column: "provider_id");
|
||||||
|
|
||||||
migrationBuilder.CreateIndex(
|
migrationBuilder.CreateIndex(
|
||||||
name: "ix_collections_slug",
|
name: "ix_collections_slug",
|
||||||
table: "collections",
|
table: "collections",
|
||||||
column: "slug",
|
column: "slug",
|
||||||
unique: true);
|
unique: true);
|
||||||
|
|
||||||
|
migrationBuilder.CreateIndex(
|
||||||
|
name: "ix_episode_metadata_id_provider_id",
|
||||||
|
table: "episode_metadata_id",
|
||||||
|
column: "provider_id");
|
||||||
|
|
||||||
migrationBuilder.CreateIndex(
|
migrationBuilder.CreateIndex(
|
||||||
name: "ix_episodes_season_id",
|
name: "ix_episodes_season_id",
|
||||||
table: "episodes",
|
table: "episodes",
|
||||||
@ -583,54 +641,34 @@ namespace Kyoo.Postgresql.Migrations
|
|||||||
unique: true);
|
unique: true);
|
||||||
|
|
||||||
migrationBuilder.CreateIndex(
|
migrationBuilder.CreateIndex(
|
||||||
name: "ix_link_collection_show_second_id",
|
name: "ix_link_collection_show_show_id",
|
||||||
table: "link_collection_show",
|
table: "link_collection_show",
|
||||||
column: "second_id");
|
column: "show_id");
|
||||||
|
|
||||||
migrationBuilder.CreateIndex(
|
migrationBuilder.CreateIndex(
|
||||||
name: "ix_link_library_collection_second_id",
|
name: "ix_link_library_collection_library_id",
|
||||||
table: "link_library_collection",
|
table: "link_library_collection",
|
||||||
column: "second_id");
|
column: "library_id");
|
||||||
|
|
||||||
migrationBuilder.CreateIndex(
|
migrationBuilder.CreateIndex(
|
||||||
name: "ix_link_library_provider_second_id",
|
name: "ix_link_library_provider_provider_id",
|
||||||
table: "link_library_provider",
|
table: "link_library_provider",
|
||||||
column: "second_id");
|
column: "provider_id");
|
||||||
|
|
||||||
migrationBuilder.CreateIndex(
|
migrationBuilder.CreateIndex(
|
||||||
name: "ix_link_library_show_second_id",
|
name: "ix_link_library_show_show_id",
|
||||||
table: "link_library_show",
|
table: "link_library_show",
|
||||||
column: "second_id");
|
column: "show_id");
|
||||||
|
|
||||||
migrationBuilder.CreateIndex(
|
migrationBuilder.CreateIndex(
|
||||||
name: "ix_link_show_genre_second_id",
|
name: "ix_link_show_genre_show_id",
|
||||||
table: "link_show_genre",
|
table: "link_show_genre",
|
||||||
column: "second_id");
|
column: "show_id");
|
||||||
|
|
||||||
migrationBuilder.CreateIndex(
|
migrationBuilder.CreateIndex(
|
||||||
name: "ix_link_user_show_second_id",
|
name: "ix_link_user_show_watched_id",
|
||||||
table: "link_user_show",
|
table: "link_user_show",
|
||||||
column: "second_id");
|
column: "watched_id");
|
||||||
|
|
||||||
migrationBuilder.CreateIndex(
|
|
||||||
name: "ix_metadata_id_episode_second_id",
|
|
||||||
table: "metadata_id_episode",
|
|
||||||
column: "second_id");
|
|
||||||
|
|
||||||
migrationBuilder.CreateIndex(
|
|
||||||
name: "ix_metadata_id_people_second_id",
|
|
||||||
table: "metadata_id_people",
|
|
||||||
column: "second_id");
|
|
||||||
|
|
||||||
migrationBuilder.CreateIndex(
|
|
||||||
name: "ix_metadata_id_season_second_id",
|
|
||||||
table: "metadata_id_season",
|
|
||||||
column: "second_id");
|
|
||||||
|
|
||||||
migrationBuilder.CreateIndex(
|
|
||||||
name: "ix_metadata_id_show_second_id",
|
|
||||||
table: "metadata_id_show",
|
|
||||||
column: "second_id");
|
|
||||||
|
|
||||||
migrationBuilder.CreateIndex(
|
migrationBuilder.CreateIndex(
|
||||||
name: "ix_people_slug",
|
name: "ix_people_slug",
|
||||||
@ -638,6 +676,11 @@ namespace Kyoo.Postgresql.Migrations
|
|||||||
column: "slug",
|
column: "slug",
|
||||||
unique: true);
|
unique: true);
|
||||||
|
|
||||||
|
migrationBuilder.CreateIndex(
|
||||||
|
name: "ix_people_metadata_id_provider_id",
|
||||||
|
table: "people_metadata_id",
|
||||||
|
column: "provider_id");
|
||||||
|
|
||||||
migrationBuilder.CreateIndex(
|
migrationBuilder.CreateIndex(
|
||||||
name: "ix_people_roles_people_id",
|
name: "ix_people_roles_people_id",
|
||||||
table: "people_roles",
|
table: "people_roles",
|
||||||
@ -654,6 +697,11 @@ namespace Kyoo.Postgresql.Migrations
|
|||||||
column: "slug",
|
column: "slug",
|
||||||
unique: true);
|
unique: true);
|
||||||
|
|
||||||
|
migrationBuilder.CreateIndex(
|
||||||
|
name: "ix_season_metadata_id_provider_id",
|
||||||
|
table: "season_metadata_id",
|
||||||
|
column: "provider_id");
|
||||||
|
|
||||||
migrationBuilder.CreateIndex(
|
migrationBuilder.CreateIndex(
|
||||||
name: "ix_seasons_show_id_season_number",
|
name: "ix_seasons_show_id_season_number",
|
||||||
table: "seasons",
|
table: "seasons",
|
||||||
@ -666,6 +714,11 @@ namespace Kyoo.Postgresql.Migrations
|
|||||||
column: "slug",
|
column: "slug",
|
||||||
unique: true);
|
unique: true);
|
||||||
|
|
||||||
|
migrationBuilder.CreateIndex(
|
||||||
|
name: "ix_show_metadata_id_provider_id",
|
||||||
|
table: "show_metadata_id",
|
||||||
|
column: "provider_id");
|
||||||
|
|
||||||
migrationBuilder.CreateIndex(
|
migrationBuilder.CreateIndex(
|
||||||
name: "ix_shows_slug",
|
name: "ix_shows_slug",
|
||||||
table: "shows",
|
table: "shows",
|
||||||
@ -677,6 +730,11 @@ namespace Kyoo.Postgresql.Migrations
|
|||||||
table: "shows",
|
table: "shows",
|
||||||
column: "studio_id");
|
column: "studio_id");
|
||||||
|
|
||||||
|
migrationBuilder.CreateIndex(
|
||||||
|
name: "ix_studio_metadata_id_provider_id",
|
||||||
|
table: "studio_metadata_id",
|
||||||
|
column: "provider_id");
|
||||||
|
|
||||||
migrationBuilder.CreateIndex(
|
migrationBuilder.CreateIndex(
|
||||||
name: "ix_studios_slug",
|
name: "ix_studios_slug",
|
||||||
table: "studios",
|
table: "studios",
|
||||||
@ -702,13 +760,19 @@ namespace Kyoo.Postgresql.Migrations
|
|||||||
unique: true);
|
unique: true);
|
||||||
|
|
||||||
migrationBuilder.CreateIndex(
|
migrationBuilder.CreateIndex(
|
||||||
name: "ix_watched_episodes_second_id",
|
name: "ix_watched_episodes_episode_id",
|
||||||
table: "watched_episodes",
|
table: "watched_episodes",
|
||||||
column: "second_id");
|
column: "episode_id");
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override void Down(MigrationBuilder migrationBuilder)
|
protected override void Down(MigrationBuilder migrationBuilder)
|
||||||
{
|
{
|
||||||
|
migrationBuilder.DropTable(
|
||||||
|
name: "collection_metadata_id");
|
||||||
|
|
||||||
|
migrationBuilder.DropTable(
|
||||||
|
name: "episode_metadata_id");
|
||||||
|
|
||||||
migrationBuilder.DropTable(
|
migrationBuilder.DropTable(
|
||||||
name: "link_collection_show");
|
name: "link_collection_show");
|
||||||
|
|
||||||
@ -728,20 +792,20 @@ namespace Kyoo.Postgresql.Migrations
|
|||||||
name: "link_user_show");
|
name: "link_user_show");
|
||||||
|
|
||||||
migrationBuilder.DropTable(
|
migrationBuilder.DropTable(
|
||||||
name: "metadata_id_episode");
|
name: "people_metadata_id");
|
||||||
|
|
||||||
migrationBuilder.DropTable(
|
|
||||||
name: "metadata_id_people");
|
|
||||||
|
|
||||||
migrationBuilder.DropTable(
|
|
||||||
name: "metadata_id_season");
|
|
||||||
|
|
||||||
migrationBuilder.DropTable(
|
|
||||||
name: "metadata_id_show");
|
|
||||||
|
|
||||||
migrationBuilder.DropTable(
|
migrationBuilder.DropTable(
|
||||||
name: "people_roles");
|
name: "people_roles");
|
||||||
|
|
||||||
|
migrationBuilder.DropTable(
|
||||||
|
name: "season_metadata_id");
|
||||||
|
|
||||||
|
migrationBuilder.DropTable(
|
||||||
|
name: "show_metadata_id");
|
||||||
|
|
||||||
|
migrationBuilder.DropTable(
|
||||||
|
name: "studio_metadata_id");
|
||||||
|
|
||||||
migrationBuilder.DropTable(
|
migrationBuilder.DropTable(
|
||||||
name: "tracks");
|
name: "tracks");
|
||||||
|
|
||||||
@ -758,10 +822,10 @@ namespace Kyoo.Postgresql.Migrations
|
|||||||
name: "genres");
|
name: "genres");
|
||||||
|
|
||||||
migrationBuilder.DropTable(
|
migrationBuilder.DropTable(
|
||||||
name: "providers");
|
name: "people");
|
||||||
|
|
||||||
migrationBuilder.DropTable(
|
migrationBuilder.DropTable(
|
||||||
name: "people");
|
name: "providers");
|
||||||
|
|
||||||
migrationBuilder.DropTable(
|
migrationBuilder.DropTable(
|
||||||
name: "episodes");
|
name: "episodes");
|
File diff suppressed because it is too large
Load Diff
@ -141,7 +141,7 @@ namespace Kyoo.Postgresql.Migrations
|
|||||||
// language=PostgreSQL
|
// language=PostgreSQL
|
||||||
migrationBuilder.Sql(@"
|
migrationBuilder.Sql(@"
|
||||||
CREATE VIEW library_items AS
|
CREATE VIEW library_items AS
|
||||||
SELECT s.id, s.slug, s.title, s.overview, s.status, s.start_air, s.end_air, s.poster, CASE
|
SELECT s.id, s.slug, s.title, s.overview, s.status, s.start_air, s.end_air, s.images, CASE
|
||||||
WHEN s.is_movie THEN 'movie'::item_type
|
WHEN s.is_movie THEN 'movie'::item_type
|
||||||
ELSE 'show'::item_type
|
ELSE 'show'::item_type
|
||||||
END AS type
|
END AS type
|
||||||
@ -149,11 +149,11 @@ namespace Kyoo.Postgresql.Migrations
|
|||||||
WHERE NOT (EXISTS (
|
WHERE NOT (EXISTS (
|
||||||
SELECT 1
|
SELECT 1
|
||||||
FROM link_collection_show AS l
|
FROM link_collection_show AS l
|
||||||
INNER JOIN collections AS c ON l.first_id = c.id
|
INNER JOIN collections AS c ON l.collection_id = c.id
|
||||||
WHERE s.id = l.second_id))
|
WHERE s.id = l.show_id))
|
||||||
UNION ALL
|
UNION ALL
|
||||||
SELECT -c0.id, c0.slug, c0.name AS title, c0.overview, 'unknown'::status AS status,
|
SELECT -c0.id, c0.slug, c0.name AS title, c0.overview, 'unknown'::status AS status,
|
||||||
NULL AS start_air, NULL AS end_air, c0.poster, 'collection'::item_type AS type
|
NULL AS start_air, NULL AS end_air, c0.images, 'collection'::item_type AS type
|
||||||
FROM collections AS c0");
|
FROM collections AS c0");
|
||||||
}
|
}
|
||||||
|
|
File diff suppressed because it is too large
Load Diff
@ -1,6 +1,8 @@
|
|||||||
using System;
|
using System;
|
||||||
|
using System.Globalization;
|
||||||
using System.Linq.Expressions;
|
using System.Linq.Expressions;
|
||||||
using System.Reflection;
|
using System.Reflection;
|
||||||
|
using EFCore.NamingConventions.Internal;
|
||||||
using Kyoo.Models;
|
using Kyoo.Models;
|
||||||
using Microsoft.EntityFrameworkCore;
|
using Microsoft.EntityFrameworkCore;
|
||||||
using Npgsql;
|
using Npgsql;
|
||||||
@ -99,9 +101,55 @@ namespace Kyoo.Postgresql
|
|||||||
.Property(x => x.ExtraData)
|
.Property(x => x.ExtraData)
|
||||||
.HasColumnType("jsonb");
|
.HasColumnType("jsonb");
|
||||||
|
|
||||||
|
modelBuilder.Entity<LibraryItem>()
|
||||||
|
.Property(x => x.Images)
|
||||||
|
.HasColumnType("jsonb");
|
||||||
|
modelBuilder.Entity<Collection>()
|
||||||
|
.Property(x => x.Images)
|
||||||
|
.HasColumnType("jsonb");
|
||||||
|
modelBuilder.Entity<Show>()
|
||||||
|
.Property(x => x.Images)
|
||||||
|
.HasColumnType("jsonb");
|
||||||
|
modelBuilder.Entity<Season>()
|
||||||
|
.Property(x => x.Images)
|
||||||
|
.HasColumnType("jsonb");
|
||||||
|
modelBuilder.Entity<Episode>()
|
||||||
|
.Property(x => x.Images)
|
||||||
|
.HasColumnType("jsonb");
|
||||||
|
modelBuilder.Entity<People>()
|
||||||
|
.Property(x => x.Images)
|
||||||
|
.HasColumnType("jsonb");
|
||||||
|
modelBuilder.Entity<Provider>()
|
||||||
|
.Property(x => x.Images)
|
||||||
|
.HasColumnType("jsonb");
|
||||||
|
modelBuilder.Entity<User>()
|
||||||
|
.Property(x => x.Images)
|
||||||
|
.HasColumnType("jsonb");
|
||||||
|
|
||||||
base.OnModelCreating(modelBuilder);
|
base.OnModelCreating(modelBuilder);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
protected override string MetadataName<T>()
|
||||||
|
{
|
||||||
|
SnakeCaseNameRewriter rewriter = new(CultureInfo.InvariantCulture);
|
||||||
|
return rewriter.RewriteName(typeof(T).Name + nameof(MetadataID));
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
protected override string LinkName<T, T2>()
|
||||||
|
{
|
||||||
|
SnakeCaseNameRewriter rewriter = new(CultureInfo.InvariantCulture);
|
||||||
|
return rewriter.RewriteName("Link" + typeof(T).Name + typeof(T2).Name);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
protected override string LinkNameFk<T>()
|
||||||
|
{
|
||||||
|
SnakeCaseNameRewriter rewriter = new(CultureInfo.InvariantCulture);
|
||||||
|
return rewriter.RewriteName(typeof(T).Name + "ID");
|
||||||
|
}
|
||||||
|
|
||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
protected override bool IsDuplicateException(Exception ex)
|
protected override bool IsDuplicateException(Exception ex)
|
||||||
{
|
{
|
||||||
|
@ -66,7 +66,7 @@ namespace Kyoo.Postgresql
|
|||||||
x.UseNpgsql(_configuration.GetDatabaseConnection("postgres"));
|
x.UseNpgsql(_configuration.GetDatabaseConnection("postgres"));
|
||||||
if (_environment.IsDevelopment())
|
if (_environment.IsDevelopment())
|
||||||
x.EnableDetailedErrors().EnableSensitiveDataLogging();
|
x.EnableDetailedErrors().EnableSensitiveDataLogging();
|
||||||
});
|
}, ServiceLifetime.Transient);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
|
File diff suppressed because it is too large
Load Diff
@ -15,7 +15,7 @@ namespace Kyoo.SqLite.Migrations
|
|||||||
.Annotation("Sqlite:Autoincrement", true),
|
.Annotation("Sqlite:Autoincrement", true),
|
||||||
Slug = table.Column<string>(type: "TEXT", nullable: false),
|
Slug = table.Column<string>(type: "TEXT", nullable: false),
|
||||||
Name = table.Column<string>(type: "TEXT", nullable: true),
|
Name = table.Column<string>(type: "TEXT", nullable: true),
|
||||||
Poster = table.Column<string>(type: "TEXT", nullable: true),
|
Images = table.Column<string>(type: "TEXT", nullable: true),
|
||||||
Overview = table.Column<string>(type: "TEXT", nullable: true)
|
Overview = table.Column<string>(type: "TEXT", nullable: true)
|
||||||
},
|
},
|
||||||
constraints: table =>
|
constraints: table =>
|
||||||
@ -60,7 +60,7 @@ namespace Kyoo.SqLite.Migrations
|
|||||||
.Annotation("Sqlite:Autoincrement", true),
|
.Annotation("Sqlite:Autoincrement", true),
|
||||||
Slug = table.Column<string>(type: "TEXT", nullable: false),
|
Slug = table.Column<string>(type: "TEXT", nullable: false),
|
||||||
Name = table.Column<string>(type: "TEXT", nullable: true),
|
Name = table.Column<string>(type: "TEXT", nullable: true),
|
||||||
Poster = table.Column<string>(type: "TEXT", nullable: true)
|
Images = table.Column<string>(type: "TEXT", nullable: true)
|
||||||
},
|
},
|
||||||
constraints: table =>
|
constraints: table =>
|
||||||
{
|
{
|
||||||
@ -75,8 +75,7 @@ namespace Kyoo.SqLite.Migrations
|
|||||||
.Annotation("Sqlite:Autoincrement", true),
|
.Annotation("Sqlite:Autoincrement", true),
|
||||||
Slug = table.Column<string>(type: "TEXT", nullable: false),
|
Slug = table.Column<string>(type: "TEXT", nullable: false),
|
||||||
Name = table.Column<string>(type: "TEXT", nullable: true),
|
Name = table.Column<string>(type: "TEXT", nullable: true),
|
||||||
Logo = table.Column<string>(type: "TEXT", nullable: true),
|
Images = table.Column<string>(type: "TEXT", nullable: true)
|
||||||
LogoExtension = table.Column<string>(type: "TEXT", nullable: true)
|
|
||||||
},
|
},
|
||||||
constraints: table =>
|
constraints: table =>
|
||||||
{
|
{
|
||||||
@ -108,7 +107,8 @@ namespace Kyoo.SqLite.Migrations
|
|||||||
Email = table.Column<string>(type: "TEXT", nullable: true),
|
Email = table.Column<string>(type: "TEXT", nullable: true),
|
||||||
Password = table.Column<string>(type: "TEXT", nullable: true),
|
Password = table.Column<string>(type: "TEXT", nullable: true),
|
||||||
Permissions = table.Column<string>(type: "TEXT", nullable: true),
|
Permissions = table.Column<string>(type: "TEXT", nullable: true),
|
||||||
ExtraData = table.Column<string>(type: "TEXT", nullable: true)
|
ExtraData = table.Column<string>(type: "TEXT", nullable: true),
|
||||||
|
Images = table.Column<string>(type: "TEXT", nullable: true)
|
||||||
},
|
},
|
||||||
constraints: table =>
|
constraints: table =>
|
||||||
{
|
{
|
||||||
@ -116,74 +116,100 @@ namespace Kyoo.SqLite.Migrations
|
|||||||
});
|
});
|
||||||
|
|
||||||
migrationBuilder.CreateTable(
|
migrationBuilder.CreateTable(
|
||||||
name: "Link<Library, Collection>",
|
name: "LinkLibraryCollection",
|
||||||
columns: table => new
|
columns: table => new
|
||||||
{
|
{
|
||||||
FirstID = table.Column<int>(type: "INTEGER", nullable: false),
|
CollectionID = table.Column<int>(type: "INTEGER", nullable: false),
|
||||||
SecondID = table.Column<int>(type: "INTEGER", nullable: false)
|
LibraryID = table.Column<int>(type: "INTEGER", nullable: false)
|
||||||
},
|
},
|
||||||
constraints: table =>
|
constraints: table =>
|
||||||
{
|
{
|
||||||
table.PrimaryKey("PK_Link<Library, Collection>", x => new { x.FirstID, x.SecondID });
|
table.PrimaryKey("PK_LinkLibraryCollection", x => new { x.CollectionID, x.LibraryID });
|
||||||
table.ForeignKey(
|
table.ForeignKey(
|
||||||
name: "FK_Link<Library, Collection>_Collections_SecondID",
|
name: "FK_LinkLibraryCollection_Collections_CollectionID",
|
||||||
column: x => x.SecondID,
|
column: x => x.CollectionID,
|
||||||
principalTable: "Collections",
|
principalTable: "Collections",
|
||||||
principalColumn: "ID",
|
principalColumn: "ID",
|
||||||
onDelete: ReferentialAction.Cascade);
|
onDelete: ReferentialAction.Cascade);
|
||||||
table.ForeignKey(
|
table.ForeignKey(
|
||||||
name: "FK_Link<Library, Collection>_Libraries_FirstID",
|
name: "FK_LinkLibraryCollection_Libraries_LibraryID",
|
||||||
column: x => x.FirstID,
|
column: x => x.LibraryID,
|
||||||
principalTable: "Libraries",
|
principalTable: "Libraries",
|
||||||
principalColumn: "ID",
|
principalColumn: "ID",
|
||||||
onDelete: ReferentialAction.Cascade);
|
onDelete: ReferentialAction.Cascade);
|
||||||
});
|
});
|
||||||
|
|
||||||
migrationBuilder.CreateTable(
|
migrationBuilder.CreateTable(
|
||||||
name: "Link<Library, Provider>",
|
name: "CollectionMetadataID",
|
||||||
columns: table => new
|
columns: table => new
|
||||||
{
|
{
|
||||||
FirstID = table.Column<int>(type: "INTEGER", nullable: false),
|
ResourceID = table.Column<int>(type: "INTEGER", nullable: false),
|
||||||
SecondID = table.Column<int>(type: "INTEGER", nullable: false)
|
ProviderID = table.Column<int>(type: "INTEGER", nullable: false),
|
||||||
|
DataID = table.Column<string>(type: "TEXT", nullable: true),
|
||||||
|
Link = table.Column<string>(type: "TEXT", nullable: true)
|
||||||
},
|
},
|
||||||
constraints: table =>
|
constraints: table =>
|
||||||
{
|
{
|
||||||
table.PrimaryKey("PK_Link<Library, Provider>", x => new { x.FirstID, x.SecondID });
|
table.PrimaryKey("PK_CollectionMetadataID", x => new { x.ResourceID, x.ProviderID });
|
||||||
table.ForeignKey(
|
table.ForeignKey(
|
||||||
name: "FK_Link<Library, Provider>_Libraries_FirstID",
|
name: "FK_CollectionMetadataID_Collections_ResourceID",
|
||||||
column: x => x.FirstID,
|
column: x => x.ResourceID,
|
||||||
principalTable: "Libraries",
|
principalTable: "Collections",
|
||||||
principalColumn: "ID",
|
principalColumn: "ID",
|
||||||
onDelete: ReferentialAction.Cascade);
|
onDelete: ReferentialAction.Cascade);
|
||||||
table.ForeignKey(
|
table.ForeignKey(
|
||||||
name: "FK_Link<Library, Provider>_Providers_SecondID",
|
name: "FK_CollectionMetadataID_Providers_ProviderID",
|
||||||
column: x => x.SecondID,
|
column: x => x.ProviderID,
|
||||||
principalTable: "Providers",
|
principalTable: "Providers",
|
||||||
principalColumn: "ID",
|
principalColumn: "ID",
|
||||||
onDelete: ReferentialAction.Cascade);
|
onDelete: ReferentialAction.Cascade);
|
||||||
});
|
});
|
||||||
|
|
||||||
migrationBuilder.CreateTable(
|
migrationBuilder.CreateTable(
|
||||||
name: "MetadataID<People>",
|
name: "LinkLibraryProvider",
|
||||||
columns: table => new
|
columns: table => new
|
||||||
{
|
{
|
||||||
FirstID = table.Column<int>(type: "INTEGER", nullable: false),
|
LibraryID = table.Column<int>(type: "INTEGER", nullable: false),
|
||||||
SecondID = table.Column<int>(type: "INTEGER", nullable: false),
|
ProviderID = table.Column<int>(type: "INTEGER", nullable: false)
|
||||||
|
},
|
||||||
|
constraints: table =>
|
||||||
|
{
|
||||||
|
table.PrimaryKey("PK_LinkLibraryProvider", x => new { x.LibraryID, x.ProviderID });
|
||||||
|
table.ForeignKey(
|
||||||
|
name: "FK_LinkLibraryProvider_Libraries_LibraryID",
|
||||||
|
column: x => x.LibraryID,
|
||||||
|
principalTable: "Libraries",
|
||||||
|
principalColumn: "ID",
|
||||||
|
onDelete: ReferentialAction.Cascade);
|
||||||
|
table.ForeignKey(
|
||||||
|
name: "FK_LinkLibraryProvider_Providers_ProviderID",
|
||||||
|
column: x => x.ProviderID,
|
||||||
|
principalTable: "Providers",
|
||||||
|
principalColumn: "ID",
|
||||||
|
onDelete: ReferentialAction.Cascade);
|
||||||
|
});
|
||||||
|
|
||||||
|
migrationBuilder.CreateTable(
|
||||||
|
name: "PeopleMetadataID",
|
||||||
|
columns: table => new
|
||||||
|
{
|
||||||
|
ResourceID = table.Column<int>(type: "INTEGER", nullable: false),
|
||||||
|
ProviderID = table.Column<int>(type: "INTEGER", nullable: false),
|
||||||
DataID = table.Column<string>(type: "TEXT", nullable: true),
|
DataID = table.Column<string>(type: "TEXT", nullable: true),
|
||||||
Link = table.Column<string>(type: "TEXT", nullable: true)
|
Link = table.Column<string>(type: "TEXT", nullable: true)
|
||||||
},
|
},
|
||||||
constraints: table =>
|
constraints: table =>
|
||||||
{
|
{
|
||||||
table.PrimaryKey("PK_MetadataID<People>", x => new { x.FirstID, x.SecondID });
|
table.PrimaryKey("PK_PeopleMetadataID", x => new { x.ResourceID, x.ProviderID });
|
||||||
table.ForeignKey(
|
table.ForeignKey(
|
||||||
name: "FK_MetadataID<People>_People_FirstID",
|
name: "FK_PeopleMetadataID_People_ResourceID",
|
||||||
column: x => x.FirstID,
|
column: x => x.ResourceID,
|
||||||
principalTable: "People",
|
principalTable: "People",
|
||||||
principalColumn: "ID",
|
principalColumn: "ID",
|
||||||
onDelete: ReferentialAction.Cascade);
|
onDelete: ReferentialAction.Cascade);
|
||||||
table.ForeignKey(
|
table.ForeignKey(
|
||||||
name: "FK_MetadataID<People>_Providers_SecondID",
|
name: "FK_PeopleMetadataID_Providers_ProviderID",
|
||||||
column: x => x.SecondID,
|
column: x => x.ProviderID,
|
||||||
principalTable: "Providers",
|
principalTable: "Providers",
|
||||||
principalColumn: "ID",
|
principalColumn: "ID",
|
||||||
onDelete: ReferentialAction.Cascade);
|
onDelete: ReferentialAction.Cascade);
|
||||||
@ -201,12 +227,9 @@ namespace Kyoo.SqLite.Migrations
|
|||||||
Path = table.Column<string>(type: "TEXT", nullable: true),
|
Path = table.Column<string>(type: "TEXT", nullable: true),
|
||||||
Overview = table.Column<string>(type: "TEXT", nullable: true),
|
Overview = table.Column<string>(type: "TEXT", nullable: true),
|
||||||
Status = table.Column<int>(type: "INTEGER", nullable: false),
|
Status = table.Column<int>(type: "INTEGER", nullable: false),
|
||||||
TrailerUrl = table.Column<string>(type: "TEXT", nullable: true),
|
|
||||||
StartAir = table.Column<DateTime>(type: "TEXT", nullable: true),
|
StartAir = table.Column<DateTime>(type: "TEXT", nullable: true),
|
||||||
EndAir = table.Column<DateTime>(type: "TEXT", nullable: true),
|
EndAir = table.Column<DateTime>(type: "TEXT", nullable: true),
|
||||||
Poster = table.Column<string>(type: "TEXT", nullable: true),
|
Images = table.Column<string>(type: "TEXT", nullable: true),
|
||||||
Logo = table.Column<string>(type: "TEXT", nullable: true),
|
|
||||||
Backdrop = table.Column<string>(type: "TEXT", nullable: true),
|
|
||||||
IsMovie = table.Column<bool>(type: "INTEGER", nullable: false),
|
IsMovie = table.Column<bool>(type: "INTEGER", nullable: false),
|
||||||
StudioID = table.Column<int>(type: "INTEGER", nullable: true)
|
StudioID = table.Column<int>(type: "INTEGER", nullable: true)
|
||||||
},
|
},
|
||||||
@ -222,134 +245,133 @@ namespace Kyoo.SqLite.Migrations
|
|||||||
});
|
});
|
||||||
|
|
||||||
migrationBuilder.CreateTable(
|
migrationBuilder.CreateTable(
|
||||||
name: "Link<Collection, Show>",
|
name: "StudioMetadataID",
|
||||||
columns: table => new
|
columns: table => new
|
||||||
{
|
{
|
||||||
FirstID = table.Column<int>(type: "INTEGER", nullable: false),
|
ResourceID = table.Column<int>(type: "INTEGER", nullable: false),
|
||||||
SecondID = table.Column<int>(type: "INTEGER", nullable: false)
|
ProviderID = table.Column<int>(type: "INTEGER", nullable: false),
|
||||||
},
|
|
||||||
constraints: table =>
|
|
||||||
{
|
|
||||||
table.PrimaryKey("PK_Link<Collection, Show>", x => new { x.FirstID, x.SecondID });
|
|
||||||
table.ForeignKey(
|
|
||||||
name: "FK_Link<Collection, Show>_Collections_FirstID",
|
|
||||||
column: x => x.FirstID,
|
|
||||||
principalTable: "Collections",
|
|
||||||
principalColumn: "ID",
|
|
||||||
onDelete: ReferentialAction.Cascade);
|
|
||||||
table.ForeignKey(
|
|
||||||
name: "FK_Link<Collection, Show>_Shows_SecondID",
|
|
||||||
column: x => x.SecondID,
|
|
||||||
principalTable: "Shows",
|
|
||||||
principalColumn: "ID",
|
|
||||||
onDelete: ReferentialAction.Cascade);
|
|
||||||
});
|
|
||||||
|
|
||||||
migrationBuilder.CreateTable(
|
|
||||||
name: "Link<Library, Show>",
|
|
||||||
columns: table => new
|
|
||||||
{
|
|
||||||
FirstID = table.Column<int>(type: "INTEGER", nullable: false),
|
|
||||||
SecondID = table.Column<int>(type: "INTEGER", nullable: false)
|
|
||||||
},
|
|
||||||
constraints: table =>
|
|
||||||
{
|
|
||||||
table.PrimaryKey("PK_Link<Library, Show>", x => new { x.FirstID, x.SecondID });
|
|
||||||
table.ForeignKey(
|
|
||||||
name: "FK_Link<Library, Show>_Libraries_FirstID",
|
|
||||||
column: x => x.FirstID,
|
|
||||||
principalTable: "Libraries",
|
|
||||||
principalColumn: "ID",
|
|
||||||
onDelete: ReferentialAction.Cascade);
|
|
||||||
table.ForeignKey(
|
|
||||||
name: "FK_Link<Library, Show>_Shows_SecondID",
|
|
||||||
column: x => x.SecondID,
|
|
||||||
principalTable: "Shows",
|
|
||||||
principalColumn: "ID",
|
|
||||||
onDelete: ReferentialAction.Cascade);
|
|
||||||
});
|
|
||||||
|
|
||||||
migrationBuilder.CreateTable(
|
|
||||||
name: "Link<Show, Genre>",
|
|
||||||
columns: table => new
|
|
||||||
{
|
|
||||||
FirstID = table.Column<int>(type: "INTEGER", nullable: false),
|
|
||||||
SecondID = table.Column<int>(type: "INTEGER", nullable: false)
|
|
||||||
},
|
|
||||||
constraints: table =>
|
|
||||||
{
|
|
||||||
table.PrimaryKey("PK_Link<Show, Genre>", x => new { x.FirstID, x.SecondID });
|
|
||||||
table.ForeignKey(
|
|
||||||
name: "FK_Link<Show, Genre>_Genres_SecondID",
|
|
||||||
column: x => x.SecondID,
|
|
||||||
principalTable: "Genres",
|
|
||||||
principalColumn: "ID",
|
|
||||||
onDelete: ReferentialAction.Cascade);
|
|
||||||
table.ForeignKey(
|
|
||||||
name: "FK_Link<Show, Genre>_Shows_FirstID",
|
|
||||||
column: x => x.FirstID,
|
|
||||||
principalTable: "Shows",
|
|
||||||
principalColumn: "ID",
|
|
||||||
onDelete: ReferentialAction.Cascade);
|
|
||||||
});
|
|
||||||
|
|
||||||
migrationBuilder.CreateTable(
|
|
||||||
name: "Link<User, Show>",
|
|
||||||
columns: table => new
|
|
||||||
{
|
|
||||||
FirstID = table.Column<int>(type: "INTEGER", nullable: false),
|
|
||||||
SecondID = table.Column<int>(type: "INTEGER", nullable: false)
|
|
||||||
},
|
|
||||||
constraints: table =>
|
|
||||||
{
|
|
||||||
table.PrimaryKey("PK_Link<User, Show>", x => new { x.FirstID, x.SecondID });
|
|
||||||
table.ForeignKey(
|
|
||||||
name: "FK_Link<User, Show>_Shows_SecondID",
|
|
||||||
column: x => x.SecondID,
|
|
||||||
principalTable: "Shows",
|
|
||||||
principalColumn: "ID",
|
|
||||||
onDelete: ReferentialAction.Cascade);
|
|
||||||
table.ForeignKey(
|
|
||||||
name: "FK_Link<User, Show>_Users_FirstID",
|
|
||||||
column: x => x.FirstID,
|
|
||||||
principalTable: "Users",
|
|
||||||
principalColumn: "ID",
|
|
||||||
onDelete: ReferentialAction.Cascade);
|
|
||||||
});
|
|
||||||
|
|
||||||
migrationBuilder.CreateTable(
|
|
||||||
name: "MetadataID<Show>",
|
|
||||||
columns: table => new
|
|
||||||
{
|
|
||||||
FirstID = table.Column<int>(type: "INTEGER", nullable: false),
|
|
||||||
SecondID = table.Column<int>(type: "INTEGER", nullable: false),
|
|
||||||
DataID = table.Column<string>(type: "TEXT", nullable: true),
|
DataID = table.Column<string>(type: "TEXT", nullable: true),
|
||||||
Link = table.Column<string>(type: "TEXT", nullable: true)
|
Link = table.Column<string>(type: "TEXT", nullable: true)
|
||||||
},
|
},
|
||||||
constraints: table =>
|
constraints: table =>
|
||||||
{
|
{
|
||||||
table.PrimaryKey("PK_MetadataID<Show>", x => new { x.FirstID, x.SecondID });
|
table.PrimaryKey("PK_StudioMetadataID", x => new { x.ResourceID, x.ProviderID });
|
||||||
table.ForeignKey(
|
table.ForeignKey(
|
||||||
name: "FK_MetadataID<Show>_Providers_SecondID",
|
name: "FK_StudioMetadataID_Providers_ProviderID",
|
||||||
column: x => x.SecondID,
|
column: x => x.ProviderID,
|
||||||
principalTable: "Providers",
|
principalTable: "Providers",
|
||||||
principalColumn: "ID",
|
principalColumn: "ID",
|
||||||
onDelete: ReferentialAction.Cascade);
|
onDelete: ReferentialAction.Cascade);
|
||||||
table.ForeignKey(
|
table.ForeignKey(
|
||||||
name: "FK_MetadataID<Show>_Shows_FirstID",
|
name: "FK_StudioMetadataID_Studios_ResourceID",
|
||||||
column: x => x.FirstID,
|
column: x => x.ResourceID,
|
||||||
|
principalTable: "Studios",
|
||||||
|
principalColumn: "ID",
|
||||||
|
onDelete: ReferentialAction.Cascade);
|
||||||
|
});
|
||||||
|
|
||||||
|
migrationBuilder.CreateTable(
|
||||||
|
name: "LinkCollectionShow",
|
||||||
|
columns: table => new
|
||||||
|
{
|
||||||
|
CollectionID = table.Column<int>(type: "INTEGER", nullable: false),
|
||||||
|
ShowID = table.Column<int>(type: "INTEGER", nullable: false)
|
||||||
|
},
|
||||||
|
constraints: table =>
|
||||||
|
{
|
||||||
|
table.PrimaryKey("PK_LinkCollectionShow", x => new { x.CollectionID, x.ShowID });
|
||||||
|
table.ForeignKey(
|
||||||
|
name: "FK_LinkCollectionShow_Collections_CollectionID",
|
||||||
|
column: x => x.CollectionID,
|
||||||
|
principalTable: "Collections",
|
||||||
|
principalColumn: "ID",
|
||||||
|
onDelete: ReferentialAction.Cascade);
|
||||||
|
table.ForeignKey(
|
||||||
|
name: "FK_LinkCollectionShow_Shows_ShowID",
|
||||||
|
column: x => x.ShowID,
|
||||||
principalTable: "Shows",
|
principalTable: "Shows",
|
||||||
principalColumn: "ID",
|
principalColumn: "ID",
|
||||||
onDelete: ReferentialAction.Cascade);
|
onDelete: ReferentialAction.Cascade);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
migrationBuilder.CreateTable(
|
||||||
|
name: "LinkLibraryShow",
|
||||||
|
columns: table => new
|
||||||
|
{
|
||||||
|
LibraryID = table.Column<int>(type: "INTEGER", nullable: false),
|
||||||
|
ShowID = table.Column<int>(type: "INTEGER", nullable: false)
|
||||||
|
},
|
||||||
|
constraints: table =>
|
||||||
|
{
|
||||||
|
table.PrimaryKey("PK_LinkLibraryShow", x => new { x.LibraryID, x.ShowID });
|
||||||
|
table.ForeignKey(
|
||||||
|
name: "FK_LinkLibraryShow_Libraries_LibraryID",
|
||||||
|
column: x => x.LibraryID,
|
||||||
|
principalTable: "Libraries",
|
||||||
|
principalColumn: "ID",
|
||||||
|
onDelete: ReferentialAction.Cascade);
|
||||||
|
table.ForeignKey(
|
||||||
|
name: "FK_LinkLibraryShow_Shows_ShowID",
|
||||||
|
column: x => x.ShowID,
|
||||||
|
principalTable: "Shows",
|
||||||
|
principalColumn: "ID",
|
||||||
|
onDelete: ReferentialAction.Cascade);
|
||||||
|
});
|
||||||
|
|
||||||
|
migrationBuilder.CreateTable(
|
||||||
|
name: "LinkShowGenre",
|
||||||
|
columns: table => new
|
||||||
|
{
|
||||||
|
GenreID = table.Column<int>(type: "INTEGER", nullable: false),
|
||||||
|
ShowID = table.Column<int>(type: "INTEGER", nullable: false)
|
||||||
|
},
|
||||||
|
constraints: table =>
|
||||||
|
{
|
||||||
|
table.PrimaryKey("PK_LinkShowGenre", x => new { x.GenreID, x.ShowID });
|
||||||
|
table.ForeignKey(
|
||||||
|
name: "FK_LinkShowGenre_Genres_GenreID",
|
||||||
|
column: x => x.GenreID,
|
||||||
|
principalTable: "Genres",
|
||||||
|
principalColumn: "ID",
|
||||||
|
onDelete: ReferentialAction.Cascade);
|
||||||
|
table.ForeignKey(
|
||||||
|
name: "FK_LinkShowGenre_Shows_ShowID",
|
||||||
|
column: x => x.ShowID,
|
||||||
|
principalTable: "Shows",
|
||||||
|
principalColumn: "ID",
|
||||||
|
onDelete: ReferentialAction.Cascade);
|
||||||
|
});
|
||||||
|
|
||||||
|
migrationBuilder.CreateTable(
|
||||||
|
name: "LinkUserShow",
|
||||||
|
columns: table => new
|
||||||
|
{
|
||||||
|
UsersID = table.Column<int>(type: "INTEGER", nullable: false),
|
||||||
|
WatchedID = table.Column<int>(type: "INTEGER", nullable: false)
|
||||||
|
},
|
||||||
|
constraints: table =>
|
||||||
|
{
|
||||||
|
table.PrimaryKey("PK_LinkUserShow", x => new { x.UsersID, x.WatchedID });
|
||||||
|
table.ForeignKey(
|
||||||
|
name: "FK_LinkUserShow_Shows_WatchedID",
|
||||||
|
column: x => x.WatchedID,
|
||||||
|
principalTable: "Shows",
|
||||||
|
principalColumn: "ID",
|
||||||
|
onDelete: ReferentialAction.Cascade);
|
||||||
|
table.ForeignKey(
|
||||||
|
name: "FK_LinkUserShow_Users_UsersID",
|
||||||
|
column: x => x.UsersID,
|
||||||
|
principalTable: "Users",
|
||||||
|
principalColumn: "ID",
|
||||||
|
onDelete: ReferentialAction.Cascade);
|
||||||
|
});
|
||||||
|
|
||||||
migrationBuilder.CreateTable(
|
migrationBuilder.CreateTable(
|
||||||
name: "PeopleRoles",
|
name: "PeopleRoles",
|
||||||
columns: table => new
|
columns: table => new
|
||||||
{
|
{
|
||||||
ID = table.Column<int>(type: "INTEGER", nullable: false)
|
ID = table.Column<int>(type: "INTEGER", nullable: false)
|
||||||
.Annotation("Sqlite:Autoincrement", true),
|
.Annotation("Sqlite:Autoincrement", true),
|
||||||
ForPeople = table.Column<bool>(type: "INTEGER", nullable: false),
|
|
||||||
PeopleID = table.Column<int>(type: "INTEGER", nullable: false),
|
PeopleID = table.Column<int>(type: "INTEGER", nullable: false),
|
||||||
ShowID = table.Column<int>(type: "INTEGER", nullable: false),
|
ShowID = table.Column<int>(type: "INTEGER", nullable: false),
|
||||||
Type = table.Column<string>(type: "TEXT", nullable: true),
|
Type = table.Column<string>(type: "TEXT", nullable: true),
|
||||||
@ -385,7 +407,7 @@ namespace Kyoo.SqLite.Migrations
|
|||||||
Overview = table.Column<string>(type: "TEXT", nullable: true),
|
Overview = table.Column<string>(type: "TEXT", nullable: true),
|
||||||
StartDate = table.Column<DateTime>(type: "TEXT", nullable: true),
|
StartDate = table.Column<DateTime>(type: "TEXT", nullable: true),
|
||||||
EndDate = table.Column<DateTime>(type: "TEXT", nullable: true),
|
EndDate = table.Column<DateTime>(type: "TEXT", nullable: true),
|
||||||
Poster = table.Column<string>(type: "TEXT", nullable: true)
|
Images = table.Column<string>(type: "TEXT", nullable: true)
|
||||||
},
|
},
|
||||||
constraints: table =>
|
constraints: table =>
|
||||||
{
|
{
|
||||||
@ -398,6 +420,32 @@ namespace Kyoo.SqLite.Migrations
|
|||||||
onDelete: ReferentialAction.Cascade);
|
onDelete: ReferentialAction.Cascade);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
migrationBuilder.CreateTable(
|
||||||
|
name: "ShowMetadataID",
|
||||||
|
columns: table => new
|
||||||
|
{
|
||||||
|
ResourceID = table.Column<int>(type: "INTEGER", nullable: false),
|
||||||
|
ProviderID = table.Column<int>(type: "INTEGER", nullable: false),
|
||||||
|
DataID = table.Column<string>(type: "TEXT", nullable: true),
|
||||||
|
Link = table.Column<string>(type: "TEXT", nullable: true)
|
||||||
|
},
|
||||||
|
constraints: table =>
|
||||||
|
{
|
||||||
|
table.PrimaryKey("PK_ShowMetadataID", x => new { x.ResourceID, x.ProviderID });
|
||||||
|
table.ForeignKey(
|
||||||
|
name: "FK_ShowMetadataID_Providers_ProviderID",
|
||||||
|
column: x => x.ProviderID,
|
||||||
|
principalTable: "Providers",
|
||||||
|
principalColumn: "ID",
|
||||||
|
onDelete: ReferentialAction.Cascade);
|
||||||
|
table.ForeignKey(
|
||||||
|
name: "FK_ShowMetadataID_Shows_ResourceID",
|
||||||
|
column: x => x.ResourceID,
|
||||||
|
principalTable: "Shows",
|
||||||
|
principalColumn: "ID",
|
||||||
|
onDelete: ReferentialAction.Cascade);
|
||||||
|
});
|
||||||
|
|
||||||
migrationBuilder.CreateTable(
|
migrationBuilder.CreateTable(
|
||||||
name: "Episodes",
|
name: "Episodes",
|
||||||
columns: table => new
|
columns: table => new
|
||||||
@ -411,7 +459,7 @@ namespace Kyoo.SqLite.Migrations
|
|||||||
EpisodeNumber = table.Column<int>(type: "INTEGER", nullable: true),
|
EpisodeNumber = table.Column<int>(type: "INTEGER", nullable: true),
|
||||||
AbsoluteNumber = table.Column<int>(type: "INTEGER", nullable: true),
|
AbsoluteNumber = table.Column<int>(type: "INTEGER", nullable: true),
|
||||||
Path = table.Column<string>(type: "TEXT", nullable: true),
|
Path = table.Column<string>(type: "TEXT", nullable: true),
|
||||||
Thumb = table.Column<string>(type: "TEXT", nullable: true),
|
Images = table.Column<string>(type: "TEXT", nullable: true),
|
||||||
Title = table.Column<string>(type: "TEXT", nullable: true),
|
Title = table.Column<string>(type: "TEXT", nullable: true),
|
||||||
Overview = table.Column<string>(type: "TEXT", nullable: true),
|
Overview = table.Column<string>(type: "TEXT", nullable: true),
|
||||||
ReleaseDate = table.Column<DateTime>(type: "TEXT", nullable: true)
|
ReleaseDate = table.Column<DateTime>(type: "TEXT", nullable: true)
|
||||||
@ -434,52 +482,52 @@ namespace Kyoo.SqLite.Migrations
|
|||||||
});
|
});
|
||||||
|
|
||||||
migrationBuilder.CreateTable(
|
migrationBuilder.CreateTable(
|
||||||
name: "MetadataID<Season>",
|
name: "SeasonMetadataID",
|
||||||
columns: table => new
|
columns: table => new
|
||||||
{
|
{
|
||||||
FirstID = table.Column<int>(type: "INTEGER", nullable: false),
|
ResourceID = table.Column<int>(type: "INTEGER", nullable: false),
|
||||||
SecondID = table.Column<int>(type: "INTEGER", nullable: false),
|
ProviderID = table.Column<int>(type: "INTEGER", nullable: false),
|
||||||
DataID = table.Column<string>(type: "TEXT", nullable: true),
|
DataID = table.Column<string>(type: "TEXT", nullable: true),
|
||||||
Link = table.Column<string>(type: "TEXT", nullable: true)
|
Link = table.Column<string>(type: "TEXT", nullable: true)
|
||||||
},
|
},
|
||||||
constraints: table =>
|
constraints: table =>
|
||||||
{
|
{
|
||||||
table.PrimaryKey("PK_MetadataID<Season>", x => new { x.FirstID, x.SecondID });
|
table.PrimaryKey("PK_SeasonMetadataID", x => new { x.ResourceID, x.ProviderID });
|
||||||
table.ForeignKey(
|
table.ForeignKey(
|
||||||
name: "FK_MetadataID<Season>_Providers_SecondID",
|
name: "FK_SeasonMetadataID_Providers_ProviderID",
|
||||||
column: x => x.SecondID,
|
column: x => x.ProviderID,
|
||||||
principalTable: "Providers",
|
principalTable: "Providers",
|
||||||
principalColumn: "ID",
|
principalColumn: "ID",
|
||||||
onDelete: ReferentialAction.Cascade);
|
onDelete: ReferentialAction.Cascade);
|
||||||
table.ForeignKey(
|
table.ForeignKey(
|
||||||
name: "FK_MetadataID<Season>_Seasons_FirstID",
|
name: "FK_SeasonMetadataID_Seasons_ResourceID",
|
||||||
column: x => x.FirstID,
|
column: x => x.ResourceID,
|
||||||
principalTable: "Seasons",
|
principalTable: "Seasons",
|
||||||
principalColumn: "ID",
|
principalColumn: "ID",
|
||||||
onDelete: ReferentialAction.Cascade);
|
onDelete: ReferentialAction.Cascade);
|
||||||
});
|
});
|
||||||
|
|
||||||
migrationBuilder.CreateTable(
|
migrationBuilder.CreateTable(
|
||||||
name: "MetadataID<Episode>",
|
name: "EpisodeMetadataID",
|
||||||
columns: table => new
|
columns: table => new
|
||||||
{
|
{
|
||||||
FirstID = table.Column<int>(type: "INTEGER", nullable: false),
|
ResourceID = table.Column<int>(type: "INTEGER", nullable: false),
|
||||||
SecondID = table.Column<int>(type: "INTEGER", nullable: false),
|
ProviderID = table.Column<int>(type: "INTEGER", nullable: false),
|
||||||
DataID = table.Column<string>(type: "TEXT", nullable: true),
|
DataID = table.Column<string>(type: "TEXT", nullable: true),
|
||||||
Link = table.Column<string>(type: "TEXT", nullable: true)
|
Link = table.Column<string>(type: "TEXT", nullable: true)
|
||||||
},
|
},
|
||||||
constraints: table =>
|
constraints: table =>
|
||||||
{
|
{
|
||||||
table.PrimaryKey("PK_MetadataID<Episode>", x => new { x.FirstID, x.SecondID });
|
table.PrimaryKey("PK_EpisodeMetadataID", x => new { x.ResourceID, x.ProviderID });
|
||||||
table.ForeignKey(
|
table.ForeignKey(
|
||||||
name: "FK_MetadataID<Episode>_Episodes_FirstID",
|
name: "FK_EpisodeMetadataID_Episodes_ResourceID",
|
||||||
column: x => x.FirstID,
|
column: x => x.ResourceID,
|
||||||
principalTable: "Episodes",
|
principalTable: "Episodes",
|
||||||
principalColumn: "ID",
|
principalColumn: "ID",
|
||||||
onDelete: ReferentialAction.Cascade);
|
onDelete: ReferentialAction.Cascade);
|
||||||
table.ForeignKey(
|
table.ForeignKey(
|
||||||
name: "FK_MetadataID<Episode>_Providers_SecondID",
|
name: "FK_EpisodeMetadataID_Providers_ProviderID",
|
||||||
column: x => x.SecondID,
|
column: x => x.ProviderID,
|
||||||
principalTable: "Providers",
|
principalTable: "Providers",
|
||||||
principalColumn: "ID",
|
principalColumn: "ID",
|
||||||
onDelete: ReferentialAction.Cascade);
|
onDelete: ReferentialAction.Cascade);
|
||||||
@ -518,33 +566,43 @@ namespace Kyoo.SqLite.Migrations
|
|||||||
name: "WatchedEpisodes",
|
name: "WatchedEpisodes",
|
||||||
columns: table => new
|
columns: table => new
|
||||||
{
|
{
|
||||||
FirstID = table.Column<int>(type: "INTEGER", nullable: false),
|
UserID = table.Column<int>(type: "INTEGER", nullable: false),
|
||||||
SecondID = table.Column<int>(type: "INTEGER", nullable: false),
|
EpisodeID = table.Column<int>(type: "INTEGER", nullable: false),
|
||||||
WatchedPercentage = table.Column<int>(type: "INTEGER", nullable: false)
|
WatchedPercentage = table.Column<int>(type: "INTEGER", nullable: false)
|
||||||
},
|
},
|
||||||
constraints: table =>
|
constraints: table =>
|
||||||
{
|
{
|
||||||
table.PrimaryKey("PK_WatchedEpisodes", x => new { x.FirstID, x.SecondID });
|
table.PrimaryKey("PK_WatchedEpisodes", x => new { x.UserID, x.EpisodeID });
|
||||||
table.ForeignKey(
|
table.ForeignKey(
|
||||||
name: "FK_WatchedEpisodes_Episodes_SecondID",
|
name: "FK_WatchedEpisodes_Episodes_EpisodeID",
|
||||||
column: x => x.SecondID,
|
column: x => x.EpisodeID,
|
||||||
principalTable: "Episodes",
|
principalTable: "Episodes",
|
||||||
principalColumn: "ID",
|
principalColumn: "ID",
|
||||||
onDelete: ReferentialAction.Cascade);
|
onDelete: ReferentialAction.Cascade);
|
||||||
table.ForeignKey(
|
table.ForeignKey(
|
||||||
name: "FK_WatchedEpisodes_Users_FirstID",
|
name: "FK_WatchedEpisodes_Users_UserID",
|
||||||
column: x => x.FirstID,
|
column: x => x.UserID,
|
||||||
principalTable: "Users",
|
principalTable: "Users",
|
||||||
principalColumn: "ID",
|
principalColumn: "ID",
|
||||||
onDelete: ReferentialAction.Cascade);
|
onDelete: ReferentialAction.Cascade);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
migrationBuilder.CreateIndex(
|
||||||
|
name: "IX_CollectionMetadataID_ProviderID",
|
||||||
|
table: "CollectionMetadataID",
|
||||||
|
column: "ProviderID");
|
||||||
|
|
||||||
migrationBuilder.CreateIndex(
|
migrationBuilder.CreateIndex(
|
||||||
name: "IX_Collections_Slug",
|
name: "IX_Collections_Slug",
|
||||||
table: "Collections",
|
table: "Collections",
|
||||||
column: "Slug",
|
column: "Slug",
|
||||||
unique: true);
|
unique: true);
|
||||||
|
|
||||||
|
migrationBuilder.CreateIndex(
|
||||||
|
name: "IX_EpisodeMetadataID_ProviderID",
|
||||||
|
table: "EpisodeMetadataID",
|
||||||
|
column: "ProviderID");
|
||||||
|
|
||||||
migrationBuilder.CreateIndex(
|
migrationBuilder.CreateIndex(
|
||||||
name: "IX_Episodes_SeasonID",
|
name: "IX_Episodes_SeasonID",
|
||||||
table: "Episodes",
|
table: "Episodes",
|
||||||
@ -575,54 +633,34 @@ namespace Kyoo.SqLite.Migrations
|
|||||||
unique: true);
|
unique: true);
|
||||||
|
|
||||||
migrationBuilder.CreateIndex(
|
migrationBuilder.CreateIndex(
|
||||||
name: "IX_Link<Collection, Show>_SecondID",
|
name: "IX_LinkCollectionShow_ShowID",
|
||||||
table: "Link<Collection, Show>",
|
table: "LinkCollectionShow",
|
||||||
column: "SecondID");
|
column: "ShowID");
|
||||||
|
|
||||||
migrationBuilder.CreateIndex(
|
migrationBuilder.CreateIndex(
|
||||||
name: "IX_Link<Library, Collection>_SecondID",
|
name: "IX_LinkLibraryCollection_LibraryID",
|
||||||
table: "Link<Library, Collection>",
|
table: "LinkLibraryCollection",
|
||||||
column: "SecondID");
|
column: "LibraryID");
|
||||||
|
|
||||||
migrationBuilder.CreateIndex(
|
migrationBuilder.CreateIndex(
|
||||||
name: "IX_Link<Library, Provider>_SecondID",
|
name: "IX_LinkLibraryProvider_ProviderID",
|
||||||
table: "Link<Library, Provider>",
|
table: "LinkLibraryProvider",
|
||||||
column: "SecondID");
|
column: "ProviderID");
|
||||||
|
|
||||||
migrationBuilder.CreateIndex(
|
migrationBuilder.CreateIndex(
|
||||||
name: "IX_Link<Library, Show>_SecondID",
|
name: "IX_LinkLibraryShow_ShowID",
|
||||||
table: "Link<Library, Show>",
|
table: "LinkLibraryShow",
|
||||||
column: "SecondID");
|
column: "ShowID");
|
||||||
|
|
||||||
migrationBuilder.CreateIndex(
|
migrationBuilder.CreateIndex(
|
||||||
name: "IX_Link<Show, Genre>_SecondID",
|
name: "IX_LinkShowGenre_ShowID",
|
||||||
table: "Link<Show, Genre>",
|
table: "LinkShowGenre",
|
||||||
column: "SecondID");
|
column: "ShowID");
|
||||||
|
|
||||||
migrationBuilder.CreateIndex(
|
migrationBuilder.CreateIndex(
|
||||||
name: "IX_Link<User, Show>_SecondID",
|
name: "IX_LinkUserShow_WatchedID",
|
||||||
table: "Link<User, Show>",
|
table: "LinkUserShow",
|
||||||
column: "SecondID");
|
column: "WatchedID");
|
||||||
|
|
||||||
migrationBuilder.CreateIndex(
|
|
||||||
name: "IX_MetadataID<Episode>_SecondID",
|
|
||||||
table: "MetadataID<Episode>",
|
|
||||||
column: "SecondID");
|
|
||||||
|
|
||||||
migrationBuilder.CreateIndex(
|
|
||||||
name: "IX_MetadataID<People>_SecondID",
|
|
||||||
table: "MetadataID<People>",
|
|
||||||
column: "SecondID");
|
|
||||||
|
|
||||||
migrationBuilder.CreateIndex(
|
|
||||||
name: "IX_MetadataID<Season>_SecondID",
|
|
||||||
table: "MetadataID<Season>",
|
|
||||||
column: "SecondID");
|
|
||||||
|
|
||||||
migrationBuilder.CreateIndex(
|
|
||||||
name: "IX_MetadataID<Show>_SecondID",
|
|
||||||
table: "MetadataID<Show>",
|
|
||||||
column: "SecondID");
|
|
||||||
|
|
||||||
migrationBuilder.CreateIndex(
|
migrationBuilder.CreateIndex(
|
||||||
name: "IX_People_Slug",
|
name: "IX_People_Slug",
|
||||||
@ -630,6 +668,11 @@ namespace Kyoo.SqLite.Migrations
|
|||||||
column: "Slug",
|
column: "Slug",
|
||||||
unique: true);
|
unique: true);
|
||||||
|
|
||||||
|
migrationBuilder.CreateIndex(
|
||||||
|
name: "IX_PeopleMetadataID_ProviderID",
|
||||||
|
table: "PeopleMetadataID",
|
||||||
|
column: "ProviderID");
|
||||||
|
|
||||||
migrationBuilder.CreateIndex(
|
migrationBuilder.CreateIndex(
|
||||||
name: "IX_PeopleRoles_PeopleID",
|
name: "IX_PeopleRoles_PeopleID",
|
||||||
table: "PeopleRoles",
|
table: "PeopleRoles",
|
||||||
@ -646,6 +689,11 @@ namespace Kyoo.SqLite.Migrations
|
|||||||
column: "Slug",
|
column: "Slug",
|
||||||
unique: true);
|
unique: true);
|
||||||
|
|
||||||
|
migrationBuilder.CreateIndex(
|
||||||
|
name: "IX_SeasonMetadataID_ProviderID",
|
||||||
|
table: "SeasonMetadataID",
|
||||||
|
column: "ProviderID");
|
||||||
|
|
||||||
migrationBuilder.CreateIndex(
|
migrationBuilder.CreateIndex(
|
||||||
name: "IX_Seasons_ShowID_SeasonNumber",
|
name: "IX_Seasons_ShowID_SeasonNumber",
|
||||||
table: "Seasons",
|
table: "Seasons",
|
||||||
@ -658,6 +706,11 @@ namespace Kyoo.SqLite.Migrations
|
|||||||
column: "Slug",
|
column: "Slug",
|
||||||
unique: true);
|
unique: true);
|
||||||
|
|
||||||
|
migrationBuilder.CreateIndex(
|
||||||
|
name: "IX_ShowMetadataID_ProviderID",
|
||||||
|
table: "ShowMetadataID",
|
||||||
|
column: "ProviderID");
|
||||||
|
|
||||||
migrationBuilder.CreateIndex(
|
migrationBuilder.CreateIndex(
|
||||||
name: "IX_Shows_Slug",
|
name: "IX_Shows_Slug",
|
||||||
table: "Shows",
|
table: "Shows",
|
||||||
@ -669,6 +722,11 @@ namespace Kyoo.SqLite.Migrations
|
|||||||
table: "Shows",
|
table: "Shows",
|
||||||
column: "StudioID");
|
column: "StudioID");
|
||||||
|
|
||||||
|
migrationBuilder.CreateIndex(
|
||||||
|
name: "IX_StudioMetadataID_ProviderID",
|
||||||
|
table: "StudioMetadataID",
|
||||||
|
column: "ProviderID");
|
||||||
|
|
||||||
migrationBuilder.CreateIndex(
|
migrationBuilder.CreateIndex(
|
||||||
name: "IX_Studios_Slug",
|
name: "IX_Studios_Slug",
|
||||||
table: "Studios",
|
table: "Studios",
|
||||||
@ -694,46 +752,52 @@ namespace Kyoo.SqLite.Migrations
|
|||||||
unique: true);
|
unique: true);
|
||||||
|
|
||||||
migrationBuilder.CreateIndex(
|
migrationBuilder.CreateIndex(
|
||||||
name: "IX_WatchedEpisodes_SecondID",
|
name: "IX_WatchedEpisodes_EpisodeID",
|
||||||
table: "WatchedEpisodes",
|
table: "WatchedEpisodes",
|
||||||
column: "SecondID");
|
column: "EpisodeID");
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override void Down(MigrationBuilder migrationBuilder)
|
protected override void Down(MigrationBuilder migrationBuilder)
|
||||||
{
|
{
|
||||||
migrationBuilder.DropTable(
|
migrationBuilder.DropTable(
|
||||||
name: "Link<Collection, Show>");
|
name: "CollectionMetadataID");
|
||||||
|
|
||||||
migrationBuilder.DropTable(
|
migrationBuilder.DropTable(
|
||||||
name: "Link<Library, Collection>");
|
name: "EpisodeMetadataID");
|
||||||
|
|
||||||
migrationBuilder.DropTable(
|
migrationBuilder.DropTable(
|
||||||
name: "Link<Library, Provider>");
|
name: "LinkCollectionShow");
|
||||||
|
|
||||||
migrationBuilder.DropTable(
|
migrationBuilder.DropTable(
|
||||||
name: "Link<Library, Show>");
|
name: "LinkLibraryCollection");
|
||||||
|
|
||||||
migrationBuilder.DropTable(
|
migrationBuilder.DropTable(
|
||||||
name: "Link<Show, Genre>");
|
name: "LinkLibraryProvider");
|
||||||
|
|
||||||
migrationBuilder.DropTable(
|
migrationBuilder.DropTable(
|
||||||
name: "Link<User, Show>");
|
name: "LinkLibraryShow");
|
||||||
|
|
||||||
migrationBuilder.DropTable(
|
migrationBuilder.DropTable(
|
||||||
name: "MetadataID<Episode>");
|
name: "LinkShowGenre");
|
||||||
|
|
||||||
migrationBuilder.DropTable(
|
migrationBuilder.DropTable(
|
||||||
name: "MetadataID<People>");
|
name: "LinkUserShow");
|
||||||
|
|
||||||
migrationBuilder.DropTable(
|
migrationBuilder.DropTable(
|
||||||
name: "MetadataID<Season>");
|
name: "PeopleMetadataID");
|
||||||
|
|
||||||
migrationBuilder.DropTable(
|
|
||||||
name: "MetadataID<Show>");
|
|
||||||
|
|
||||||
migrationBuilder.DropTable(
|
migrationBuilder.DropTable(
|
||||||
name: "PeopleRoles");
|
name: "PeopleRoles");
|
||||||
|
|
||||||
|
migrationBuilder.DropTable(
|
||||||
|
name: "SeasonMetadataID");
|
||||||
|
|
||||||
|
migrationBuilder.DropTable(
|
||||||
|
name: "ShowMetadataID");
|
||||||
|
|
||||||
|
migrationBuilder.DropTable(
|
||||||
|
name: "StudioMetadataID");
|
||||||
|
|
||||||
migrationBuilder.DropTable(
|
migrationBuilder.DropTable(
|
||||||
name: "Tracks");
|
name: "Tracks");
|
||||||
|
|
||||||
@ -750,10 +814,10 @@ namespace Kyoo.SqLite.Migrations
|
|||||||
name: "Genres");
|
name: "Genres");
|
||||||
|
|
||||||
migrationBuilder.DropTable(
|
migrationBuilder.DropTable(
|
||||||
name: "Providers");
|
name: "People");
|
||||||
|
|
||||||
migrationBuilder.DropTable(
|
migrationBuilder.DropTable(
|
||||||
name: "People");
|
name: "Providers");
|
||||||
|
|
||||||
migrationBuilder.DropTable(
|
migrationBuilder.DropTable(
|
||||||
name: "Episodes");
|
name: "Episodes");
|
File diff suppressed because it is too large
Load Diff
@ -154,19 +154,19 @@ namespace Kyoo.SqLite.Migrations
|
|||||||
// language=SQLite
|
// language=SQLite
|
||||||
migrationBuilder.Sql(@"
|
migrationBuilder.Sql(@"
|
||||||
CREATE VIEW LibraryItems AS
|
CREATE VIEW LibraryItems AS
|
||||||
SELECT s.ID, s.Slug, s.Title, s.Overview, s.Status, s.StartAir, s.EndAir, s.Poster, CASE
|
SELECT s.ID, s.Slug, s.Title, s.Overview, s.Status, s.StartAir, s.EndAir, s.Images, CASE
|
||||||
WHEN s.IsMovie THEN 1
|
WHEN s.IsMovie THEN 1
|
||||||
ELSE 0
|
ELSE 0
|
||||||
END AS Type
|
END AS Type
|
||||||
FROM Shows AS s
|
FROM Shows AS s
|
||||||
WHERE NOT (EXISTS (
|
WHERE NOT (EXISTS (
|
||||||
SELECT 1
|
SELECT 1
|
||||||
FROM 'Link<Collection, Show>' AS l
|
FROM LinkCollectionShow AS l
|
||||||
INNER JOIN Collections AS c ON l.FirstID = c.ID
|
INNER JOIN Collections AS c ON l.CollectionID = c.ID
|
||||||
WHERE s.ID = l.SecondID))
|
WHERE s.ID = l.ShowID))
|
||||||
UNION ALL
|
UNION ALL
|
||||||
SELECT -c0.ID, c0.Slug, c0.Name AS Title, c0.Overview, 3 AS Status,
|
SELECT -c0.ID, c0.Slug, c0.Name AS Title, c0.Overview, 0 AS Status,
|
||||||
NULL AS StartAir, NULL AS EndAir, c0.Poster, 2 AS Type
|
NULL AS StartAir, NULL AS EndAir, c0.Images, 2 AS Type
|
||||||
FROM collections AS c0");
|
FROM collections AS c0");
|
||||||
}
|
}
|
||||||
|
|
File diff suppressed because it is too large
Load Diff
@ -102,12 +102,41 @@ namespace Kyoo.SqLite
|
|||||||
.Property(x => x.Type)
|
.Property(x => x.Type)
|
||||||
.HasConversion<int>();
|
.HasConversion<int>();
|
||||||
|
|
||||||
ValueConverter<Dictionary<string, string>, string> jsonConvertor = new(
|
ValueConverter<Dictionary<string, string>, string> extraDataConvertor = new(
|
||||||
x => JsonConvert.SerializeObject(x),
|
x => JsonConvert.SerializeObject(x),
|
||||||
x => JsonConvert.DeserializeObject<Dictionary<string, string>>(x));
|
x => JsonConvert.DeserializeObject<Dictionary<string, string>>(x));
|
||||||
modelBuilder.Entity<User>()
|
modelBuilder.Entity<User>()
|
||||||
.Property(x => x.ExtraData)
|
.Property(x => x.ExtraData)
|
||||||
|
.HasConversion(extraDataConvertor);
|
||||||
|
|
||||||
|
ValueConverter<Dictionary<int, string>, string> jsonConvertor = new(
|
||||||
|
x => JsonConvert.SerializeObject(x),
|
||||||
|
x => JsonConvert.DeserializeObject<Dictionary<int, string>>(x));
|
||||||
|
modelBuilder.Entity<LibraryItem>()
|
||||||
|
.Property(x => x.Images)
|
||||||
.HasConversion(jsonConvertor);
|
.HasConversion(jsonConvertor);
|
||||||
|
modelBuilder.Entity<Collection>()
|
||||||
|
.Property(x => x.Images)
|
||||||
|
.HasConversion(jsonConvertor);
|
||||||
|
modelBuilder.Entity<Show>()
|
||||||
|
.Property(x => x.Images)
|
||||||
|
.HasConversion(jsonConvertor);
|
||||||
|
modelBuilder.Entity<Season>()
|
||||||
|
.Property(x => x.Images)
|
||||||
|
.HasConversion(jsonConvertor);
|
||||||
|
modelBuilder.Entity<Episode>()
|
||||||
|
.Property(x => x.Images)
|
||||||
|
.HasConversion(jsonConvertor);
|
||||||
|
modelBuilder.Entity<People>()
|
||||||
|
.Property(x => x.Images)
|
||||||
|
.HasConversion(jsonConvertor);
|
||||||
|
modelBuilder.Entity<Provider>()
|
||||||
|
.Property(x => x.Images)
|
||||||
|
.HasConversion(jsonConvertor);
|
||||||
|
modelBuilder.Entity<User>()
|
||||||
|
.Property(x => x.Images)
|
||||||
|
.HasConversion(jsonConvertor);
|
||||||
|
|
||||||
|
|
||||||
modelBuilder.Entity<LibraryItem>()
|
modelBuilder.Entity<LibraryItem>()
|
||||||
.ToView("LibraryItems")
|
.ToView("LibraryItems")
|
||||||
@ -115,6 +144,24 @@ namespace Kyoo.SqLite
|
|||||||
base.OnModelCreating(modelBuilder);
|
base.OnModelCreating(modelBuilder);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
protected override string MetadataName<T>()
|
||||||
|
{
|
||||||
|
return typeof(T).Name + nameof(MetadataID);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
protected override string LinkName<T, T2>()
|
||||||
|
{
|
||||||
|
return "Link" + typeof(T).Name + typeof(T2).Name;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
protected override string LinkNameFk<T>()
|
||||||
|
{
|
||||||
|
return typeof(T).Name + "ID";
|
||||||
|
}
|
||||||
|
|
||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
protected override bool IsDuplicateException(Exception ex)
|
protected override bool IsDuplicateException(Exception ex)
|
||||||
{
|
{
|
||||||
|
@ -66,7 +66,7 @@ namespace Kyoo.SqLite
|
|||||||
x.UseSqlite(_configuration.GetDatabaseConnection("sqlite"));
|
x.UseSqlite(_configuration.GetDatabaseConnection("sqlite"));
|
||||||
if (_environment.IsDevelopment())
|
if (_environment.IsDevelopment())
|
||||||
x.EnableDetailedErrors().EnableSensitiveDataLogging();
|
x.EnableDetailedErrors().EnableSensitiveDataLogging();
|
||||||
});
|
}, ServiceLifetime.Transient);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
|
@ -1,67 +0,0 @@
|
|||||||
using System;
|
|
||||||
using System.Threading.Tasks;
|
|
||||||
using Kyoo.Controllers;
|
|
||||||
using Xunit.Abstractions;
|
|
||||||
|
|
||||||
namespace Kyoo.Tests
|
|
||||||
{
|
|
||||||
public class RepositoryActivator : IDisposable, IAsyncDisposable
|
|
||||||
{
|
|
||||||
public TestContext Context { get; }
|
|
||||||
public ILibraryManager LibraryManager { get; }
|
|
||||||
|
|
||||||
|
|
||||||
private readonly DatabaseContext _database;
|
|
||||||
|
|
||||||
public RepositoryActivator(ITestOutputHelper output, PostgresFixture postgres = null)
|
|
||||||
{
|
|
||||||
Context = postgres == null
|
|
||||||
? new SqLiteTestContext(output)
|
|
||||||
: new PostgresTestContext(postgres, output);
|
|
||||||
_database = Context.New();
|
|
||||||
|
|
||||||
ProviderRepository provider = new(_database);
|
|
||||||
LibraryRepository library = new(_database, provider);
|
|
||||||
CollectionRepository collection = new(_database);
|
|
||||||
GenreRepository genre = new(_database);
|
|
||||||
StudioRepository studio = new(_database);
|
|
||||||
PeopleRepository people = new(_database, provider,
|
|
||||||
new Lazy<IShowRepository>(() => LibraryManager.ShowRepository));
|
|
||||||
ShowRepository show = new(_database, studio, people, genre, provider);
|
|
||||||
SeasonRepository season = new(_database, provider);
|
|
||||||
LibraryItemRepository libraryItem = new(_database,
|
|
||||||
new Lazy<ILibraryRepository>(() => LibraryManager.LibraryRepository));
|
|
||||||
TrackRepository track = new(_database);
|
|
||||||
EpisodeRepository episode = new(_database, provider, track);
|
|
||||||
UserRepository user = new(_database);
|
|
||||||
|
|
||||||
LibraryManager = new LibraryManager(new IBaseRepository[] {
|
|
||||||
provider,
|
|
||||||
library,
|
|
||||||
libraryItem,
|
|
||||||
collection,
|
|
||||||
show,
|
|
||||||
season,
|
|
||||||
episode,
|
|
||||||
track,
|
|
||||||
people,
|
|
||||||
studio,
|
|
||||||
genre,
|
|
||||||
user
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
public void Dispose()
|
|
||||||
{
|
|
||||||
_database.Dispose();
|
|
||||||
Context.Dispose();
|
|
||||||
GC.SuppressFinalize(this);
|
|
||||||
}
|
|
||||||
|
|
||||||
public async ValueTask DisposeAsync()
|
|
||||||
{
|
|
||||||
await _database.DisposeAsync();
|
|
||||||
await Context.DisposeAsync();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,37 +0,0 @@
|
|||||||
using Kyoo.Controllers;
|
|
||||||
using Kyoo.Models;
|
|
||||||
using Xunit;
|
|
||||||
using Xunit.Abstractions;
|
|
||||||
|
|
||||||
namespace Kyoo.Tests.Database
|
|
||||||
{
|
|
||||||
namespace SqLite
|
|
||||||
{
|
|
||||||
public class CollectionTests : ACollectionTests
|
|
||||||
{
|
|
||||||
public CollectionTests(ITestOutputHelper output)
|
|
||||||
: base(new RepositoryActivator(output)) { }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
namespace PostgreSQL
|
|
||||||
{
|
|
||||||
[Collection(nameof(Postgresql))]
|
|
||||||
public class CollectionTests : ACollectionTests
|
|
||||||
{
|
|
||||||
public CollectionTests(PostgresFixture postgres, ITestOutputHelper output)
|
|
||||||
: base(new RepositoryActivator(output, postgres)) { }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public abstract class ACollectionTests : RepositoryTests<Collection>
|
|
||||||
{
|
|
||||||
private readonly ICollectionRepository _repository;
|
|
||||||
|
|
||||||
protected ACollectionTests(RepositoryActivator repositories)
|
|
||||||
: base(repositories)
|
|
||||||
{
|
|
||||||
_repository = Repositories.LibraryManager.CollectionRepository;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,51 +0,0 @@
|
|||||||
using System.Linq;
|
|
||||||
using System.Threading.Tasks;
|
|
||||||
using Kyoo.Controllers;
|
|
||||||
using Kyoo.Models;
|
|
||||||
using Xunit;
|
|
||||||
using Xunit.Abstractions;
|
|
||||||
|
|
||||||
namespace Kyoo.Tests.Database
|
|
||||||
{
|
|
||||||
namespace SqLite
|
|
||||||
{
|
|
||||||
public class LibraryTests : ALibraryTests
|
|
||||||
{
|
|
||||||
public LibraryTests(ITestOutputHelper output)
|
|
||||||
: base(new RepositoryActivator(output)) { }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
namespace PostgreSQL
|
|
||||||
{
|
|
||||||
[Collection(nameof(Postgresql))]
|
|
||||||
public class LibraryTests : ALibraryTests
|
|
||||||
{
|
|
||||||
public LibraryTests(PostgresFixture postgres, ITestOutputHelper output)
|
|
||||||
: base(new RepositoryActivator(output, postgres)) { }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public abstract class ALibraryTests : RepositoryTests<Library>
|
|
||||||
{
|
|
||||||
private readonly ILibraryRepository _repository;
|
|
||||||
|
|
||||||
protected ALibraryTests(RepositoryActivator repositories)
|
|
||||||
: base(repositories)
|
|
||||||
{
|
|
||||||
_repository = Repositories.LibraryManager.LibraryRepository;
|
|
||||||
}
|
|
||||||
|
|
||||||
[Fact]
|
|
||||||
public async Task CreateWithProvider()
|
|
||||||
{
|
|
||||||
Library library = TestSample.GetNew<Library>();
|
|
||||||
library.Providers = new[] { TestSample.Get<Provider>() };
|
|
||||||
await _repository.Create(library);
|
|
||||||
Library retrieved = await _repository.Get(2);
|
|
||||||
await Repositories.LibraryManager.Load(retrieved, x => x.Providers);
|
|
||||||
Assert.Equal(1, retrieved.Providers.Count);
|
|
||||||
Assert.Equal(TestSample.Get<Provider>().Slug, retrieved.Providers.First().Slug);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,37 +0,0 @@
|
|||||||
using Kyoo.Controllers;
|
|
||||||
using Kyoo.Models;
|
|
||||||
using Xunit;
|
|
||||||
using Xunit.Abstractions;
|
|
||||||
|
|
||||||
namespace Kyoo.Tests.Database
|
|
||||||
{
|
|
||||||
namespace SqLite
|
|
||||||
{
|
|
||||||
public class PeopleTests : APeopleTests
|
|
||||||
{
|
|
||||||
public PeopleTests(ITestOutputHelper output)
|
|
||||||
: base(new RepositoryActivator(output)) { }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
namespace PostgreSQL
|
|
||||||
{
|
|
||||||
[Collection(nameof(Postgresql))]
|
|
||||||
public class PeopleTests : APeopleTests
|
|
||||||
{
|
|
||||||
public PeopleTests(PostgresFixture postgres, ITestOutputHelper output)
|
|
||||||
: base(new RepositoryActivator(output, postgres)) { }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public abstract class APeopleTests : RepositoryTests<People>
|
|
||||||
{
|
|
||||||
private readonly IPeopleRepository _repository;
|
|
||||||
|
|
||||||
protected APeopleTests(RepositoryActivator repositories)
|
|
||||||
: base(repositories)
|
|
||||||
{
|
|
||||||
_repository = Repositories.LibraryManager.PeopleRepository;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,79 +0,0 @@
|
|||||||
using System.Threading.Tasks;
|
|
||||||
using Kyoo.Controllers;
|
|
||||||
using Kyoo.Models;
|
|
||||||
using Xunit;
|
|
||||||
using Xunit.Abstractions;
|
|
||||||
|
|
||||||
namespace Kyoo.Tests.Database
|
|
||||||
{
|
|
||||||
namespace SqLite
|
|
||||||
{
|
|
||||||
public class SeasonTests : ASeasonTests
|
|
||||||
{
|
|
||||||
public SeasonTests(ITestOutputHelper output)
|
|
||||||
: base(new RepositoryActivator(output)) { }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
namespace PostgreSQL
|
|
||||||
{
|
|
||||||
[Collection(nameof(Postgresql))]
|
|
||||||
public class SeasonTests : ASeasonTests
|
|
||||||
{
|
|
||||||
public SeasonTests(PostgresFixture postgres, ITestOutputHelper output)
|
|
||||||
: base(new RepositoryActivator(output, postgres)) { }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public abstract class ASeasonTests : RepositoryTests<Season>
|
|
||||||
{
|
|
||||||
private readonly ISeasonRepository _repository;
|
|
||||||
|
|
||||||
protected ASeasonTests(RepositoryActivator repositories)
|
|
||||||
: base(repositories)
|
|
||||||
{
|
|
||||||
_repository = Repositories.LibraryManager.SeasonRepository;
|
|
||||||
}
|
|
||||||
|
|
||||||
[Fact]
|
|
||||||
public async Task SlugEditTest()
|
|
||||||
{
|
|
||||||
Season season = await _repository.Get(1);
|
|
||||||
Assert.Equal("anohana-s1", season.Slug);
|
|
||||||
Show show = new()
|
|
||||||
{
|
|
||||||
ID = season.ShowID,
|
|
||||||
Slug = "new-slug"
|
|
||||||
};
|
|
||||||
await Repositories.LibraryManager.ShowRepository.Edit(show, false);
|
|
||||||
season = await _repository.Get(1);
|
|
||||||
Assert.Equal("new-slug-s1", season.Slug);
|
|
||||||
}
|
|
||||||
|
|
||||||
[Fact]
|
|
||||||
public async Task SeasonNumberEditTest()
|
|
||||||
{
|
|
||||||
Season season = await _repository.Get(1);
|
|
||||||
Assert.Equal("anohana-s1", season.Slug);
|
|
||||||
await _repository.Edit(new Season
|
|
||||||
{
|
|
||||||
ID = 1,
|
|
||||||
SeasonNumber = 2
|
|
||||||
}, false);
|
|
||||||
season = await _repository.Get(1);
|
|
||||||
Assert.Equal("anohana-s2", season.Slug);
|
|
||||||
}
|
|
||||||
|
|
||||||
[Fact]
|
|
||||||
public async Task SeasonCreationSlugTest()
|
|
||||||
{
|
|
||||||
Season season = await _repository.Create(new Season
|
|
||||||
{
|
|
||||||
ShowID = TestSample.Get<Show>().ID,
|
|
||||||
SeasonNumber = 2
|
|
||||||
});
|
|
||||||
Assert.Equal($"{TestSample.Get<Show>().Slug}-s2", season.Slug);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,212 +0,0 @@
|
|||||||
using System;
|
|
||||||
using System.Diagnostics.CodeAnalysis;
|
|
||||||
using System.Linq;
|
|
||||||
using Kyoo.Models;
|
|
||||||
using Kyoo.Models.Attributes;
|
|
||||||
using Xunit;
|
|
||||||
|
|
||||||
namespace Kyoo.Tests.Utility
|
|
||||||
{
|
|
||||||
public class MergerTests
|
|
||||||
{
|
|
||||||
[Fact]
|
|
||||||
public void NullifyTest()
|
|
||||||
{
|
|
||||||
Genre genre = new("test")
|
|
||||||
{
|
|
||||||
ID = 5
|
|
||||||
};
|
|
||||||
Merger.Nullify(genre);
|
|
||||||
Assert.Equal(0, genre.ID);
|
|
||||||
Assert.Null(genre.Name);
|
|
||||||
Assert.Null(genre.Slug);
|
|
||||||
}
|
|
||||||
|
|
||||||
[Fact]
|
|
||||||
public void MergeTest()
|
|
||||||
{
|
|
||||||
Genre genre = new()
|
|
||||||
{
|
|
||||||
ID = 5
|
|
||||||
};
|
|
||||||
Genre genre2 = new()
|
|
||||||
{
|
|
||||||
Name = "test"
|
|
||||||
};
|
|
||||||
Genre ret = Merger.Merge(genre, genre2);
|
|
||||||
Assert.True(ReferenceEquals(genre, ret));
|
|
||||||
Assert.Equal(5, ret.ID);
|
|
||||||
Assert.Equal("test", genre.Name);
|
|
||||||
Assert.Null(genre.Slug);
|
|
||||||
}
|
|
||||||
|
|
||||||
[Fact]
|
|
||||||
[SuppressMessage("ReSharper", "ExpressionIsAlwaysNull")]
|
|
||||||
public void MergeNullTests()
|
|
||||||
{
|
|
||||||
Genre genre = new()
|
|
||||||
{
|
|
||||||
ID = 5
|
|
||||||
};
|
|
||||||
Assert.True(ReferenceEquals(genre, Merger.Merge(genre, null)));
|
|
||||||
Assert.True(ReferenceEquals(genre, Merger.Merge(null, genre)));
|
|
||||||
Assert.Null(Merger.Merge<Genre>(null, null));
|
|
||||||
}
|
|
||||||
|
|
||||||
private class TestIOnMerge : IOnMerge
|
|
||||||
{
|
|
||||||
public void OnMerge(object other)
|
|
||||||
{
|
|
||||||
Exception exception = new();
|
|
||||||
exception.Data[0] = other;
|
|
||||||
throw exception;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
[Fact]
|
|
||||||
public void OnMergeTest()
|
|
||||||
{
|
|
||||||
TestIOnMerge test = new();
|
|
||||||
TestIOnMerge test2 = new();
|
|
||||||
Assert.Throws<Exception>(() => Merger.Merge(test, test2));
|
|
||||||
try
|
|
||||||
{
|
|
||||||
Merger.Merge(test, test2);
|
|
||||||
}
|
|
||||||
catch (Exception ex)
|
|
||||||
{
|
|
||||||
Assert.True(ReferenceEquals(test2, ex.Data[0]));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private class Test
|
|
||||||
{
|
|
||||||
public int ID { get; set; }
|
|
||||||
|
|
||||||
public int[] Numbers { get; set; }
|
|
||||||
}
|
|
||||||
|
|
||||||
[Fact]
|
|
||||||
public void GlobalMergeListTest()
|
|
||||||
{
|
|
||||||
Test test = new()
|
|
||||||
{
|
|
||||||
ID = 5,
|
|
||||||
Numbers = new [] { 1 }
|
|
||||||
};
|
|
||||||
Test test2 = new()
|
|
||||||
{
|
|
||||||
Numbers = new [] { 3 }
|
|
||||||
};
|
|
||||||
Test ret = Merger.Merge(test, test2);
|
|
||||||
Assert.True(ReferenceEquals(test, ret));
|
|
||||||
Assert.Equal(5, ret.ID);
|
|
||||||
|
|
||||||
Assert.Equal(2, ret.Numbers.Length);
|
|
||||||
Assert.Equal(1, ret.Numbers[0]);
|
|
||||||
Assert.Equal(3, ret.Numbers[1]);
|
|
||||||
}
|
|
||||||
|
|
||||||
[Fact]
|
|
||||||
public void GlobalMergeListDuplicatesTest()
|
|
||||||
{
|
|
||||||
Test test = new()
|
|
||||||
{
|
|
||||||
ID = 5,
|
|
||||||
Numbers = new [] { 1 }
|
|
||||||
};
|
|
||||||
Test test2 = new()
|
|
||||||
{
|
|
||||||
Numbers = new []
|
|
||||||
{
|
|
||||||
1,
|
|
||||||
3,
|
|
||||||
3
|
|
||||||
}
|
|
||||||
};
|
|
||||||
Test ret = Merger.Merge(test, test2);
|
|
||||||
Assert.True(ReferenceEquals(test, ret));
|
|
||||||
Assert.Equal(5, ret.ID);
|
|
||||||
|
|
||||||
Assert.Equal(4, ret.Numbers.Length);
|
|
||||||
Assert.Equal(1, ret.Numbers[0]);
|
|
||||||
Assert.Equal(1, ret.Numbers[1]);
|
|
||||||
Assert.Equal(3, ret.Numbers[2]);
|
|
||||||
Assert.Equal(3, ret.Numbers[3]);
|
|
||||||
}
|
|
||||||
|
|
||||||
[Fact]
|
|
||||||
public void GlobalMergeListDuplicatesResourcesTest()
|
|
||||||
{
|
|
||||||
Show test = new()
|
|
||||||
{
|
|
||||||
ID = 5,
|
|
||||||
Genres = new [] { new Genre("test") }
|
|
||||||
};
|
|
||||||
Show test2 = new()
|
|
||||||
{
|
|
||||||
Genres = new []
|
|
||||||
{
|
|
||||||
new Genre("test"),
|
|
||||||
new Genre("test2")
|
|
||||||
}
|
|
||||||
};
|
|
||||||
Show ret = Merger.Merge(test, test2);
|
|
||||||
Assert.True(ReferenceEquals(test, ret));
|
|
||||||
Assert.Equal(5, ret.ID);
|
|
||||||
|
|
||||||
Assert.Equal(2, ret.Genres.Count);
|
|
||||||
Assert.Equal("test", ret.Genres.ToArray()[0].Slug);
|
|
||||||
Assert.Equal("test2", ret.Genres.ToArray()[1].Slug);
|
|
||||||
}
|
|
||||||
|
|
||||||
[Fact]
|
|
||||||
public void MergeListTest()
|
|
||||||
{
|
|
||||||
int[] first = { 1 };
|
|
||||||
int[] second = {
|
|
||||||
3,
|
|
||||||
3
|
|
||||||
};
|
|
||||||
int[] ret = Merger.MergeLists(first, second);
|
|
||||||
|
|
||||||
Assert.Equal(3, ret.Length);
|
|
||||||
Assert.Equal(1, ret[0]);
|
|
||||||
Assert.Equal(3, ret[1]);
|
|
||||||
Assert.Equal(3, ret[2]);
|
|
||||||
}
|
|
||||||
|
|
||||||
[Fact]
|
|
||||||
public void MergeListDuplicateTest()
|
|
||||||
{
|
|
||||||
int[] first = { 1 };
|
|
||||||
int[] second = {
|
|
||||||
1,
|
|
||||||
3,
|
|
||||||
3
|
|
||||||
};
|
|
||||||
int[] ret = Merger.MergeLists(first, second);
|
|
||||||
|
|
||||||
Assert.Equal(4, ret.Length);
|
|
||||||
Assert.Equal(1, ret[0]);
|
|
||||||
Assert.Equal(1, ret[1]);
|
|
||||||
Assert.Equal(3, ret[2]);
|
|
||||||
Assert.Equal(3, ret[3]);
|
|
||||||
}
|
|
||||||
|
|
||||||
[Fact]
|
|
||||||
public void MergeListDuplicateCustomEqualityTest()
|
|
||||||
{
|
|
||||||
int[] first = { 1 };
|
|
||||||
int[] second = {
|
|
||||||
3,
|
|
||||||
2
|
|
||||||
};
|
|
||||||
int[] ret = Merger.MergeLists(first, second, (x, y) => x % 2 == y % 2);
|
|
||||||
|
|
||||||
Assert.Equal(2, ret.Length);
|
|
||||||
Assert.Equal(1, ret[0]);
|
|
||||||
Assert.Equal(2, ret[1]);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
79
Kyoo.TheMovieDb/Convertors/CollectionConvertors.cs
Normal file
79
Kyoo.TheMovieDb/Convertors/CollectionConvertors.cs
Normal file
@ -0,0 +1,79 @@
|
|||||||
|
using System.Collections.Generic;
|
||||||
|
using Kyoo.Models;
|
||||||
|
using TMDbLib.Objects.Search;
|
||||||
|
|
||||||
|
namespace Kyoo.TheMovieDb
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// A class containing extensions methods to convert from TMDB's types to Kyoo's types.
|
||||||
|
/// </summary>
|
||||||
|
public static partial class Convertors
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Convert a <see cref="SearchCollection"/> into a <see cref="Collection"/>.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="collection">The collection to convert.</param>
|
||||||
|
/// <param name="provider">The provider representing TheMovieDb.</param>
|
||||||
|
/// <returns>The converted collection as a <see cref="Collection"/>.</returns>
|
||||||
|
public static Collection ToCollection(this TMDbLib.Objects.Collections.Collection collection, Provider provider)
|
||||||
|
{
|
||||||
|
return new Collection
|
||||||
|
{
|
||||||
|
Slug = Utility.ToSlug(collection.Name),
|
||||||
|
Name = collection.Name,
|
||||||
|
Overview = collection.Overview,
|
||||||
|
Images = new Dictionary<int, string>
|
||||||
|
{
|
||||||
|
[Images.Poster] = collection.PosterPath != null
|
||||||
|
? $"https://image.tmdb.org/t/p/original{collection.PosterPath}"
|
||||||
|
: null,
|
||||||
|
[Images.Thumbnail] = collection.BackdropPath != null
|
||||||
|
? $"https://image.tmdb.org/t/p/original{collection.BackdropPath}"
|
||||||
|
: null
|
||||||
|
},
|
||||||
|
ExternalIDs = new []
|
||||||
|
{
|
||||||
|
new MetadataID
|
||||||
|
{
|
||||||
|
Provider = provider,
|
||||||
|
Link = $"https://www.themoviedb.org/collection/{collection.Id}",
|
||||||
|
DataID = collection.Id.ToString()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Convert a <see cref="SearchCollection"/> into a <see cref="Collection"/>.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="collection">The collection to convert.</param>
|
||||||
|
/// <param name="provider">The provider representing TheMovieDb.</param>
|
||||||
|
/// <returns>The converted collection as a <see cref="Collection"/>.</returns>
|
||||||
|
public static Collection ToCollection(this SearchCollection collection, Provider provider)
|
||||||
|
{
|
||||||
|
return new Collection
|
||||||
|
{
|
||||||
|
Slug = Utility.ToSlug(collection.Name),
|
||||||
|
Name = collection.Name,
|
||||||
|
Images = new Dictionary<int, string>
|
||||||
|
{
|
||||||
|
[Images.Poster] = collection.PosterPath != null
|
||||||
|
? $"https://image.tmdb.org/t/p/original{collection.PosterPath}"
|
||||||
|
: null,
|
||||||
|
[Images.Thumbnail] = collection.BackdropPath != null
|
||||||
|
? $"https://image.tmdb.org/t/p/original{collection.BackdropPath}"
|
||||||
|
: null
|
||||||
|
},
|
||||||
|
ExternalIDs = new []
|
||||||
|
{
|
||||||
|
new MetadataID
|
||||||
|
{
|
||||||
|
Provider = provider,
|
||||||
|
Link = $"https://www.themoviedb.org/collection/{collection.Id}",
|
||||||
|
DataID = collection.Id.ToString()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
47
Kyoo.TheMovieDb/Convertors/EpisodeConvertors.cs
Normal file
47
Kyoo.TheMovieDb/Convertors/EpisodeConvertors.cs
Normal file
@ -0,0 +1,47 @@
|
|||||||
|
using System.Collections.Generic;
|
||||||
|
using Kyoo.Models;
|
||||||
|
using TMDbLib.Objects.TvShows;
|
||||||
|
|
||||||
|
namespace Kyoo.TheMovieDb
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// A class containing extensions methods to convert from TMDB's types to Kyoo's types.
|
||||||
|
/// </summary>
|
||||||
|
public static partial class Convertors
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Convert a <see cref="TvEpisode"/> into a <see cref="Episode"/>.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="episode">The episode to convert.</param>
|
||||||
|
/// <param name="showID">The ID of the show inside TheMovieDb.</param>
|
||||||
|
/// <param name="provider">The provider representing TheMovieDb.</param>
|
||||||
|
/// <returns>The converted episode as a <see cref="Episode"/>.</returns>
|
||||||
|
public static Episode ToEpisode(this TvEpisode episode, int showID, Provider provider)
|
||||||
|
{
|
||||||
|
return new Episode
|
||||||
|
{
|
||||||
|
SeasonNumber = episode.SeasonNumber,
|
||||||
|
EpisodeNumber = episode.EpisodeNumber,
|
||||||
|
Title = episode.Name,
|
||||||
|
Overview = episode.Overview,
|
||||||
|
ReleaseDate = episode.AirDate,
|
||||||
|
Images = new Dictionary<int, string>
|
||||||
|
{
|
||||||
|
[Images.Thumbnail] = episode.StillPath != null
|
||||||
|
? $"https://image.tmdb.org/t/p/original{episode.StillPath}"
|
||||||
|
: null
|
||||||
|
},
|
||||||
|
ExternalIDs = new []
|
||||||
|
{
|
||||||
|
new MetadataID
|
||||||
|
{
|
||||||
|
Provider = provider,
|
||||||
|
Link = $"https://www.themoviedb.org/tv/{showID}" +
|
||||||
|
$"/season/{episode.SeasonNumber}/episode/{episode.EpisodeNumber}",
|
||||||
|
DataID = episode.Id.ToString()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
101
Kyoo.TheMovieDb/Convertors/MovieConvertors.cs
Normal file
101
Kyoo.TheMovieDb/Convertors/MovieConvertors.cs
Normal file
@ -0,0 +1,101 @@
|
|||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using Kyoo.Models;
|
||||||
|
using TMDbLib.Objects.Movies;
|
||||||
|
using TMDbLib.Objects.Search;
|
||||||
|
|
||||||
|
namespace Kyoo.TheMovieDb
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// A class containing extensions methods to convert from TMDB's types to Kyoo's types.
|
||||||
|
/// </summary>
|
||||||
|
public static partial class Convertors
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Convert a <see cref="Movie"/> into a <see cref="Show"/>.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="movie">The movie to convert.</param>
|
||||||
|
/// <param name="provider">The provider representing TheMovieDb.</param>
|
||||||
|
/// <returns>The converted movie as a <see cref="Show"/>.</returns>
|
||||||
|
public static Show ToShow(this Movie movie, Provider provider)
|
||||||
|
{
|
||||||
|
return new Show
|
||||||
|
{
|
||||||
|
Slug = Utility.ToSlug(movie.Title),
|
||||||
|
Title = movie.Title,
|
||||||
|
Aliases = movie.AlternativeTitles.Titles.Select(x => x.Title).ToArray(),
|
||||||
|
Overview = movie.Overview,
|
||||||
|
Status = movie.Status == "Released" ? Status.Finished : Status.Planned,
|
||||||
|
StartAir = movie.ReleaseDate,
|
||||||
|
EndAir = movie.ReleaseDate,
|
||||||
|
Images = new Dictionary<int, string>
|
||||||
|
{
|
||||||
|
[Images.Poster] = movie.PosterPath != null
|
||||||
|
? $"https://image.tmdb.org/t/p/original{movie.PosterPath}"
|
||||||
|
: null,
|
||||||
|
[Images.Thumbnail] = movie.BackdropPath != null
|
||||||
|
? $"https://image.tmdb.org/t/p/original{movie.BackdropPath}"
|
||||||
|
: null,
|
||||||
|
[Images.Trailer] = movie.Videos?.Results
|
||||||
|
.Where(x => x.Type is "Trailer" or "Teaser" && x.Site == "YouTube")
|
||||||
|
.Select(x => "https://www.youtube.com/watch?v=" + x.Key).FirstOrDefault(),
|
||||||
|
},
|
||||||
|
Genres = movie.Genres.Select(x => new Genre(x.Name)).ToArray(),
|
||||||
|
Studio = !string.IsNullOrEmpty(movie.ProductionCompanies.FirstOrDefault()?.Name)
|
||||||
|
? new Studio(movie.ProductionCompanies.First().Name)
|
||||||
|
: null,
|
||||||
|
IsMovie = true,
|
||||||
|
People = movie.Credits.Cast
|
||||||
|
.Select(x => x.ToPeople(provider))
|
||||||
|
.Concat(movie.Credits.Crew.Select(x => x.ToPeople(provider)))
|
||||||
|
.ToArray(),
|
||||||
|
ExternalIDs = new []
|
||||||
|
{
|
||||||
|
new MetadataID
|
||||||
|
{
|
||||||
|
Provider = provider,
|
||||||
|
Link = $"https://www.themoviedb.org/movie/{movie.Id}",
|
||||||
|
DataID = movie.Id.ToString()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Convert a <see cref="SearchMovie"/> into a <see cref="Show"/>.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="movie">The movie to convert.</param>
|
||||||
|
/// <param name="provider">The provider representing TheMovieDb.</param>
|
||||||
|
/// <returns>The converted movie as a <see cref="Show"/>.</returns>
|
||||||
|
public static Show ToShow(this SearchMovie movie, Provider provider)
|
||||||
|
{
|
||||||
|
return new Show
|
||||||
|
{
|
||||||
|
Slug = Utility.ToSlug(movie.Title),
|
||||||
|
Title = movie.Title,
|
||||||
|
Overview = movie.Overview,
|
||||||
|
StartAir = movie.ReleaseDate,
|
||||||
|
EndAir = movie.ReleaseDate,
|
||||||
|
Images = new Dictionary<int, string>
|
||||||
|
{
|
||||||
|
[Images.Poster] = movie.PosterPath != null
|
||||||
|
? $"https://image.tmdb.org/t/p/original{movie.PosterPath}"
|
||||||
|
: null,
|
||||||
|
[Images.Thumbnail] = movie.BackdropPath != null
|
||||||
|
? $"https://image.tmdb.org/t/p/original{movie.BackdropPath}"
|
||||||
|
: null,
|
||||||
|
},
|
||||||
|
IsMovie = true,
|
||||||
|
ExternalIDs = new []
|
||||||
|
{
|
||||||
|
new MetadataID
|
||||||
|
{
|
||||||
|
Provider = provider,
|
||||||
|
Link = $"https://www.themoviedb.org/movie/{movie.Id}",
|
||||||
|
DataID = movie.Id.ToString()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
182
Kyoo.TheMovieDb/Convertors/PeopleConvertors.cs
Normal file
182
Kyoo.TheMovieDb/Convertors/PeopleConvertors.cs
Normal file
@ -0,0 +1,182 @@
|
|||||||
|
using System.Collections.Generic;
|
||||||
|
using Kyoo.Models;
|
||||||
|
using TMDbLib.Objects.General;
|
||||||
|
using TMDbLib.Objects.People;
|
||||||
|
using TMDbLib.Objects.Search;
|
||||||
|
using Images = Kyoo.Models.Images;
|
||||||
|
using TvCast = TMDbLib.Objects.TvShows.Cast;
|
||||||
|
using MovieCast = TMDbLib.Objects.Movies.Cast;
|
||||||
|
|
||||||
|
namespace Kyoo.TheMovieDb
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// A class containing extensions methods to convert from TMDB's types to Kyoo's types.
|
||||||
|
/// </summary>
|
||||||
|
public static partial class Convertors
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Convert a <see cref="MovieCast"/> to a <see cref="PeopleRole"/>.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="cast">An internal TheMovieDB cast.</param>
|
||||||
|
/// <param name="provider">The provider that represent TheMovieDB inside Kyoo.</param>
|
||||||
|
/// <returns>A <see cref="PeopleRole"/> representing the movie cast.</returns>
|
||||||
|
public static PeopleRole ToPeople(this MovieCast cast, Provider provider)
|
||||||
|
{
|
||||||
|
return new PeopleRole
|
||||||
|
{
|
||||||
|
People = new People
|
||||||
|
{
|
||||||
|
Slug = Utility.ToSlug(cast.Name),
|
||||||
|
Name = cast.Name,
|
||||||
|
Images = new Dictionary<int, string>
|
||||||
|
{
|
||||||
|
[Images.Poster] = cast.ProfilePath != null
|
||||||
|
? $"https://image.tmdb.org/t/p/original{cast.ProfilePath}"
|
||||||
|
: null
|
||||||
|
},
|
||||||
|
ExternalIDs = new[]
|
||||||
|
{
|
||||||
|
new MetadataID
|
||||||
|
{
|
||||||
|
Provider = provider,
|
||||||
|
DataID = cast.Id.ToString(),
|
||||||
|
Link = $"https://www.themoviedb.org/person/{cast.Id}"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
Type = "Actor",
|
||||||
|
Role = cast.Character
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Convert a <see cref="TvCast"/> to a <see cref="PeopleRole"/>.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="cast">An internal TheMovieDB cast.</param>
|
||||||
|
/// <param name="provider">The provider that represent TheMovieDB inside Kyoo.</param>
|
||||||
|
/// <returns>A <see cref="PeopleRole"/> representing the movie cast.</returns>
|
||||||
|
public static PeopleRole ToPeople(this TvCast cast, Provider provider)
|
||||||
|
{
|
||||||
|
return new PeopleRole
|
||||||
|
{
|
||||||
|
People = new People
|
||||||
|
{
|
||||||
|
Slug = Utility.ToSlug(cast.Name),
|
||||||
|
Name = cast.Name,
|
||||||
|
Images = new Dictionary<int, string>
|
||||||
|
{
|
||||||
|
[Images.Poster] = cast.ProfilePath != null
|
||||||
|
? $"https://image.tmdb.org/t/p/original{cast.ProfilePath}"
|
||||||
|
: null
|
||||||
|
},
|
||||||
|
ExternalIDs = new[]
|
||||||
|
{
|
||||||
|
new MetadataID
|
||||||
|
{
|
||||||
|
Provider = provider,
|
||||||
|
DataID = cast.Id.ToString(),
|
||||||
|
Link = $"https://www.themoviedb.org/person/{cast.Id}"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
Type = "Actor",
|
||||||
|
Role = cast.Character
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Convert a <see cref="Crew"/> to a <see cref="PeopleRole"/>.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="crew">An internal TheMovieDB crew member.</param>
|
||||||
|
/// <param name="provider">The provider that represent TheMovieDB inside Kyoo.</param>
|
||||||
|
/// <returns>A <see cref="PeopleRole"/> representing the movie crew.</returns>
|
||||||
|
public static PeopleRole ToPeople(this Crew crew, Provider provider)
|
||||||
|
{
|
||||||
|
return new PeopleRole
|
||||||
|
{
|
||||||
|
People = new People
|
||||||
|
{
|
||||||
|
Slug = Utility.ToSlug(crew.Name),
|
||||||
|
Name = crew.Name,
|
||||||
|
Images = new Dictionary<int, string>
|
||||||
|
{
|
||||||
|
[Images.Poster] = crew.ProfilePath != null
|
||||||
|
? $"https://image.tmdb.org/t/p/original{crew.ProfilePath}"
|
||||||
|
: null
|
||||||
|
},
|
||||||
|
ExternalIDs = new[]
|
||||||
|
{
|
||||||
|
new MetadataID
|
||||||
|
{
|
||||||
|
Provider = provider,
|
||||||
|
DataID = crew.Id.ToString(),
|
||||||
|
Link = $"https://www.themoviedb.org/person/{crew.Id}"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
Type = crew.Department,
|
||||||
|
Role = crew.Job
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Convert a <see cref="Person"/> to a <see cref="People"/>.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="person">An internal TheMovieDB person.</param>
|
||||||
|
/// <param name="provider">The provider that represent TheMovieDB inside Kyoo.</param>
|
||||||
|
/// <returns>A <see cref="People"/> representing the person.</returns>
|
||||||
|
public static People ToPeople(this Person person, Provider provider)
|
||||||
|
{
|
||||||
|
return new People
|
||||||
|
{
|
||||||
|
Slug = Utility.ToSlug(person.Name),
|
||||||
|
Name = person.Name,
|
||||||
|
Images = new Dictionary<int, string>
|
||||||
|
{
|
||||||
|
[Images.Poster] = person.ProfilePath != null
|
||||||
|
? $"https://image.tmdb.org/t/p/original{person.ProfilePath}"
|
||||||
|
: null
|
||||||
|
},
|
||||||
|
ExternalIDs = new[]
|
||||||
|
{
|
||||||
|
new MetadataID
|
||||||
|
{
|
||||||
|
Provider = provider,
|
||||||
|
DataID = person.Id.ToString(),
|
||||||
|
Link = $"https://www.themoviedb.org/person/{person.Id}"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Convert a <see cref="SearchPerson"/> to a <see cref="People"/>.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="person">An internal TheMovieDB person.</param>
|
||||||
|
/// <param name="provider">The provider that represent TheMovieDB inside Kyoo.</param>
|
||||||
|
/// <returns>A <see cref="People"/> representing the person.</returns>
|
||||||
|
public static People ToPeople(this SearchPerson person, Provider provider)
|
||||||
|
{
|
||||||
|
return new People
|
||||||
|
{
|
||||||
|
Slug = Utility.ToSlug(person.Name),
|
||||||
|
Name = person.Name,
|
||||||
|
Images = new Dictionary<int, string>
|
||||||
|
{
|
||||||
|
[Images.Poster] = person.ProfilePath != null
|
||||||
|
? $"https://image.tmdb.org/t/p/original{person.ProfilePath}"
|
||||||
|
: null
|
||||||
|
},
|
||||||
|
ExternalIDs = new[]
|
||||||
|
{
|
||||||
|
new MetadataID
|
||||||
|
{
|
||||||
|
Provider = provider,
|
||||||
|
DataID = person.Id.ToString(),
|
||||||
|
Link = $"https://www.themoviedb.org/person/{person.Id}"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
45
Kyoo.TheMovieDb/Convertors/SeasonConvertors.cs
Normal file
45
Kyoo.TheMovieDb/Convertors/SeasonConvertors.cs
Normal file
@ -0,0 +1,45 @@
|
|||||||
|
using System.Collections.Generic;
|
||||||
|
using Kyoo.Models;
|
||||||
|
using TMDbLib.Objects.TvShows;
|
||||||
|
|
||||||
|
namespace Kyoo.TheMovieDb
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// A class containing extensions methods to convert from TMDB's types to Kyoo's types.
|
||||||
|
/// </summary>
|
||||||
|
public static partial class Convertors
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Convert a <see cref="TvSeason"/> into a <see cref="Season"/>.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="season">The season to convert.</param>
|
||||||
|
/// <param name="showID">The ID of the show inside TheMovieDb.</param>
|
||||||
|
/// <param name="provider">The provider representing TheMovieDb.</param>
|
||||||
|
/// <returns>The converted season as a <see cref="Season"/>.</returns>
|
||||||
|
public static Season ToSeason(this TvSeason season, int showID, Provider provider)
|
||||||
|
{
|
||||||
|
return new Season
|
||||||
|
{
|
||||||
|
SeasonNumber = season.SeasonNumber,
|
||||||
|
Title = season.Name,
|
||||||
|
Overview = season.Overview,
|
||||||
|
StartDate = season.AirDate,
|
||||||
|
Images = new Dictionary<int, string>
|
||||||
|
{
|
||||||
|
[Images.Poster] = season.PosterPath != null
|
||||||
|
? $"https://image.tmdb.org/t/p/original{season.PosterPath}"
|
||||||
|
: null
|
||||||
|
},
|
||||||
|
ExternalIDs = new []
|
||||||
|
{
|
||||||
|
new MetadataID
|
||||||
|
{
|
||||||
|
Provider = provider,
|
||||||
|
Link = $"https://www.themoviedb.org/tv/{showID}/season/{season.SeasonNumber}",
|
||||||
|
DataID = season.Id?.ToString()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
98
Kyoo.TheMovieDb/Convertors/ShowConvertors.cs
Normal file
98
Kyoo.TheMovieDb/Convertors/ShowConvertors.cs
Normal file
@ -0,0 +1,98 @@
|
|||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using Kyoo.Models;
|
||||||
|
using TMDbLib.Objects.Search;
|
||||||
|
using TMDbLib.Objects.TvShows;
|
||||||
|
|
||||||
|
namespace Kyoo.TheMovieDb
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// A class containing extensions methods to convert from TMDB's types to Kyoo's types.
|
||||||
|
/// </summary>
|
||||||
|
public static partial class Convertors
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Convert a <see cref="TvShow"/> to a <see cref="Show"/>.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="tv">The show to convert.</param>
|
||||||
|
/// <param name="provider">The provider representing TheMovieDb.</param>
|
||||||
|
/// <returns>A converted <see cref="TvShow"/> as a <see cref="Show"/>.</returns>
|
||||||
|
public static Show ToShow(this TvShow tv, Provider provider)
|
||||||
|
{
|
||||||
|
return new Show
|
||||||
|
{
|
||||||
|
Slug = Utility.ToSlug(tv.Name),
|
||||||
|
Title = tv.Name,
|
||||||
|
Aliases = tv.AlternativeTitles.Results.Select(x => x.Title).ToArray(),
|
||||||
|
Overview = tv.Overview,
|
||||||
|
Status = tv.Status == "Ended" ? Status.Finished : Status.Planned,
|
||||||
|
StartAir = tv.FirstAirDate,
|
||||||
|
EndAir = tv.LastAirDate,
|
||||||
|
Images = new Dictionary<int, string>
|
||||||
|
{
|
||||||
|
[Images.Poster] = tv.PosterPath != null
|
||||||
|
? $"https://image.tmdb.org/t/p/original{tv.PosterPath}"
|
||||||
|
: null,
|
||||||
|
[Images.Thumbnail] = tv.BackdropPath != null
|
||||||
|
? $"https://image.tmdb.org/t/p/original{tv.BackdropPath}"
|
||||||
|
: null,
|
||||||
|
[Images.Trailer] = tv.Videos?.Results
|
||||||
|
.Where(x => x.Type is "Trailer" or "Teaser" && x.Site == "YouTube")
|
||||||
|
.Select(x => "https://www.youtube.com/watch?v=" + x.Key).FirstOrDefault()
|
||||||
|
},
|
||||||
|
Genres = tv.Genres.Select(x => new Genre(x.Name)).ToArray(),
|
||||||
|
Studio = !string.IsNullOrEmpty(tv.ProductionCompanies.FirstOrDefault()?.Name)
|
||||||
|
? new Studio(tv.ProductionCompanies.First().Name)
|
||||||
|
: null,
|
||||||
|
People = tv.Credits.Cast
|
||||||
|
.Select(x => x.ToPeople(provider))
|
||||||
|
.Concat(tv.Credits.Crew.Select(x => x.ToPeople(provider)))
|
||||||
|
.ToArray(),
|
||||||
|
ExternalIDs = new []
|
||||||
|
{
|
||||||
|
new MetadataID
|
||||||
|
{
|
||||||
|
Provider = provider,
|
||||||
|
Link = $"https://www.themoviedb.org/tv/{tv.Id}",
|
||||||
|
DataID = tv.Id.ToString()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Convert a <see cref="SearchTv"/> to a <see cref="Show"/>.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="tv">The show to convert.</param>
|
||||||
|
/// <param name="provider">The provider representing TheMovieDb.</param>
|
||||||
|
/// <returns>A converted <see cref="SearchTv"/> as a <see cref="Show"/>.</returns>
|
||||||
|
public static Show ToShow(this SearchTv tv, Provider provider)
|
||||||
|
{
|
||||||
|
return new Show
|
||||||
|
{
|
||||||
|
Slug = Utility.ToSlug(tv.Name),
|
||||||
|
Title = tv.Name,
|
||||||
|
Overview = tv.Overview,
|
||||||
|
StartAir = tv.FirstAirDate,
|
||||||
|
Images = new Dictionary<int, string>
|
||||||
|
{
|
||||||
|
[Images.Poster] = tv.PosterPath != null
|
||||||
|
? $"https://image.tmdb.org/t/p/original{tv.PosterPath}"
|
||||||
|
: null,
|
||||||
|
[Images.Thumbnail] = tv.BackdropPath != null
|
||||||
|
? $"https://image.tmdb.org/t/p/original{tv.BackdropPath}"
|
||||||
|
: null,
|
||||||
|
},
|
||||||
|
ExternalIDs = new []
|
||||||
|
{
|
||||||
|
new MetadataID
|
||||||
|
{
|
||||||
|
Provider = provider,
|
||||||
|
Link = $"https://www.themoviedb.org/tv/{tv.Id}",
|
||||||
|
DataID = tv.Id.ToString()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
60
Kyoo.TheMovieDb/Convertors/StudioConvertors.cs
Normal file
60
Kyoo.TheMovieDb/Convertors/StudioConvertors.cs
Normal file
@ -0,0 +1,60 @@
|
|||||||
|
using Kyoo.Models;
|
||||||
|
using TMDbLib.Objects.Companies;
|
||||||
|
using TMDbLib.Objects.Search;
|
||||||
|
|
||||||
|
namespace Kyoo.TheMovieDb
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// A class containing extensions methods to convert from TMDB's types to Kyoo's types.
|
||||||
|
/// </summary>
|
||||||
|
public static partial class Convertors
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Convert a <see cref="Company"/> into a <see cref="Studio"/>.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="company">The company to convert.</param>
|
||||||
|
/// <param name="provider">The provider representing TheMovieDb.</param>
|
||||||
|
/// <returns>The converted company as a <see cref="Studio"/>.</returns>
|
||||||
|
public static Studio ToStudio(this Company company, Provider provider)
|
||||||
|
{
|
||||||
|
return new Studio
|
||||||
|
{
|
||||||
|
Slug = Utility.ToSlug(company.Name),
|
||||||
|
Name = company.Name,
|
||||||
|
ExternalIDs = new []
|
||||||
|
{
|
||||||
|
new MetadataID
|
||||||
|
{
|
||||||
|
Provider = provider,
|
||||||
|
Link = $"https://www.themoviedb.org/company/{company.Id}",
|
||||||
|
DataID = company.Id.ToString()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Convert a <see cref="SearchCompany"/> into a <see cref="Studio"/>.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="company">The company to convert.</param>
|
||||||
|
/// <param name="provider">The provider representing TheMovieDb.</param>
|
||||||
|
/// <returns>The converted company as a <see cref="Studio"/>.</returns>
|
||||||
|
public static Studio ToStudio(this SearchCompany company, Provider provider)
|
||||||
|
{
|
||||||
|
return new Studio
|
||||||
|
{
|
||||||
|
Slug = Utility.ToSlug(company.Name),
|
||||||
|
Name = company.Name,
|
||||||
|
ExternalIDs = new[]
|
||||||
|
{
|
||||||
|
new MetadataID
|
||||||
|
{
|
||||||
|
Provider = provider,
|
||||||
|
Link = $"https://www.themoviedb.org/company/{company.Id}",
|
||||||
|
DataID = company.Id.ToString()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
35
Kyoo.TheMovieDb/Kyoo.TheMovieDb.csproj
Normal file
35
Kyoo.TheMovieDb/Kyoo.TheMovieDb.csproj
Normal file
@ -0,0 +1,35 @@
|
|||||||
|
<Project Sdk="Microsoft.NET.Sdk">
|
||||||
|
<PropertyGroup>
|
||||||
|
<TargetFramework>net5.0</TargetFramework>
|
||||||
|
|
||||||
|
<Company>SDG</Company>
|
||||||
|
<Authors>Zoe Roux</Authors>
|
||||||
|
<RepositoryUrl>https://github.com/AnonymusRaccoon/Kyoo</RepositoryUrl>
|
||||||
|
<LangVersion>default</LangVersion>
|
||||||
|
<RootNamespace>Kyoo.TheMovieDb</RootNamespace>
|
||||||
|
</PropertyGroup>
|
||||||
|
|
||||||
|
<PropertyGroup>
|
||||||
|
<OutputPath>../Kyoo/bin/$(Configuration)/$(TargetFramework)/plugins/the-moviedb</OutputPath>
|
||||||
|
<AppendTargetFrameworkToOutputPath>false</AppendTargetFrameworkToOutputPath>
|
||||||
|
<ProduceReferenceAssembly>false</ProduceReferenceAssembly>
|
||||||
|
<GenerateDependencyFile>false</GenerateDependencyFile>
|
||||||
|
<GenerateRuntimeConfigurationFiles>false</GenerateRuntimeConfigurationFiles>
|
||||||
|
<CopyLocalLockFileAssemblies>true</CopyLocalLockFileAssemblies>
|
||||||
|
</PropertyGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<PackageReference Include="Microsoft.Extensions.Logging.Abstractions" Version="5.0.0" />
|
||||||
|
<PackageReference Include="Microsoft.Extensions.Options" Version="5.0.0" />
|
||||||
|
<PackageReference Include="Microsoft.Extensions.Options.ConfigurationExtensions" Version="5.0.0" />
|
||||||
|
<PackageReference Include="TMDbLib" Version="1.8.1" />
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<ProjectReference Include="../Kyoo.Common/Kyoo.Common.csproj">
|
||||||
|
<PrivateAssets>all</PrivateAssets>
|
||||||
|
<Private>false</Private>
|
||||||
|
<ExcludeAssets>runtime</ExcludeAssets>
|
||||||
|
</ProjectReference>
|
||||||
|
</ItemGroup>
|
||||||
|
</Project>
|
79
Kyoo.TheMovieDb/PluginTmdb.cs
Normal file
79
Kyoo.TheMovieDb/PluginTmdb.cs
Normal file
@ -0,0 +1,79 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using Autofac;
|
||||||
|
using Kyoo.Controllers;
|
||||||
|
using Kyoo.Models.Attributes;
|
||||||
|
using Kyoo.TheMovieDb.Models;
|
||||||
|
using Microsoft.AspNetCore.Builder;
|
||||||
|
using Microsoft.Extensions.Configuration;
|
||||||
|
using Microsoft.Extensions.DependencyInjection;
|
||||||
|
|
||||||
|
namespace Kyoo.TheMovieDb
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// A plugin that add a <see cref="IMetadataProvider"/> for TheMovieDB.
|
||||||
|
/// </summary>
|
||||||
|
public class PluginTmdb : IPlugin
|
||||||
|
{
|
||||||
|
/// <inheritdoc />
|
||||||
|
public string Slug => "the-moviedb";
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public string Name => "TheMovieDb Provider";
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public string Description => "A metadata provider for TheMovieDB.";
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public ICollection<Type> Provides => new []
|
||||||
|
{
|
||||||
|
typeof(IMetadataProvider)
|
||||||
|
};
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public ICollection<ConditionalProvide> ConditionalProvides => ArraySegment<ConditionalProvide>.Empty;
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public ICollection<Type> Requires => ArraySegment<Type>.Empty;
|
||||||
|
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The configuration to use.
|
||||||
|
/// </summary>
|
||||||
|
private readonly IConfiguration _configuration;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The configuration manager used to register typed/untyped implementations.
|
||||||
|
/// </summary>
|
||||||
|
[Injected] public IConfigurationManager ConfigurationManager { private get; set; }
|
||||||
|
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Create a new tmdb module instance and use the given configuration.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="configuration">The configuration to use</param>
|
||||||
|
public PluginTmdb(IConfiguration configuration)
|
||||||
|
{
|
||||||
|
_configuration = configuration;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public void Configure(ContainerBuilder builder)
|
||||||
|
{
|
||||||
|
builder.RegisterProvider<TheMovieDbProvider>();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public void Configure(IServiceCollection services, ICollection<Type> availableTypes)
|
||||||
|
{
|
||||||
|
services.Configure<TheMovieDbOptions>(_configuration.GetSection(TheMovieDbOptions.Path));
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public void ConfigureAspNet(IApplicationBuilder app)
|
||||||
|
{
|
||||||
|
ConfigurationManager.AddTyped<TheMovieDbOptions>(TheMovieDbOptions.Path);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
280
Kyoo.TheMovieDb/ProviderTmdb.cs
Normal file
280
Kyoo.TheMovieDb/ProviderTmdb.cs
Normal file
@ -0,0 +1,280 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using Kyoo.Controllers;
|
||||||
|
using Kyoo.Models;
|
||||||
|
using Kyoo.TheMovieDb.Models;
|
||||||
|
using Microsoft.Extensions.Logging;
|
||||||
|
using Microsoft.Extensions.Options;
|
||||||
|
using TMDbLib.Client;
|
||||||
|
using TMDbLib.Objects.Movies;
|
||||||
|
using TMDbLib.Objects.Search;
|
||||||
|
using TMDbLib.Objects.TvShows;
|
||||||
|
|
||||||
|
namespace Kyoo.TheMovieDb
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// A metadata provider for TheMovieDb.
|
||||||
|
/// </summary>
|
||||||
|
public class TheMovieDbProvider : IMetadataProvider
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// The API key used to authenticate with TheMovieDb API.
|
||||||
|
/// </summary>
|
||||||
|
private readonly IOptions<TheMovieDbOptions> _apiKey;
|
||||||
|
/// <summary>
|
||||||
|
/// The logger to use in ase of issue.
|
||||||
|
/// </summary>
|
||||||
|
private readonly ILogger<TheMovieDbProvider> _logger;
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public Provider Provider => new()
|
||||||
|
{
|
||||||
|
Slug = "the-moviedb",
|
||||||
|
Name = "TheMovieDB",
|
||||||
|
Images = new Dictionary<int, string>
|
||||||
|
{
|
||||||
|
[Images.Logo] = "https://www.themoviedb.org/assets/2/v4/logos/v2/" +
|
||||||
|
"blue_short-8e7b30f73a4020692ccca9c88bafe5dcb6f8a62a4c6bc55cd9ba82bb2cd95f6c.svg"
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Create a new <see cref="TheMovieDbProvider"/> using the given api key.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="apiKey">The api key</param>
|
||||||
|
/// <param name="logger">The logger to use in case of issue.</param>
|
||||||
|
public TheMovieDbProvider(IOptions<TheMovieDbOptions> apiKey, ILogger<TheMovieDbProvider> logger)
|
||||||
|
{
|
||||||
|
_apiKey = apiKey;
|
||||||
|
_logger = logger;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public Task<T> Get<T>(T item)
|
||||||
|
where T : class, IResource
|
||||||
|
{
|
||||||
|
return item switch
|
||||||
|
{
|
||||||
|
Collection collection => _GetCollection(collection) as Task<T>,
|
||||||
|
Show show => _GetShow(show) as Task<T>,
|
||||||
|
Season season => _GetSeason(season) as Task<T>,
|
||||||
|
Episode episode => _GetEpisode(episode) as Task<T>,
|
||||||
|
People person => _GetPerson(person) as Task<T>,
|
||||||
|
Studio studio => _GetStudio(studio) as Task<T>,
|
||||||
|
_ => null
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Get a collection using it's id, if the id is not present in the collection, fallback to a name search.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="collection">The collection to search for</param>
|
||||||
|
/// <returns>A collection containing metadata from TheMovieDb</returns>
|
||||||
|
private async Task<Collection> _GetCollection(Collection collection)
|
||||||
|
{
|
||||||
|
if (!collection.TryGetID(Provider.Slug, out int id))
|
||||||
|
{
|
||||||
|
Collection found = (await _SearchCollections(collection.Name ?? collection.Slug)).FirstOrDefault();
|
||||||
|
if (found?.TryGetID(Provider.Slug, out id) != true)
|
||||||
|
return found;
|
||||||
|
}
|
||||||
|
|
||||||
|
TMDbClient client = new(_apiKey.Value.ApiKey);
|
||||||
|
return (await client.GetCollectionAsync(id)).ToCollection(Provider);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Get a show using it's id, if the id is not present in the show, fallback to a title search.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="show">The show to search for</param>
|
||||||
|
/// <returns>A show containing metadata from TheMovieDb</returns>
|
||||||
|
private async Task<Show> _GetShow(Show show)
|
||||||
|
{
|
||||||
|
if (!show.TryGetID(Provider.Slug, out int id))
|
||||||
|
{
|
||||||
|
Show found = (await _SearchShows(show.Title ?? show.Slug, show.StartAir?.Year))
|
||||||
|
.FirstOrDefault(x => x.IsMovie == show.IsMovie);
|
||||||
|
if (found?.TryGetID(Provider.Slug, out id) != true)
|
||||||
|
return found;
|
||||||
|
}
|
||||||
|
|
||||||
|
TMDbClient client = new(_apiKey.Value.ApiKey);
|
||||||
|
|
||||||
|
if (show.IsMovie)
|
||||||
|
{
|
||||||
|
return (await client
|
||||||
|
.GetMovieAsync(id, MovieMethods.AlternativeTitles | MovieMethods.Videos | MovieMethods.Credits))
|
||||||
|
?.ToShow(Provider);
|
||||||
|
}
|
||||||
|
|
||||||
|
return (await client
|
||||||
|
.GetTvShowAsync(id, TvShowMethods.AlternativeTitles | TvShowMethods.Videos | TvShowMethods.Credits))
|
||||||
|
?.ToShow(Provider);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Get a season using it's show and it's season number.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="season">The season to retrieve metadata for.</param>
|
||||||
|
/// <returns>A season containing metadata from TheMovieDb</returns>
|
||||||
|
private async Task<Season> _GetSeason(Season season)
|
||||||
|
{
|
||||||
|
if (season.Show == null)
|
||||||
|
{
|
||||||
|
_logger.LogWarning("Metadata for a season was requested but it's show is not loaded. " +
|
||||||
|
"This is unsupported");
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!season.Show.TryGetID(Provider.Slug, out int id))
|
||||||
|
return null;
|
||||||
|
|
||||||
|
TMDbClient client = new(_apiKey.Value.ApiKey);
|
||||||
|
return (await client.GetTvSeasonAsync(id, season.SeasonNumber))
|
||||||
|
.ToSeason(id, Provider);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Get an episode using it's show, it's season number and it's episode number.
|
||||||
|
/// Absolute numbering is not supported.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="episode">The episode to retrieve metadata for.</param>
|
||||||
|
/// <returns>An episode containing metadata from TheMovieDb</returns>
|
||||||
|
private async Task<Episode> _GetEpisode(Episode episode)
|
||||||
|
{
|
||||||
|
if (episode.Show == null)
|
||||||
|
{
|
||||||
|
_logger.LogWarning("Metadata for an episode was requested but it's show is not loaded. " +
|
||||||
|
"This is unsupported");
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
if (!episode.Show.TryGetID(Provider.Slug, out int id)
|
||||||
|
|| episode.SeasonNumber == null || episode.EpisodeNumber == null)
|
||||||
|
return null;
|
||||||
|
|
||||||
|
TMDbClient client = new(_apiKey.Value.ApiKey);
|
||||||
|
return (await client.GetTvEpisodeAsync(id, episode.SeasonNumber.Value, episode.EpisodeNumber.Value))
|
||||||
|
.ToEpisode(id, Provider);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Get a person using it's id, if the id is not present in the person, fallback to a name search.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="person">The person to search for</param>
|
||||||
|
/// <returns>A person containing metadata from TheMovieDb</returns>
|
||||||
|
private async Task<People> _GetPerson(People person)
|
||||||
|
{
|
||||||
|
if (!person.TryGetID(Provider.Slug, out int id))
|
||||||
|
{
|
||||||
|
People found = (await _SearchPeople(person.Name ?? person.Slug)).FirstOrDefault();
|
||||||
|
if (found?.TryGetID(Provider.Slug, out id) != true)
|
||||||
|
return found;
|
||||||
|
}
|
||||||
|
|
||||||
|
TMDbClient client = new(_apiKey.Value.ApiKey);
|
||||||
|
return (await client.GetPersonAsync(id)).ToPeople(Provider);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Get a studio using it's id, if the id is not present in the studio, fallback to a name search.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="studio">The studio to search for</param>
|
||||||
|
/// <returns>A studio containing metadata from TheMovieDb</returns>
|
||||||
|
private async Task<Studio> _GetStudio(Studio studio)
|
||||||
|
{
|
||||||
|
if (!studio.TryGetID(Provider.Slug, out int id))
|
||||||
|
{
|
||||||
|
Studio found = (await _SearchStudios(studio.Name ?? studio.Slug)).FirstOrDefault();
|
||||||
|
if (found?.TryGetID(Provider.Slug, out id) != true)
|
||||||
|
return found;
|
||||||
|
}
|
||||||
|
|
||||||
|
TMDbClient client = new(_apiKey.Value.ApiKey);
|
||||||
|
return (await client.GetCompanyAsync(id)).ToStudio(Provider);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public async Task<ICollection<T>> Search<T>(string query)
|
||||||
|
where T : class, IResource
|
||||||
|
{
|
||||||
|
if (typeof(T) == typeof(Collection))
|
||||||
|
return (await _SearchCollections(query) as ICollection<T>)!;
|
||||||
|
if (typeof(T) == typeof(Show))
|
||||||
|
return (await _SearchShows(query) as ICollection<T>)!;
|
||||||
|
if (typeof(T) == typeof(People))
|
||||||
|
return (await _SearchPeople(query) as ICollection<T>)!;
|
||||||
|
if (typeof(T) == typeof(Studio))
|
||||||
|
return (await _SearchStudios(query) as ICollection<T>)!;
|
||||||
|
return ArraySegment<T>.Empty;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Search for a collection using it's name as a query.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="query">The query to search for</param>
|
||||||
|
/// <returns>A list of collections containing metadata from TheMovieDb</returns>
|
||||||
|
private async Task<ICollection<Collection>> _SearchCollections(string query)
|
||||||
|
{
|
||||||
|
TMDbClient client = new(_apiKey.Value.ApiKey);
|
||||||
|
return (await client.SearchCollectionAsync(query))
|
||||||
|
.Results
|
||||||
|
.Select(x => x.ToCollection(Provider))
|
||||||
|
.ToArray();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Search for a show using it's name as a query.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="query">The query to search for</param>
|
||||||
|
/// <param name="year">The year in witch the show has aired.</param>
|
||||||
|
/// <returns>A list of shows containing metadata from TheMovieDb</returns>
|
||||||
|
private async Task<ICollection<Show>> _SearchShows(string query, int? year = null)
|
||||||
|
{
|
||||||
|
TMDbClient client = new(_apiKey.Value.ApiKey);
|
||||||
|
return (await client.SearchMultiAsync(query, year: year ?? 0))
|
||||||
|
.Results
|
||||||
|
.Select(x =>
|
||||||
|
{
|
||||||
|
return x switch
|
||||||
|
{
|
||||||
|
SearchTv tv => tv.ToShow(Provider),
|
||||||
|
SearchMovie movie => movie.ToShow(Provider),
|
||||||
|
_ => null
|
||||||
|
};
|
||||||
|
})
|
||||||
|
.Where(x => x != null)
|
||||||
|
.ToArray();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Search for people using there name as a query.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="query">The query to search for</param>
|
||||||
|
/// <returns>A list of people containing metadata from TheMovieDb</returns>
|
||||||
|
private async Task<ICollection<People>> _SearchPeople(string query)
|
||||||
|
{
|
||||||
|
TMDbClient client = new(_apiKey.Value.ApiKey);
|
||||||
|
return (await client.SearchPersonAsync(query))
|
||||||
|
.Results
|
||||||
|
.Select(x => x.ToPeople(Provider))
|
||||||
|
.ToArray();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Search for studios using there name as a query.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="query">The query to search for</param>
|
||||||
|
/// <returns>A list of studios containing metadata from TheMovieDb</returns>
|
||||||
|
private async Task<ICollection<Studio>> _SearchStudios(string query)
|
||||||
|
{
|
||||||
|
TMDbClient client = new(_apiKey.Value.ApiKey);
|
||||||
|
return (await client.SearchCompanyAsync(query))
|
||||||
|
.Results
|
||||||
|
.Select(x => x.ToStudio(Provider))
|
||||||
|
.ToArray();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
18
Kyoo.TheMovieDb/TheMovieDbOptions.cs
Normal file
18
Kyoo.TheMovieDb/TheMovieDbOptions.cs
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
namespace Kyoo.TheMovieDb.Models
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// The option containing the api key for TheMovieDb.
|
||||||
|
/// </summary>
|
||||||
|
public class TheMovieDbOptions
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// The path to get this option from the root configuration.
|
||||||
|
/// </summary>
|
||||||
|
public const string Path = "the-moviedb";
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The api key of TheMovieDb.
|
||||||
|
/// </summary>
|
||||||
|
public string ApiKey { get; set; }
|
||||||
|
}
|
||||||
|
}
|
@ -1,4 +1,5 @@
|
|||||||
using System;
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
using System.Globalization;
|
using System.Globalization;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using Kyoo.Models;
|
using Kyoo.Models;
|
||||||
@ -47,7 +48,7 @@ namespace Kyoo.TheTvdb
|
|||||||
/// <returns>A show representing the given search result.</returns>
|
/// <returns>A show representing the given search result.</returns>
|
||||||
public static Show ToShow(this SeriesSearchResult result, Provider provider)
|
public static Show ToShow(this SeriesSearchResult result, Provider provider)
|
||||||
{
|
{
|
||||||
return new()
|
return new Show
|
||||||
{
|
{
|
||||||
Slug = result.Slug,
|
Slug = result.Slug,
|
||||||
Title = result.SeriesName,
|
Title = result.SeriesName,
|
||||||
@ -55,14 +56,19 @@ namespace Kyoo.TheTvdb
|
|||||||
Overview = result.Overview,
|
Overview = result.Overview,
|
||||||
Status = _GetStatus(result.Status),
|
Status = _GetStatus(result.Status),
|
||||||
StartAir = _ParseDate(result.FirstAired),
|
StartAir = _ParseDate(result.FirstAired),
|
||||||
Poster = result.Poster != null ? $"https://www.thetvdb.com{result.Poster}" : null,
|
Images = new Dictionary<int, string>
|
||||||
|
{
|
||||||
|
[Images.Poster] = !string.IsNullOrEmpty(result.Poster)
|
||||||
|
? $"https://www.thetvdb.com{result.Poster}"
|
||||||
|
: null,
|
||||||
|
},
|
||||||
ExternalIDs = new[]
|
ExternalIDs = new[]
|
||||||
{
|
{
|
||||||
new MetadataID<Show>
|
new MetadataID
|
||||||
{
|
{
|
||||||
DataID = result.Id.ToString(),
|
DataID = result.Id.ToString(),
|
||||||
Link = $"https://www.thetvdb.com/series/{result.Slug}",
|
Link = $"https://www.thetvdb.com/series/{result.Slug}",
|
||||||
Second = provider
|
Provider = provider
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@ -76,7 +82,7 @@ namespace Kyoo.TheTvdb
|
|||||||
/// <returns>A show representing the given series.</returns>
|
/// <returns>A show representing the given series.</returns>
|
||||||
public static Show ToShow(this Series series, Provider provider)
|
public static Show ToShow(this Series series, Provider provider)
|
||||||
{
|
{
|
||||||
return new()
|
return new Show
|
||||||
{
|
{
|
||||||
Slug = series.Slug,
|
Slug = series.Slug,
|
||||||
Title = series.SeriesName,
|
Title = series.SeriesName,
|
||||||
@ -84,16 +90,23 @@ namespace Kyoo.TheTvdb
|
|||||||
Overview = series.Overview,
|
Overview = series.Overview,
|
||||||
Status = _GetStatus(series.Status),
|
Status = _GetStatus(series.Status),
|
||||||
StartAir = _ParseDate(series.FirstAired),
|
StartAir = _ParseDate(series.FirstAired),
|
||||||
Poster = series.Poster != null ? $"https://www.thetvdb.com/banners/{series.Poster}" : null,
|
Images = new Dictionary<int, string>
|
||||||
Backdrop = series.FanArt != null ? $"https://www.thetvdb.com/banners/{series.FanArt}" : null,
|
{
|
||||||
|
[Images.Poster] = !string.IsNullOrEmpty(series.Poster)
|
||||||
|
? $"https://www.thetvdb.com/banners/{series.Poster}"
|
||||||
|
: null,
|
||||||
|
[Images.Thumbnail] = !string.IsNullOrEmpty(series.FanArt)
|
||||||
|
? $"https://www.thetvdb.com/banners/{series.FanArt}"
|
||||||
|
: null
|
||||||
|
},
|
||||||
Genres = series.Genre.Select(y => new Genre(y)).ToList(),
|
Genres = series.Genre.Select(y => new Genre(y)).ToList(),
|
||||||
ExternalIDs = new[]
|
ExternalIDs = new[]
|
||||||
{
|
{
|
||||||
new MetadataID<Show>
|
new MetadataID
|
||||||
{
|
{
|
||||||
DataID = series.Id.ToString(),
|
DataID = series.Id.ToString(),
|
||||||
Link = $"https://www.thetvdb.com/series/{series.Slug}",
|
Link = $"https://www.thetvdb.com/series/{series.Slug}",
|
||||||
Second = provider
|
Provider = provider
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@ -103,25 +116,20 @@ namespace Kyoo.TheTvdb
|
|||||||
/// Convert a tvdb actor to a kyoo <see cref="PeopleRole"/>.
|
/// Convert a tvdb actor to a kyoo <see cref="PeopleRole"/>.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="actor">The actor to convert</param>
|
/// <param name="actor">The actor to convert</param>
|
||||||
/// <param name="provider">The provider representing the tvdb inside kyoo</param>
|
|
||||||
/// <returns>A people role representing the given actor in the role they played.</returns>
|
/// <returns>A people role representing the given actor in the role they played.</returns>
|
||||||
public static PeopleRole ToPeopleRole(this Actor actor, Provider provider)
|
public static PeopleRole ToPeopleRole(this Actor actor)
|
||||||
{
|
{
|
||||||
return new()
|
return new PeopleRole
|
||||||
{
|
{
|
||||||
People = new People
|
People = new People
|
||||||
{
|
{
|
||||||
Slug = Utility.ToSlug(actor.Name),
|
Slug = Utility.ToSlug(actor.Name),
|
||||||
Name = actor.Name,
|
Name = actor.Name,
|
||||||
Poster = actor.Image != null ? $"https://www.thetvdb.com/banners/{actor.Image}" : null,
|
Images = new Dictionary<int, string>
|
||||||
ExternalIDs = new []
|
|
||||||
{
|
{
|
||||||
new MetadataID<People>()
|
[Images.Poster] = !string.IsNullOrEmpty(actor.Image)
|
||||||
{
|
? $"https://www.thetvdb.com/banners/{actor.Image}"
|
||||||
DataID = actor.Id.ToString(),
|
: null
|
||||||
Link = $"https://www.thetvdb.com/people/{actor.Id}",
|
|
||||||
Second = provider
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
Role = actor.Role,
|
Role = actor.Role,
|
||||||
@ -137,21 +145,26 @@ namespace Kyoo.TheTvdb
|
|||||||
/// <returns>A episode representing the given tvdb episode.</returns>
|
/// <returns>A episode representing the given tvdb episode.</returns>
|
||||||
public static Episode ToEpisode(this EpisodeRecord episode, Provider provider)
|
public static Episode ToEpisode(this EpisodeRecord episode, Provider provider)
|
||||||
{
|
{
|
||||||
return new()
|
return new Episode
|
||||||
{
|
{
|
||||||
SeasonNumber = episode.AiredSeason,
|
SeasonNumber = episode.AiredSeason,
|
||||||
EpisodeNumber = episode.AiredEpisodeNumber,
|
EpisodeNumber = episode.AiredEpisodeNumber,
|
||||||
AbsoluteNumber = episode.AbsoluteNumber,
|
AbsoluteNumber = episode.AbsoluteNumber,
|
||||||
Title = episode.EpisodeName,
|
Title = episode.EpisodeName,
|
||||||
Overview = episode.Overview,
|
Overview = episode.Overview,
|
||||||
Thumb = episode.Filename != null ? $"https://www.thetvdb.com/banners/{episode.Filename}" : null,
|
Images = new Dictionary<int, string>
|
||||||
|
{
|
||||||
|
[Images.Thumbnail] = !string.IsNullOrEmpty(episode.Filename)
|
||||||
|
? $"https://www.thetvdb.com/banners/{episode.Filename}"
|
||||||
|
: null
|
||||||
|
},
|
||||||
ExternalIDs = new[]
|
ExternalIDs = new[]
|
||||||
{
|
{
|
||||||
new MetadataID<Episode>
|
new MetadataID
|
||||||
{
|
{
|
||||||
DataID = episode.Id.ToString(),
|
DataID = episode.Id.ToString(),
|
||||||
Link = $"https://www.thetvdb.com/series/{episode.SeriesId}/episodes/{episode.Id}",
|
Link = $"https://www.thetvdb.com/series/{episode.SeriesId}/episodes/{episode.Id}",
|
||||||
Second = provider
|
Provider = provider
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
@ -32,8 +32,10 @@ namespace Kyoo.TheTvdb
|
|||||||
{
|
{
|
||||||
Slug = "the-tvdb",
|
Slug = "the-tvdb",
|
||||||
Name = "TheTVDB",
|
Name = "TheTVDB",
|
||||||
LogoExtension = "png",
|
Images = new Dictionary<int, string>
|
||||||
Logo = "https://www.thetvdb.com/images/logo.png"
|
{
|
||||||
|
[Images.Logo] = "https://www.thetvdb.com/images/logo.png"
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
@ -93,7 +95,7 @@ namespace Kyoo.TheTvdb
|
|||||||
Show ret = series.Data.ToShow(Provider);
|
Show ret = series.Data.ToShow(Provider);
|
||||||
|
|
||||||
TvDbResponse<Actor[]> people = await _client.Series.GetActorsAsync(id);
|
TvDbResponse<Actor[]> people = await _client.Series.GetActorsAsync(id);
|
||||||
ret.People = people.Data.Select(x => x.ToPeopleRole(Provider)).ToArray();
|
ret.People = people.Data.Select(x => x.ToPeopleRole()).ToArray();
|
||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1 +1 @@
|
|||||||
Subproject commit c037270d3339fcf0075984a089f353c5c332a751
|
Subproject commit dca10903ff54a8999732695b5c2a0a5c94f85200
|
18
Kyoo.sln
18
Kyoo.sln
@ -5,8 +5,6 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Kyoo.Common", "Kyoo.Common\
|
|||||||
EndProject
|
EndProject
|
||||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Kyoo.CommonAPI", "Kyoo.CommonAPI\Kyoo.CommonAPI.csproj", "{6F91B645-F785-46BB-9C4F-1EFC83E489B6}"
|
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Kyoo.CommonAPI", "Kyoo.CommonAPI\Kyoo.CommonAPI.csproj", "{6F91B645-F785-46BB-9C4F-1EFC83E489B6}"
|
||||||
EndProject
|
EndProject
|
||||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Kyoo.Tests", "Kyoo.Tests\Kyoo.Tests.csproj", "{D179D5FF-9F75-4B27-8E27-0DBDF1806611}"
|
|
||||||
EndProject
|
|
||||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Kyoo.Postgresql", "Kyoo.Postgresql\Kyoo.Postgresql.csproj", "{3213C96D-0BF3-460B-A8B5-B9977229408A}"
|
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Kyoo.Postgresql", "Kyoo.Postgresql\Kyoo.Postgresql.csproj", "{3213C96D-0BF3-460B-A8B5-B9977229408A}"
|
||||||
EndProject
|
EndProject
|
||||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Kyoo.Authentication", "Kyoo.Authentication\Kyoo.Authentication.csproj", "{7A841335-6523-47DB-9717-80AA7BD943FD}"
|
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Kyoo.Authentication", "Kyoo.Authentication\Kyoo.Authentication.csproj", "{7A841335-6523-47DB-9717-80AA7BD943FD}"
|
||||||
@ -15,6 +13,10 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Kyoo.SqLite", "Kyoo.SqLite\
|
|||||||
EndProject
|
EndProject
|
||||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Kyoo.TheTvdb", "Kyoo.TheTvdb\Kyoo.TheTvdb.csproj", "{D06BF829-23F5-40F3-A62D-627D9F4B4D6C}"
|
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Kyoo.TheTvdb", "Kyoo.TheTvdb\Kyoo.TheTvdb.csproj", "{D06BF829-23F5-40F3-A62D-627D9F4B4D6C}"
|
||||||
EndProject
|
EndProject
|
||||||
|
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Kyoo.TheMovieDb", "Kyoo.TheMovieDb\Kyoo.TheMovieDb.csproj", "{BAB270D4-E0EA-4329-BA65-512FDAB01001}"
|
||||||
|
EndProject
|
||||||
|
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Kyoo.Tests", "tests\Kyoo.Tests\Kyoo.Tests.csproj", "{0C8AA7EA-E723-4532-852F-35AA4E8AFED5}"
|
||||||
|
EndProject
|
||||||
Global
|
Global
|
||||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||||
Debug|Any CPU = Debug|Any CPU
|
Debug|Any CPU = Debug|Any CPU
|
||||||
@ -33,10 +35,6 @@ Global
|
|||||||
{6F91B645-F785-46BB-9C4F-1EFC83E489B6}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
{6F91B645-F785-46BB-9C4F-1EFC83E489B6}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||||
{6F91B645-F785-46BB-9C4F-1EFC83E489B6}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
{6F91B645-F785-46BB-9C4F-1EFC83E489B6}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||||
{6F91B645-F785-46BB-9C4F-1EFC83E489B6}.Release|Any CPU.Build.0 = Release|Any CPU
|
{6F91B645-F785-46BB-9C4F-1EFC83E489B6}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||||
{D179D5FF-9F75-4B27-8E27-0DBDF1806611}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
|
||||||
{D179D5FF-9F75-4B27-8E27-0DBDF1806611}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
|
||||||
{D179D5FF-9F75-4B27-8E27-0DBDF1806611}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
|
||||||
{D179D5FF-9F75-4B27-8E27-0DBDF1806611}.Release|Any CPU.Build.0 = Release|Any CPU
|
|
||||||
{3213C96D-0BF3-460B-A8B5-B9977229408A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
{3213C96D-0BF3-460B-A8B5-B9977229408A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||||
{3213C96D-0BF3-460B-A8B5-B9977229408A}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
{3213C96D-0BF3-460B-A8B5-B9977229408A}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||||
{3213C96D-0BF3-460B-A8B5-B9977229408A}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
{3213C96D-0BF3-460B-A8B5-B9977229408A}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||||
@ -53,5 +51,13 @@ Global
|
|||||||
{D06BF829-23F5-40F3-A62D-627D9F4B4D6C}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
{D06BF829-23F5-40F3-A62D-627D9F4B4D6C}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||||
{D06BF829-23F5-40F3-A62D-627D9F4B4D6C}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
{D06BF829-23F5-40F3-A62D-627D9F4B4D6C}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||||
{D06BF829-23F5-40F3-A62D-627D9F4B4D6C}.Release|Any CPU.Build.0 = Release|Any CPU
|
{D06BF829-23F5-40F3-A62D-627D9F4B4D6C}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||||
|
{BAB270D4-E0EA-4329-BA65-512FDAB01001}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||||
|
{BAB270D4-E0EA-4329-BA65-512FDAB01001}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||||
|
{BAB270D4-E0EA-4329-BA65-512FDAB01001}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||||
|
{BAB270D4-E0EA-4329-BA65-512FDAB01001}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||||
|
{0C8AA7EA-E723-4532-852F-35AA4E8AFED5}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||||
|
{0C8AA7EA-E723-4532-852F-35AA4E8AFED5}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||||
|
{0C8AA7EA-E723-4532-852F-35AA4E8AFED5}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||||
|
{0C8AA7EA-E723-4532-852F-35AA4E8AFED5}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||||
EndGlobalSection
|
EndGlobalSection
|
||||||
EndGlobal
|
EndGlobal
|
||||||
|
@ -1,11 +0,0 @@
|
|||||||
<wpf:ResourceDictionary xml:space="preserve" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:s="clr-namespace:System;assembly=mscorlib" xmlns:ss="urn:shemas-jetbrains-com:settings-storage-xaml" xmlns:wpf="http://schemas.microsoft.com/winfx/2006/xaml/presentation">
|
|
||||||
<s:String x:Key="/Default/CodeStyle/CodeFormatting/CSharpFormat/INDENT_STYLE/@EntryValue">Tab</s:String>
|
|
||||||
<s:String x:Key="/Default/CodeStyle/CSharpVarKeywordUsage/ForBuiltInTypes/@EntryValue">UseExplicitType</s:String>
|
|
||||||
<s:String x:Key="/Default/CodeStyle/CSharpVarKeywordUsage/ForOtherTypes/@EntryValue">UseExplicitType</s:String>
|
|
||||||
<s:String x:Key="/Default/CodeStyle/CSharpVarKeywordUsage/ForSimpleTypes/@EntryValue">UseExplicitType</s:String>
|
|
||||||
<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/Abbreviations/=API/@EntryIndexedValue">API</s:String>
|
|
||||||
<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/Abbreviations/=DB/@EntryIndexedValue">DB</s:String>
|
|
||||||
<s:Boolean x:Key="/Default/Environment/SettingsMigration/IsMigratorApplied/=JetBrains_002EReSharper_002EPsi_002ECSharp_002ECodeStyle_002ECSharpKeepExistingMigration/@EntryIndexedValue">True</s:Boolean>
|
|
||||||
<s:Boolean x:Key="/Default/Environment/SettingsMigration/IsMigratorApplied/=JetBrains_002EReSharper_002EPsi_002ECSharp_002ECodeStyle_002ECSharpPlaceEmbeddedOnSameLineMigration/@EntryIndexedValue">True</s:Boolean>
|
|
||||||
<s:Boolean x:Key="/Default/Environment/SettingsMigration/IsMigratorApplied/=JetBrains_002EReSharper_002EPsi_002ECSharp_002ECodeStyle_002ECSharpUseContinuousIndentInsideBracesMigration/@EntryIndexedValue">True</s:Boolean>
|
|
||||||
<s:Boolean x:Key="/Default/Environment/SettingsMigration/IsMigratorApplied/=JetBrains_002EReSharper_002EPsi_002ECSharp_002ECodeStyle_002ESettingsUpgrade_002EMigrateBlankLinesAroundFieldToBlankLinesAroundProperty/@EntryIndexedValue">True</s:Boolean></wpf:ResourceDictionary>
|
|
@ -8,7 +8,9 @@ using Autofac.Features.Metadata;
|
|||||||
using JetBrains.Annotations;
|
using JetBrains.Annotations;
|
||||||
using Kyoo.Common.Models.Attributes;
|
using Kyoo.Common.Models.Attributes;
|
||||||
using Kyoo.Models;
|
using Kyoo.Models;
|
||||||
|
using Kyoo.Models.Options;
|
||||||
using Microsoft.AspNetCore.Mvc;
|
using Microsoft.AspNetCore.Mvc;
|
||||||
|
using Microsoft.Extensions.Options;
|
||||||
|
|
||||||
namespace Kyoo.Controllers
|
namespace Kyoo.Controllers
|
||||||
{
|
{
|
||||||
@ -23,14 +25,31 @@ namespace Kyoo.Controllers
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
private readonly ICollection<Meta<Func<IFileSystem>, FileSystemMetadataAttribute>> _fileSystems;
|
private readonly ICollection<Meta<Func<IFileSystem>, FileSystemMetadataAttribute>> _fileSystems;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The library manager used to load shows to retrieve their path
|
||||||
|
/// (only if the option is set to metadata in show)
|
||||||
|
/// </summary>
|
||||||
|
private readonly ILibraryManager _libraryManager;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Options to check if the metadata should be kept in the show directory or in a kyoo's directory.
|
||||||
|
/// </summary>
|
||||||
|
private readonly IOptionsMonitor<BasicOptions> _options;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Create a new <see cref="FileSystemComposite"/> from a list of <see cref="IFileSystem"/> mapped to their
|
/// Create a new <see cref="FileSystemComposite"/> from a list of <see cref="IFileSystem"/> mapped to their
|
||||||
/// metadata.
|
/// metadata.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="fileSystems">The list of filesystem mapped to their metadata.</param>
|
/// <param name="fileSystems">The list of filesystem mapped to their metadata.</param>
|
||||||
public FileSystemComposite(ICollection<Meta<Func<IFileSystem>, FileSystemMetadataAttribute>> fileSystems)
|
/// <param name="libraryManager">The library manager used to load shows to retrieve their path.</param>
|
||||||
|
/// <param name="options">The options to use.</param>
|
||||||
|
public FileSystemComposite(ICollection<Meta<Func<IFileSystem>, FileSystemMetadataAttribute>> fileSystems,
|
||||||
|
ILibraryManager libraryManager,
|
||||||
|
IOptionsMonitor<BasicOptions> options)
|
||||||
{
|
{
|
||||||
_fileSystems = fileSystems;
|
_fileSystems = fileSystems;
|
||||||
|
_libraryManager = libraryManager;
|
||||||
|
_options = options;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@ -88,6 +107,15 @@ namespace Kyoo.Controllers
|
|||||||
.GetReader(relativePath);
|
.GetReader(relativePath);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public Task<Stream> GetReader(string path, AsyncRef<string> mime)
|
||||||
|
{
|
||||||
|
if (path == null)
|
||||||
|
throw new ArgumentNullException(nameof(path));
|
||||||
|
return _GetFileSystemForPath(path, out string relativePath)
|
||||||
|
.GetReader(relativePath, mime);
|
||||||
|
}
|
||||||
|
|
||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
public Task<Stream> NewFile(string path)
|
public Task<Stream> NewFile(string path)
|
||||||
{
|
{
|
||||||
@ -132,12 +160,41 @@ namespace Kyoo.Controllers
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
public string GetExtraDirectory(Show show)
|
public async Task<string> GetExtraDirectory<T>(T resource)
|
||||||
{
|
{
|
||||||
if (show == null)
|
switch (resource)
|
||||||
throw new ArgumentNullException(nameof(show));
|
{
|
||||||
return _GetFileSystemForPath(show.Path, out string _)
|
case Season season:
|
||||||
.GetExtraDirectory(show);
|
await _libraryManager.Load(season, x => x.Show);
|
||||||
|
break;
|
||||||
|
case Episode episode:
|
||||||
|
await _libraryManager.Load(episode, x => x.Show);
|
||||||
|
break;
|
||||||
|
case Track track:
|
||||||
|
await _libraryManager.Load(track, x => x.Episode);
|
||||||
|
await _libraryManager.Load(track.Episode, x => x.Show);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
IFileSystem fs = resource switch
|
||||||
|
{
|
||||||
|
Show show => _GetFileSystemForPath(show.Path, out string _),
|
||||||
|
Season season => _GetFileSystemForPath(season.Show.Path, out string _),
|
||||||
|
Episode episode => _GetFileSystemForPath(episode.Show.Path, out string _),
|
||||||
|
Track track => _GetFileSystemForPath(track.Episode.Show.Path, out string _),
|
||||||
|
_ => _GetFileSystemForPath(_options.CurrentValue.MetadataPath, out string _)
|
||||||
|
};
|
||||||
|
string path = await fs.GetExtraDirectory(resource)
|
||||||
|
?? resource switch
|
||||||
|
{
|
||||||
|
Season season => await GetExtraDirectory(season.Show),
|
||||||
|
Episode episode => await GetExtraDirectory(episode.Show),
|
||||||
|
Track track => await GetExtraDirectory(track.Episode),
|
||||||
|
IResource res => Combine(_options.CurrentValue.MetadataPath,
|
||||||
|
typeof(T).Name.ToLowerInvariant(), res.Slug),
|
||||||
|
_ => Combine(_options.CurrentValue.MetadataPath, typeof(T).Name.ToLowerInvariant())
|
||||||
|
};
|
||||||
|
return await CreateDirectory(path);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -1,10 +1,10 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
|
using System.Diagnostics.CodeAnalysis;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using System.Net.Http;
|
using System.Net.Http;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using Kyoo.Common.Models.Attributes;
|
using Kyoo.Common.Models.Attributes;
|
||||||
using Kyoo.Models;
|
|
||||||
using Microsoft.AspNetCore.Mvc;
|
using Microsoft.AspNetCore.Mvc;
|
||||||
|
|
||||||
namespace Kyoo.Controllers
|
namespace Kyoo.Controllers
|
||||||
@ -45,6 +45,16 @@ namespace Kyoo.Controllers
|
|||||||
return client.GetStreamAsync(path);
|
return client.GetStreamAsync(path);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public async Task<Stream> GetReader(string path, AsyncRef<string> mime)
|
||||||
|
{
|
||||||
|
HttpClient client = _clientFactory.CreateClient();
|
||||||
|
HttpResponseMessage response = await client.GetAsync(path);
|
||||||
|
response.EnsureSuccessStatusCode();
|
||||||
|
mime.Value = response.Content.Headers.ContentType?.MediaType;
|
||||||
|
return await response.Content.ReadAsStreamAsync();
|
||||||
|
}
|
||||||
|
|
||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
public Task<Stream> NewFile(string path)
|
public Task<Stream> NewFile(string path)
|
||||||
{
|
{
|
||||||
@ -76,7 +86,7 @@ namespace Kyoo.Controllers
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
public string GetExtraDirectory(Show show)
|
public Task<string> GetExtraDirectory<T>(T resource)
|
||||||
{
|
{
|
||||||
throw new NotSupportedException("Extras can not be stored inside an http filesystem.");
|
throw new NotSupportedException("Extras can not be stored inside an http filesystem.");
|
||||||
}
|
}
|
||||||
@ -85,6 +95,8 @@ namespace Kyoo.Controllers
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// An <see cref="IActionResult"/> to proxy an http request.
|
/// An <see cref="IActionResult"/> to proxy an http request.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
// TODO remove this suppress message once the class has been implemented.
|
||||||
|
[SuppressMessage("ReSharper", "NotAccessedField.Local")]
|
||||||
public class HttpForwardResult : IActionResult
|
public class HttpForwardResult : IActionResult
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
@ -4,8 +4,10 @@ using System.IO;
|
|||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using Kyoo.Common.Models.Attributes;
|
using Kyoo.Common.Models.Attributes;
|
||||||
using Kyoo.Models;
|
using Kyoo.Models;
|
||||||
|
using Kyoo.Models.Options;
|
||||||
using Microsoft.AspNetCore.Mvc;
|
using Microsoft.AspNetCore.Mvc;
|
||||||
using Microsoft.AspNetCore.StaticFiles;
|
using Microsoft.AspNetCore.StaticFiles;
|
||||||
|
using Microsoft.Extensions.Options;
|
||||||
|
|
||||||
namespace Kyoo.Controllers
|
namespace Kyoo.Controllers
|
||||||
{
|
{
|
||||||
@ -20,6 +22,20 @@ namespace Kyoo.Controllers
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
private FileExtensionContentTypeProvider _provider;
|
private FileExtensionContentTypeProvider _provider;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Options to check if the metadata should be kept in the show directory or in a kyoo's directory.
|
||||||
|
/// </summary>
|
||||||
|
private readonly IOptionsMonitor<BasicOptions> _options;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Create a new <see cref="LocalFileSystem"/> with the specified options.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="options">The options to use.</param>
|
||||||
|
public LocalFileSystem(IOptionsMonitor<BasicOptions> options)
|
||||||
|
{
|
||||||
|
_options = options;
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Get the content type of a file using it's extension.
|
/// Get the content type of a file using it's extension.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@ -63,6 +79,16 @@ namespace Kyoo.Controllers
|
|||||||
return Task.FromResult<Stream>(File.OpenRead(path));
|
return Task.FromResult<Stream>(File.OpenRead(path));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public Task<Stream> GetReader(string path, AsyncRef<string> mime)
|
||||||
|
{
|
||||||
|
if (path == null)
|
||||||
|
throw new ArgumentNullException(nameof(path));
|
||||||
|
_provider.TryGetContentType(path, out string mimeValue);
|
||||||
|
mime.Value = mimeValue;
|
||||||
|
return Task.FromResult<Stream>(File.OpenRead(path));
|
||||||
|
}
|
||||||
|
|
||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
public Task<Stream> NewFile(string path)
|
public Task<Stream> NewFile(string path)
|
||||||
{
|
{
|
||||||
@ -104,11 +130,18 @@ namespace Kyoo.Controllers
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
public string GetExtraDirectory(Show show)
|
public Task<string> GetExtraDirectory<T>(T resource)
|
||||||
{
|
{
|
||||||
string path = Path.Combine(show.Path, "Extra");
|
if (!_options.CurrentValue.MetadataInShow)
|
||||||
Directory.CreateDirectory(path);
|
return Task.FromResult<string>(null);
|
||||||
return path;
|
return Task.FromResult(resource switch
|
||||||
|
{
|
||||||
|
Show show => Combine(show.Path, "Extra"),
|
||||||
|
Season season => Combine(season.Show.Path, "Extra"),
|
||||||
|
Episode episode => Combine(episode.Show.Path, "Extra"),
|
||||||
|
Track track => Combine(track.Episode.Show.Path, "Extra"),
|
||||||
|
_ => null
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -18,6 +18,11 @@ namespace Kyoo.Controllers
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
private readonly DatabaseContext _database;
|
private readonly DatabaseContext _database;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// A provider repository to handle externalID creation and deletion
|
||||||
|
/// </summary>
|
||||||
|
private readonly IProviderRepository _providers;
|
||||||
|
|
||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
protected override Expression<Func<Collection, object>> DefaultSort => x => x.Name;
|
protected override Expression<Func<Collection, object>> DefaultSort => x => x.Name;
|
||||||
|
|
||||||
@ -25,17 +30,19 @@ namespace Kyoo.Controllers
|
|||||||
/// Create a new <see cref="CollectionRepository"/>.
|
/// Create a new <see cref="CollectionRepository"/>.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="database">The database handle to use</param>
|
/// <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)
|
: base(database)
|
||||||
{
|
{
|
||||||
_database = database;
|
_database = database;
|
||||||
|
_providers = providers;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
public override async Task<ICollection<Collection>> Search(string query)
|
public override async Task<ICollection<Collection>> Search(string query)
|
||||||
{
|
{
|
||||||
return await _database.Collections
|
return await _database.Collections
|
||||||
.Where(_database.Like<Collection>(x => x.Name, $"%{query}%"))
|
.Where(_database.Like<Collection>(x => x.Name + " " + x.Slug, $"%{query}%"))
|
||||||
.OrderBy(DefaultSort)
|
.OrderBy(DefaultSort)
|
||||||
.Take(20)
|
.Take(20)
|
||||||
.ToListAsync();
|
.ToListAsync();
|
||||||
@ -50,6 +57,40 @@ namespace Kyoo.Controllers
|
|||||||
return obj;
|
return obj;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
protected override async Task Validate(Collection resource)
|
||||||
|
{
|
||||||
|
await base.Validate(resource);
|
||||||
|
|
||||||
|
if (string.IsNullOrEmpty(resource.Slug))
|
||||||
|
throw new ArgumentException("The collection's slug must be set and not empty");
|
||||||
|
if (string.IsNullOrEmpty(resource.Name))
|
||||||
|
throw new ArgumentException("The collection's name must be set and not empty");
|
||||||
|
|
||||||
|
if (resource.ExternalIDs != null)
|
||||||
|
{
|
||||||
|
foreach (MetadataID id in resource.ExternalIDs)
|
||||||
|
{
|
||||||
|
id.Provider = _database.LocalEntity<Provider>(id.Provider.Slug)
|
||||||
|
?? await _providers.CreateIfNotExists(id.Provider);
|
||||||
|
id.ProviderID = id.Provider.ID;
|
||||||
|
}
|
||||||
|
_database.MetadataIds<Collection>().AttachRange(resource.ExternalIDs);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
protected override async Task EditRelations(Collection resource, Collection changed, bool resetOld)
|
||||||
|
{
|
||||||
|
await Validate(changed);
|
||||||
|
|
||||||
|
if (changed.ExternalIDs != null || resetOld)
|
||||||
|
{
|
||||||
|
await Database.Entry(resource).Collection(x => x.ExternalIDs).LoadAsync();
|
||||||
|
resource.ExternalIDs = changed.ExternalIDs;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
public override async Task Delete(Collection obj)
|
public override async Task Delete(Collection obj)
|
||||||
{
|
{
|
||||||
|
@ -99,7 +99,7 @@ namespace Kyoo.Controllers
|
|||||||
public override async Task<ICollection<Episode>> Search(string query)
|
public override async Task<ICollection<Episode>> Search(string query)
|
||||||
{
|
{
|
||||||
return await _database.Episodes
|
return await _database.Episodes
|
||||||
.Where(x => x.EpisodeNumber != null)
|
.Where(x => x.EpisodeNumber != null || x.AbsoluteNumber != null)
|
||||||
.Where(_database.Like<Episode>(x => x.Title, $"%{query}%"))
|
.Where(_database.Like<Episode>(x => x.Title, $"%{query}%"))
|
||||||
.OrderBy(DefaultSort)
|
.OrderBy(DefaultSort)
|
||||||
.Take(20)
|
.Take(20)
|
||||||
@ -111,7 +111,6 @@ namespace Kyoo.Controllers
|
|||||||
{
|
{
|
||||||
await base.Create(obj);
|
await base.Create(obj);
|
||||||
_database.Entry(obj).State = EntityState.Added;
|
_database.Entry(obj).State = EntityState.Added;
|
||||||
obj.ExternalIDs.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 await ValidateTracks(obj);
|
return await ValidateTracks(obj);
|
||||||
}
|
}
|
||||||
@ -119,8 +118,7 @@ namespace Kyoo.Controllers
|
|||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
protected override async Task EditRelations(Episode resource, Episode changed, bool resetOld)
|
protected override async Task EditRelations(Episode resource, Episode changed, bool resetOld)
|
||||||
{
|
{
|
||||||
if (resource.ShowID <= 0)
|
await Validate(changed);
|
||||||
throw new InvalidOperationException($"Can't store an episode not related to any show (showID: {resource.ShowID}).");
|
|
||||||
|
|
||||||
if (changed.Tracks != null || resetOld)
|
if (changed.Tracks != null || resetOld)
|
||||||
{
|
{
|
||||||
@ -134,8 +132,6 @@ namespace Kyoo.Controllers
|
|||||||
await Database.Entry(resource).Collection(x => x.ExternalIDs).LoadAsync();
|
await Database.Entry(resource).Collection(x => x.ExternalIDs).LoadAsync();
|
||||||
resource.ExternalIDs = changed.ExternalIDs;
|
resource.ExternalIDs = changed.ExternalIDs;
|
||||||
}
|
}
|
||||||
|
|
||||||
await Validate(resource);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@ -145,12 +141,16 @@ namespace Kyoo.Controllers
|
|||||||
/// <returns>The <see cref="resource"/> parameter is returned.</returns>
|
/// <returns>The <see cref="resource"/> parameter is returned.</returns>
|
||||||
private async Task<Episode> ValidateTracks(Episode resource)
|
private async Task<Episode> ValidateTracks(Episode resource)
|
||||||
{
|
{
|
||||||
resource.Tracks = await TaskUtils.DefaultIfNull(resource.Tracks?.SelectAsync(x =>
|
if (resource.Tracks == null)
|
||||||
|
return resource;
|
||||||
|
|
||||||
|
resource.Tracks = await resource.Tracks.SelectAsync(x =>
|
||||||
{
|
{
|
||||||
x.Episode = resource;
|
x.Episode = resource;
|
||||||
x.EpisodeSlug = resource.Slug;
|
x.EpisodeSlug = resource.Slug;
|
||||||
return _tracks.Create(x);
|
return _tracks.Create(x);
|
||||||
}).ToListAsync());
|
}).ToListAsync();
|
||||||
|
_database.Tracks.AttachRange(resource.Tracks);
|
||||||
return resource;
|
return resource;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -158,12 +158,24 @@ namespace Kyoo.Controllers
|
|||||||
protected override async Task Validate(Episode resource)
|
protected override async Task Validate(Episode resource)
|
||||||
{
|
{
|
||||||
await base.Validate(resource);
|
await base.Validate(resource);
|
||||||
await resource.ExternalIDs.ForEachAsync(async x =>
|
if (resource.ShowID <= 0)
|
||||||
{
|
{
|
||||||
x.Second = await _providers.CreateIfNotExists(x.Second);
|
if (resource.Show == null)
|
||||||
x.SecondID = x.Second.ID;
|
throw new ArgumentException($"Can't store an episode not related " +
|
||||||
_database.Entry(x.Second).State = EntityState.Detached;
|
$"to any show (showID: {resource.ShowID}).");
|
||||||
});
|
resource.ShowID = resource.Show.ID;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (resource.ExternalIDs != null)
|
||||||
|
{
|
||||||
|
foreach (MetadataID id in resource.ExternalIDs)
|
||||||
|
{
|
||||||
|
id.Provider = _database.LocalEntity<Provider>(id.Provider.Slug)
|
||||||
|
?? await _providers.CreateIfNotExists(id.Provider);
|
||||||
|
id.ProviderID = id.Provider.ID;
|
||||||
|
}
|
||||||
|
_database.MetadataIds<Episode>().AttachRange(resource.ExternalIDs);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
|
@ -43,7 +43,7 @@ namespace Kyoo.Controllers
|
|||||||
public override async Task<ICollection<Library>> Search(string query)
|
public override async Task<ICollection<Library>> Search(string query)
|
||||||
{
|
{
|
||||||
return await _database.Libraries
|
return await _database.Libraries
|
||||||
.Where(_database.Like<Library>(x => x.Name, $"%{query}%"))
|
.Where(_database.Like<Library>(x => x.Name + " " + x.Slug, $"%{query}%"))
|
||||||
.OrderBy(DefaultSort)
|
.OrderBy(DefaultSort)
|
||||||
.Take(20)
|
.Take(20)
|
||||||
.ToListAsync();
|
.ToListAsync();
|
||||||
@ -54,7 +54,6 @@ namespace Kyoo.Controllers
|
|||||||
{
|
{
|
||||||
await base.Create(obj);
|
await base.Create(obj);
|
||||||
_database.Entry(obj).State = EntityState.Added;
|
_database.Entry(obj).State = EntityState.Added;
|
||||||
obj.ProviderLinks.ForEach(x => _database.Entry(x).State = EntityState.Added);
|
|
||||||
await _database.SaveChangesAsync($"Trying to insert a duplicated library (slug {obj.Slug} already exists).");
|
await _database.SaveChangesAsync($"Trying to insert a duplicated library (slug {obj.Slug} already exists).");
|
||||||
return obj;
|
return obj;
|
||||||
}
|
}
|
||||||
@ -63,20 +62,7 @@ namespace Kyoo.Controllers
|
|||||||
protected override async Task Validate(Library resource)
|
protected override async Task Validate(Library resource)
|
||||||
{
|
{
|
||||||
await base.Validate(resource);
|
await base.Validate(resource);
|
||||||
resource.ProviderLinks = resource.Providers?
|
|
||||||
.Select(x => Link.Create(resource, x))
|
|
||||||
.ToList();
|
|
||||||
await resource.ProviderLinks.ForEachAsync(async id =>
|
|
||||||
{
|
|
||||||
id.Second = await _providers.CreateIfNotExists(id.Second);
|
|
||||||
id.SecondID = id.Second.ID;
|
|
||||||
_database.Entry(id.Second).State = EntityState.Detached;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <inheritdoc />
|
|
||||||
protected override async Task EditRelations(Library resource, Library changed, bool resetOld)
|
|
||||||
{
|
|
||||||
if (string.IsNullOrEmpty(resource.Slug))
|
if (string.IsNullOrEmpty(resource.Slug))
|
||||||
throw new ArgumentException("The library's slug must be set and not empty");
|
throw new ArgumentException("The library's slug must be set and not empty");
|
||||||
if (string.IsNullOrEmpty(resource.Name))
|
if (string.IsNullOrEmpty(resource.Name))
|
||||||
@ -84,9 +70,22 @@ namespace Kyoo.Controllers
|
|||||||
if (resource.Paths == null || !resource.Paths.Any())
|
if (resource.Paths == null || !resource.Paths.Any())
|
||||||
throw new ArgumentException("The library should have a least one path.");
|
throw new ArgumentException("The library should have a least one path.");
|
||||||
|
|
||||||
if (changed.Providers != null || resetOld)
|
if (resource.Providers != null)
|
||||||
|
{
|
||||||
|
resource.Providers = await resource.Providers
|
||||||
|
.SelectAsync(x => _providers.CreateIfNotExists(x))
|
||||||
|
.ToListAsync();
|
||||||
|
_database.AttachRange(resource.Providers);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
protected override async Task EditRelations(Library resource, Library changed, bool resetOld)
|
||||||
{
|
{
|
||||||
await Validate(changed);
|
await Validate(changed);
|
||||||
|
|
||||||
|
if (changed.Providers != null || resetOld)
|
||||||
|
{
|
||||||
await Database.Entry(resource).Collection(x => x.Providers).LoadAsync();
|
await Database.Entry(resource).Collection(x => x.Providers).LoadAsync();
|
||||||
resource.Providers = changed.Providers;
|
resource.Providers = changed.Providers;
|
||||||
}
|
}
|
||||||
|
@ -62,7 +62,6 @@ namespace Kyoo.Controllers
|
|||||||
{
|
{
|
||||||
await base.Create(obj);
|
await base.Create(obj);
|
||||||
_database.Entry(obj).State = EntityState.Added;
|
_database.Entry(obj).State = EntityState.Added;
|
||||||
obj.ExternalIDs.ForEach(x => _database.Entry(x).State = EntityState.Added);
|
|
||||||
await _database.SaveChangesAsync($"Trying to insert a duplicated people (slug {obj.Slug} already exists).");
|
await _database.SaveChangesAsync($"Trying to insert a duplicated people (slug {obj.Slug} already exists).");
|
||||||
return obj;
|
return obj;
|
||||||
}
|
}
|
||||||
@ -71,23 +70,35 @@ namespace Kyoo.Controllers
|
|||||||
protected override async Task Validate(People resource)
|
protected override async Task Validate(People resource)
|
||||||
{
|
{
|
||||||
await base.Validate(resource);
|
await base.Validate(resource);
|
||||||
await resource.ExternalIDs.ForEachAsync(async id =>
|
|
||||||
|
if (resource.ExternalIDs != null)
|
||||||
{
|
{
|
||||||
id.Second = await _providers.CreateIfNotExists(id.Second);
|
foreach (MetadataID id in resource.ExternalIDs)
|
||||||
id.SecondID = id.Second.ID;
|
|
||||||
_database.Entry(id.Second).State = EntityState.Detached;
|
|
||||||
});
|
|
||||||
await resource.Roles.ForEachAsync(async role =>
|
|
||||||
{
|
{
|
||||||
role.Show = await _shows.Value.CreateIfNotExists(role.Show);
|
id.Provider = _database.LocalEntity<Provider>(id.Provider.Slug)
|
||||||
|
?? await _providers.CreateIfNotExists(id.Provider);
|
||||||
|
id.ProviderID = id.Provider.ID;
|
||||||
|
}
|
||||||
|
_database.MetadataIds<People>().AttachRange(resource.ExternalIDs);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (resource.Roles != null)
|
||||||
|
{
|
||||||
|
foreach (PeopleRole role in resource.Roles)
|
||||||
|
{
|
||||||
|
role.Show = _database.LocalEntity<Show>(role.Show.Slug)
|
||||||
|
?? await _shows.Value.CreateIfNotExists(role.Show);
|
||||||
role.ShowID = role.Show.ID;
|
role.ShowID = role.Show.ID;
|
||||||
_database.Entry(role.Show).State = EntityState.Detached;
|
_database.Entry(role).State = EntityState.Added;
|
||||||
});
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
protected override async Task EditRelations(People resource, People changed, bool resetOld)
|
protected override async Task EditRelations(People resource, People changed, bool resetOld)
|
||||||
{
|
{
|
||||||
|
await Validate(changed);
|
||||||
|
|
||||||
if (changed.Roles != null || resetOld)
|
if (changed.Roles != null || resetOld)
|
||||||
{
|
{
|
||||||
await Database.Entry(resource).Collection(x => x.Roles).LoadAsync();
|
await Database.Entry(resource).Collection(x => x.Roles).LoadAsync();
|
||||||
@ -98,9 +109,7 @@ namespace Kyoo.Controllers
|
|||||||
{
|
{
|
||||||
await Database.Entry(resource).Collection(x => x.ExternalIDs).LoadAsync();
|
await Database.Entry(resource).Collection(x => x.ExternalIDs).LoadAsync();
|
||||||
resource.ExternalIDs = changed.ExternalIDs;
|
resource.ExternalIDs = changed.ExternalIDs;
|
||||||
|
|
||||||
}
|
}
|
||||||
await base.EditRelations(resource, changed, resetOld);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
|
@ -18,12 +18,9 @@ namespace Kyoo.Controllers
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
private readonly DatabaseContext _database;
|
private readonly DatabaseContext _database;
|
||||||
|
|
||||||
/// <inheritdoc />
|
|
||||||
protected override Expression<Func<Provider, object>> DefaultSort => x => x.Slug;
|
|
||||||
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Create a new <see cref="ProviderRepository"/>.
|
/// Create a new <see cref="ProviderRepository" />.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="database">The database handle</param>
|
/// <param name="database">The database handle</param>
|
||||||
public ProviderRepository(DatabaseContext database)
|
public ProviderRepository(DatabaseContext database)
|
||||||
@ -32,6 +29,9 @@ namespace Kyoo.Controllers
|
|||||||
_database = database;
|
_database = database;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
protected override Expression<Func<Provider, object>> DefaultSort => x => x.Slug;
|
||||||
|
|
||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
public override async Task<ICollection<Provider>> Search(string query)
|
public override async Task<ICollection<Provider>> Search(string query)
|
||||||
{
|
{
|
||||||
@ -47,7 +47,8 @@ namespace Kyoo.Controllers
|
|||||||
{
|
{
|
||||||
await base.Create(obj);
|
await base.Create(obj);
|
||||||
_database.Entry(obj).State = EntityState.Added;
|
_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;
|
return obj;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -62,14 +63,15 @@ namespace Kyoo.Controllers
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
public Task<ICollection<MetadataID<T>>> GetMetadataID<T>(Expression<Func<MetadataID<T>, bool>> where = null,
|
public Task<ICollection<MetadataID>> GetMetadataID<T>(Expression<Func<MetadataID, bool>> where = null,
|
||||||
Sort<MetadataID<T>> sort = default,
|
Sort<MetadataID> sort = default,
|
||||||
Pagination limit = default)
|
Pagination limit = default)
|
||||||
where T : class, IResource
|
where T : class, IMetadata
|
||||||
{
|
{
|
||||||
return ApplyFilters(_database.MetadataIds<T>().Include(y => y.Second),
|
return ApplyFilters(_database.MetadataIds<T>()
|
||||||
x => _database.MetadataIds<T>().FirstOrDefaultAsync(y => y.FirstID == x),
|
.Include(y => y.Provider),
|
||||||
x => x.FirstID,
|
x => _database.MetadataIds<T>().FirstOrDefaultAsync(y => y.ResourceID == x),
|
||||||
|
x => x.ResourceID,
|
||||||
where,
|
where,
|
||||||
sort,
|
sort,
|
||||||
limit);
|
limit);
|
||||||
|
@ -87,7 +87,6 @@ namespace Kyoo.Controllers
|
|||||||
{
|
{
|
||||||
await base.Create(obj);
|
await base.Create(obj);
|
||||||
_database.Entry(obj).State = EntityState.Added;
|
_database.Entry(obj).State = EntityState.Added;
|
||||||
obj.ExternalIDs.ForEach(x => _database.Entry(x).State = EntityState.Added);
|
|
||||||
await _database.SaveChangesAsync($"Trying to insert a duplicated season (slug {obj.Slug} already exists).");
|
await _database.SaveChangesAsync($"Trying to insert a duplicated season (slug {obj.Slug} already exists).");
|
||||||
return obj;
|
return obj;
|
||||||
}
|
}
|
||||||
@ -95,32 +94,37 @@ namespace Kyoo.Controllers
|
|||||||
/// <inheritdoc/>
|
/// <inheritdoc/>
|
||||||
protected override async Task Validate(Season resource)
|
protected override async Task Validate(Season resource)
|
||||||
{
|
{
|
||||||
|
await base.Validate(resource);
|
||||||
if (resource.ShowID <= 0)
|
if (resource.ShowID <= 0)
|
||||||
{
|
{
|
||||||
if (resource.Show == null)
|
if (resource.Show == null)
|
||||||
throw new InvalidOperationException(
|
throw new ArgumentException(
|
||||||
$"Can't store a season not related to any show (showID: {resource.ShowID}).");
|
$"Can't store a season not related to any show (showID: {resource.ShowID}).");
|
||||||
resource.ShowID = resource.Show.ID;
|
resource.ShowID = resource.Show.ID;
|
||||||
}
|
}
|
||||||
|
|
||||||
await base.Validate(resource);
|
if (resource.ExternalIDs != null)
|
||||||
await resource.ExternalIDs.ForEachAsync(async id =>
|
|
||||||
{
|
{
|
||||||
id.Second = await _providers.CreateIfNotExists(id.Second);
|
foreach (MetadataID id in resource.ExternalIDs)
|
||||||
id.SecondID = id.Second.ID;
|
{
|
||||||
_database.Entry(id.Second).State = EntityState.Detached;
|
id.Provider = _database.LocalEntity<Provider>(id.Provider.Slug)
|
||||||
});
|
?? await _providers.CreateIfNotExists(id.Provider);
|
||||||
|
id.ProviderID = id.Provider.ID;
|
||||||
|
}
|
||||||
|
_database.MetadataIds<Season>().AttachRange(resource.ExternalIDs);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <inheritdoc/>
|
/// <inheritdoc/>
|
||||||
protected override async Task EditRelations(Season resource, Season changed, bool resetOld)
|
protected override async Task EditRelations(Season resource, Season changed, bool resetOld)
|
||||||
{
|
{
|
||||||
|
await Validate(changed);
|
||||||
|
|
||||||
if (changed.ExternalIDs != null || resetOld)
|
if (changed.ExternalIDs != null || resetOld)
|
||||||
{
|
{
|
||||||
await Database.Entry(resource).Collection(x => x.ExternalIDs).LoadAsync();
|
await Database.Entry(resource).Collection(x => x.ExternalIDs).LoadAsync();
|
||||||
resource.ExternalIDs = changed.ExternalIDs;
|
resource.ExternalIDs = changed.ExternalIDs;
|
||||||
}
|
}
|
||||||
await base.EditRelations(resource, changed, resetOld);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <inheritdoc/>
|
/// <inheritdoc/>
|
||||||
|
@ -76,9 +76,6 @@ namespace Kyoo.Controllers
|
|||||||
{
|
{
|
||||||
await base.Create(obj);
|
await base.Create(obj);
|
||||||
_database.Entry(obj).State = EntityState.Added;
|
_database.Entry(obj).State = EntityState.Added;
|
||||||
obj.GenreLinks.ForEach(x => _database.Entry(x).State = EntityState.Added);
|
|
||||||
obj.People.ForEach(x => _database.Entry(x).State = EntityState.Added);
|
|
||||||
obj.ExternalIDs.ForEach(x => _database.Entry(x).State = EntityState.Added);
|
|
||||||
await _database.SaveChangesAsync($"Trying to insert a duplicated show (slug {obj.Slug} already exists).");
|
await _database.SaveChangesAsync($"Trying to insert a duplicated show (slug {obj.Slug} already exists).");
|
||||||
return obj;
|
return obj;
|
||||||
}
|
}
|
||||||
@ -88,29 +85,40 @@ namespace Kyoo.Controllers
|
|||||||
{
|
{
|
||||||
await base.Validate(resource);
|
await base.Validate(resource);
|
||||||
if (resource.Studio != null)
|
if (resource.Studio != null)
|
||||||
|
{
|
||||||
resource.Studio = await _studios.CreateIfNotExists(resource.Studio);
|
resource.Studio = await _studios.CreateIfNotExists(resource.Studio);
|
||||||
|
resource.StudioID = resource.Studio.ID;
|
||||||
|
}
|
||||||
|
|
||||||
resource.GenreLinks = resource.Genres?
|
if (resource.Genres != null)
|
||||||
.Select(x => Link.Create(resource, x))
|
|
||||||
.ToList();
|
|
||||||
await resource.GenreLinks.ForEachAsync(async id =>
|
|
||||||
{
|
{
|
||||||
id.Second = await _genres.CreateIfNotExists(id.Second);
|
resource.Genres = await resource.Genres
|
||||||
id.SecondID = id.Second.ID;
|
.SelectAsync(x => _genres.CreateIfNotExists(x))
|
||||||
_database.Entry(id.Second).State = EntityState.Detached;
|
.ToListAsync();
|
||||||
});
|
_database.AttachRange(resource.Genres);
|
||||||
await resource.ExternalIDs.ForEachAsync(async id =>
|
}
|
||||||
|
|
||||||
|
if (resource.ExternalIDs != null)
|
||||||
{
|
{
|
||||||
id.Second = await _providers.CreateIfNotExists(id.Second);
|
foreach (MetadataID id in resource.ExternalIDs)
|
||||||
id.SecondID = id.Second.ID;
|
|
||||||
_database.Entry(id.Second).State = EntityState.Detached;
|
|
||||||
});
|
|
||||||
await resource.People.ForEachAsync(async role =>
|
|
||||||
{
|
{
|
||||||
role.People = await _people.CreateIfNotExists(role.People);
|
id.Provider = _database.LocalEntity<Provider>(id.Provider.Slug)
|
||||||
|
?? await _providers.CreateIfNotExists(id.Provider);
|
||||||
|
id.ProviderID = id.Provider.ID;
|
||||||
|
}
|
||||||
|
_database.MetadataIds<Show>().AttachRange(resource.ExternalIDs);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (resource.People != null)
|
||||||
|
{
|
||||||
|
foreach (PeopleRole role in resource.People)
|
||||||
|
{
|
||||||
|
role.People = _database.LocalEntity<People>(role.People.Slug)
|
||||||
|
?? await _people.CreateIfNotExists(role.People);
|
||||||
role.PeopleID = role.People.ID;
|
role.PeopleID = role.People.ID;
|
||||||
_database.Entry(role.People).State = EntityState.Detached;
|
_database.Entry(role).State = EntityState.Added;
|
||||||
});
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
@ -151,21 +159,18 @@ namespace Kyoo.Controllers
|
|||||||
{
|
{
|
||||||
if (collectionID != null)
|
if (collectionID != null)
|
||||||
{
|
{
|
||||||
await _database.Links<Collection, Show>()
|
await _database.AddLinks<Collection, Show>(collectionID.Value, showID);
|
||||||
.AddAsync(new Link<Collection, Show>(collectionID.Value, showID));
|
|
||||||
await _database.SaveIfNoDuplicates();
|
await _database.SaveIfNoDuplicates();
|
||||||
|
|
||||||
if (libraryID != null)
|
if (libraryID != null)
|
||||||
{
|
{
|
||||||
await _database.Links<Library, Collection>()
|
await _database.AddLinks<Library, Collection>(libraryID.Value, collectionID.Value);
|
||||||
.AddAsync(new Link<Library, Collection>(libraryID.Value, collectionID.Value));
|
|
||||||
await _database.SaveIfNoDuplicates();
|
await _database.SaveIfNoDuplicates();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (libraryID != null)
|
if (libraryID != null)
|
||||||
{
|
{
|
||||||
await _database.Links<Library, Show>()
|
await _database.AddLinks<Library, Show>(libraryID.Value, showID);
|
||||||
.AddAsync(new Link<Library, Show>(libraryID.Value, showID));
|
|
||||||
await _database.SaveIfNoDuplicates();
|
await _database.SaveIfNoDuplicates();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -18,6 +18,11 @@ namespace Kyoo.Controllers
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
private readonly DatabaseContext _database;
|
private readonly DatabaseContext _database;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// A provider repository to handle externalID creation and deletion
|
||||||
|
/// </summary>
|
||||||
|
private readonly IProviderRepository _providers;
|
||||||
|
|
||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
protected override Expression<Func<Studio, object>> DefaultSort => x => x.Name;
|
protected override Expression<Func<Studio, object>> DefaultSort => x => x.Name;
|
||||||
|
|
||||||
@ -26,10 +31,12 @@ namespace Kyoo.Controllers
|
|||||||
/// Create a new <see cref="StudioRepository"/>.
|
/// Create a new <see cref="StudioRepository"/>.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="database">The database handle</param>
|
/// <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)
|
: base(database)
|
||||||
{
|
{
|
||||||
_database = database;
|
_database = database;
|
||||||
|
_providers = providers;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
@ -51,6 +58,34 @@ namespace Kyoo.Controllers
|
|||||||
return obj;
|
return obj;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
protected override async Task Validate(Studio resource)
|
||||||
|
{
|
||||||
|
await base.Validate(resource);
|
||||||
|
if (resource.ExternalIDs != null)
|
||||||
|
{
|
||||||
|
foreach (MetadataID id in resource.ExternalIDs)
|
||||||
|
{
|
||||||
|
id.Provider = _database.LocalEntity<Provider>(id.Provider.Slug)
|
||||||
|
?? await _providers.CreateIfNotExists(id.Provider);
|
||||||
|
id.ProviderID = id.Provider.ID;
|
||||||
|
}
|
||||||
|
_database.MetadataIds<Studio>().AttachRange(resource.ExternalIDs);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <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 />
|
/// <inheritdoc />
|
||||||
public override async Task Delete(Studio obj)
|
public override async Task Delete(Studio obj)
|
||||||
{
|
{
|
||||||
|
@ -37,19 +37,25 @@ namespace Kyoo.Controllers
|
|||||||
throw new InvalidOperationException("Tracks do not support the search method.");
|
throw new InvalidOperationException("Tracks do not support the search method.");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
protected override async Task Validate(Track resource)
|
||||||
|
{
|
||||||
|
await base.Validate(resource);
|
||||||
|
if (resource.EpisodeID <= 0)
|
||||||
|
{
|
||||||
|
resource.EpisodeID = resource.Episode?.ID ?? 0;
|
||||||
|
if (resource.EpisodeID <= 0)
|
||||||
|
throw new ArgumentException("Can't store a track not related to any episode " +
|
||||||
|
$"(episodeID: {resource.EpisodeID}).");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
public override async Task<Track> Create(Track obj)
|
public override async Task<Track> Create(Track obj)
|
||||||
{
|
{
|
||||||
if (obj == null)
|
if (obj == null)
|
||||||
throw new ArgumentNullException(nameof(obj));
|
throw new ArgumentNullException(nameof(obj));
|
||||||
|
|
||||||
if (obj.EpisodeID <= 0)
|
|
||||||
{
|
|
||||||
obj.EpisodeID = obj.Episode?.ID ?? 0;
|
|
||||||
if (obj.EpisodeID <= 0)
|
|
||||||
throw new InvalidOperationException($"Can't store a track not related to any episode (episodeID: {obj.EpisodeID}).");
|
|
||||||
}
|
|
||||||
|
|
||||||
await base.Create(obj);
|
await base.Create(obj);
|
||||||
_database.Entry(obj).State = EntityState.Added;
|
_database.Entry(obj).State = EntityState.Added;
|
||||||
await _database.SaveChangesAsync();
|
await _database.SaveChangesAsync();
|
||||||
|
@ -1,11 +1,10 @@
|
|||||||
using Kyoo.Models;
|
using Kyoo.Models;
|
||||||
using System;
|
using System;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
|
using System.Linq;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using JetBrains.Annotations;
|
using Microsoft.AspNetCore.StaticFiles;
|
||||||
using Kyoo.Models.Options;
|
|
||||||
using Microsoft.Extensions.Logging;
|
using Microsoft.Extensions.Logging;
|
||||||
using Microsoft.Extensions.Options;
|
|
||||||
|
|
||||||
namespace Kyoo.Controllers
|
namespace Kyoo.Controllers
|
||||||
{
|
{
|
||||||
@ -22,54 +21,17 @@ namespace Kyoo.Controllers
|
|||||||
/// A logger to report errors.
|
/// A logger to report errors.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
private readonly ILogger<ThumbnailsManager> _logger;
|
private readonly ILogger<ThumbnailsManager> _logger;
|
||||||
/// <summary>
|
|
||||||
/// The options containing the base path of people images and provider logos.
|
|
||||||
/// </summary>
|
|
||||||
private readonly IOptionsMonitor<BasicOptions> _options;
|
|
||||||
/// <summary>
|
|
||||||
/// A library manager used to load episode and seasons shows if they are not loaded.
|
|
||||||
/// </summary>
|
|
||||||
private readonly Lazy<ILibraryManager> _library;
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Create a new <see cref="ThumbnailsManager"/>.
|
/// Create a new <see cref="ThumbnailsManager"/>.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="files">The file manager to use.</param>
|
/// <param name="files">The file manager to use.</param>
|
||||||
/// <param name="logger">A logger to report errors</param>
|
/// <param name="logger">A logger to report errors</param>
|
||||||
/// <param name="options">The options to use.</param>
|
|
||||||
/// <param name="library">A library manager used to load shows if they are not loaded.</param>
|
|
||||||
public ThumbnailsManager(IFileSystem files,
|
public ThumbnailsManager(IFileSystem files,
|
||||||
ILogger<ThumbnailsManager> logger,
|
ILogger<ThumbnailsManager> logger)
|
||||||
IOptionsMonitor<BasicOptions> options,
|
|
||||||
Lazy<ILibraryManager> library)
|
|
||||||
{
|
{
|
||||||
_files = files;
|
_files = files;
|
||||||
_logger = logger;
|
_logger = logger;
|
||||||
_options = options;
|
|
||||||
_library = library;
|
|
||||||
|
|
||||||
options.OnChange(x =>
|
|
||||||
{
|
|
||||||
_files.CreateDirectory(x.PeoplePath);
|
|
||||||
_files.CreateDirectory(x.ProviderPath);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <inheritdoc />
|
|
||||||
public Task<bool> DownloadImages<T>(T item, bool alwaysDownload = false)
|
|
||||||
where T : IResource
|
|
||||||
{
|
|
||||||
if (item == null)
|
|
||||||
throw new ArgumentNullException(nameof(item));
|
|
||||||
return item switch
|
|
||||||
{
|
|
||||||
Show show => _Validate(show, alwaysDownload),
|
|
||||||
Season season => _Validate(season, alwaysDownload),
|
|
||||||
Episode episode => _Validate(episode, alwaysDownload),
|
|
||||||
People people => _Validate(people, alwaysDownload),
|
|
||||||
Provider provider => _Validate(provider, alwaysDownload),
|
|
||||||
_ => Task.FromResult(false)
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@ -86,8 +48,12 @@ namespace Kyoo.Controllers
|
|||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
await using Stream reader = await _files.GetReader(url);
|
AsyncRef<string> mime = new();
|
||||||
await using Stream local = await _files.NewFile(localPath);
|
await using Stream reader = await _files.GetReader(url, mime);
|
||||||
|
string extension = new FileExtensionContentTypeProvider()
|
||||||
|
.Mappings.FirstOrDefault(x => x.Value == mime.Value)
|
||||||
|
.Key;
|
||||||
|
await using Stream local = await _files.NewFile(localPath + extension);
|
||||||
await reader.CopyToAsync(local);
|
await reader.CopyToAsync(local);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
@ -98,195 +64,74 @@ namespace Kyoo.Controllers
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <inheritdoc />
|
||||||
/// Download images of a specified show.
|
public async Task<bool> DownloadImages<T>(T item, bool alwaysDownload = false)
|
||||||
/// </summary>
|
where T : IThumbnails
|
||||||
/// <param name="show">
|
|
||||||
/// The item to cache images.
|
|
||||||
/// </param>
|
|
||||||
/// <param name="alwaysDownload">
|
|
||||||
/// <c>true</c> if images should be downloaded even if they already exists locally, <c>false</c> otherwise.
|
|
||||||
/// </param>
|
|
||||||
/// <returns><c>true</c> if an image has been downloaded, <c>false</c> otherwise.</returns>
|
|
||||||
private async Task<bool> _Validate([NotNull] Show show, bool alwaysDownload)
|
|
||||||
{
|
{
|
||||||
|
if (item == null)
|
||||||
|
throw new ArgumentNullException(nameof(item));
|
||||||
|
|
||||||
|
if (item.Images == null)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
string name = item is IResource res ? res.Slug : "???";
|
||||||
bool ret = false;
|
bool ret = false;
|
||||||
|
|
||||||
if (show.Poster != null)
|
foreach ((int id, string image) in item.Images.Where(x => x.Value != null))
|
||||||
{
|
{
|
||||||
string posterPath = await GetPoster(show);
|
string localPath = await _GetPrivateImagePath(item, id);
|
||||||
if (alwaysDownload || !await _files.Exists(posterPath))
|
if (alwaysDownload || !await _files.Exists(localPath))
|
||||||
ret |= await _DownloadImage(show.Poster, posterPath, $"The poster of {show.Title}");
|
ret |= await _DownloadImage(image, localPath, $"The image n°{id} of {name}");
|
||||||
}
|
|
||||||
if (show.Logo != null)
|
|
||||||
{
|
|
||||||
string logoPath = await GetLogo(show);
|
|
||||||
if (alwaysDownload || !await _files.Exists(logoPath))
|
|
||||||
ret |= await _DownloadImage(show.Logo, logoPath, $"The logo of {show.Title}");
|
|
||||||
}
|
|
||||||
if (show.Backdrop != null)
|
|
||||||
{
|
|
||||||
string backdropPath = await GetThumbnail(show);
|
|
||||||
if (alwaysDownload || !await _files.Exists(backdropPath))
|
|
||||||
ret |= await _DownloadImage(show.Backdrop, backdropPath, $"The backdrop of {show.Title}");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Download images of a specified person.
|
/// Retrieve the local path of an image of the given item <b>without an extension</b>.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="people">
|
/// <param name="item">The item to retrieve the poster from.</param>
|
||||||
/// The item to cache images.
|
/// <param name="imageID">The ID of the image. See <see cref="Images"/> for values.</param>
|
||||||
/// </param>
|
/// <typeparam name="T">The type of the item</typeparam>
|
||||||
/// <param name="alwaysDownload">
|
/// <returns>The path of the image for the given resource, <b>even if it does not exists</b></returns>
|
||||||
/// <c>true</c> if images should be downloaded even if they already exists locally, <c>false</c> otherwise.
|
private async Task<string> _GetPrivateImagePath<T>(T item, int imageID)
|
||||||
/// </param>
|
|
||||||
/// <returns><c>true</c> if an image has been downloaded, <c>false</c> otherwise.</returns>
|
|
||||||
private async Task<bool> _Validate([NotNull] People people, bool alwaysDownload)
|
|
||||||
{
|
|
||||||
if (people == null)
|
|
||||||
throw new ArgumentNullException(nameof(people));
|
|
||||||
if (people.Poster == null)
|
|
||||||
return false;
|
|
||||||
string localPath = await GetPoster(people);
|
|
||||||
if (alwaysDownload || !await _files.Exists(localPath))
|
|
||||||
return await _DownloadImage(people.Poster, localPath, $"The profile picture of {people.Name}");
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Download images of a specified season.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="season">
|
|
||||||
/// The item to cache images.
|
|
||||||
/// </param>
|
|
||||||
/// <param name="alwaysDownload">
|
|
||||||
/// <c>true</c> if images should be downloaded even if they already exists locally, <c>false</c> otherwise.
|
|
||||||
/// </param>
|
|
||||||
/// <returns><c>true</c> if an image has been downloaded, <c>false</c> otherwise.</returns>
|
|
||||||
private async Task<bool> _Validate([NotNull] Season season, bool alwaysDownload)
|
|
||||||
{
|
|
||||||
if (season.Poster == null)
|
|
||||||
return false;
|
|
||||||
|
|
||||||
string localPath = await GetPoster(season);
|
|
||||||
if (alwaysDownload || !await _files.Exists(localPath))
|
|
||||||
return await _DownloadImage(season.Poster, localPath, $"The poster of {season.Slug}");
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Download images of a specified episode.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="episode">
|
|
||||||
/// The item to cache images.
|
|
||||||
/// </param>
|
|
||||||
/// <param name="alwaysDownload">
|
|
||||||
/// <c>true</c> if images should be downloaded even if they already exists locally, <c>false</c> otherwise.
|
|
||||||
/// </param>
|
|
||||||
/// <returns><c>true</c> if an image has been downloaded, <c>false</c> otherwise.</returns>
|
|
||||||
private async Task<bool> _Validate([NotNull] Episode episode, bool alwaysDownload)
|
|
||||||
{
|
|
||||||
if (episode.Thumb == null)
|
|
||||||
return false;
|
|
||||||
|
|
||||||
string localPath = await _GetEpisodeThumb(episode);
|
|
||||||
if (alwaysDownload || !await _files.Exists(localPath))
|
|
||||||
return await _DownloadImage(episode.Thumb, localPath, $"The thumbnail of {episode.Slug}");
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Download images of a specified provider.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="provider">
|
|
||||||
/// The item to cache images.
|
|
||||||
/// </param>
|
|
||||||
/// <param name="alwaysDownload">
|
|
||||||
/// <c>true</c> if images should be downloaded even if they already exists locally, <c>false</c> otherwise.
|
|
||||||
/// </param>
|
|
||||||
/// <returns><c>true</c> if an image has been downloaded, <c>false</c> otherwise.</returns>
|
|
||||||
private async Task<bool> _Validate([NotNull] Provider provider, bool alwaysDownload)
|
|
||||||
{
|
|
||||||
if (provider.Logo == null)
|
|
||||||
return false;
|
|
||||||
|
|
||||||
string localPath = await GetLogo(provider);
|
|
||||||
if (alwaysDownload || !await _files.Exists(localPath))
|
|
||||||
return await _DownloadImage(provider.Logo, localPath, $"The logo of {provider.Slug}");
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <inheritdoc />
|
|
||||||
public Task<string> GetPoster<T>(T item)
|
|
||||||
where T : IResource
|
|
||||||
{
|
{
|
||||||
if (item == null)
|
if (item == null)
|
||||||
throw new ArgumentNullException(nameof(item));
|
throw new ArgumentNullException(nameof(item));
|
||||||
return item switch
|
|
||||||
|
string directory = await _files.GetExtraDirectory(item);
|
||||||
|
string imageName = imageID switch
|
||||||
{
|
{
|
||||||
Show show => Task.FromResult(_files.Combine(_files.GetExtraDirectory(show), "poster.jpg")),
|
Images.Poster => "poster",
|
||||||
Season season => _GetSeasonPoster(season),
|
Images.Logo => "logo",
|
||||||
People actor => Task.FromResult(_files.Combine(_options.CurrentValue.PeoplePath, $"{actor.Slug}.jpg")),
|
Images.Thumbnail => "thumbnail",
|
||||||
_ => throw new NotSupportedException($"The type {typeof(T).Name} does not have a poster.")
|
Images.Trailer => "trailer",
|
||||||
|
_ => $"{imageID}"
|
||||||
};
|
};
|
||||||
|
|
||||||
|
switch (item)
|
||||||
|
{
|
||||||
|
case Season season:
|
||||||
|
imageName = $"season-{season.SeasonNumber}-{imageName}";
|
||||||
|
break;
|
||||||
|
case Episode episode:
|
||||||
|
directory = await _files.CreateDirectory(_files.Combine(directory, "Thumbnails"));
|
||||||
|
imageName = $"{Path.GetFileNameWithoutExtension(episode.Path)}-{imageName}";
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
return _files.Combine(directory, imageName);
|
||||||
/// Retrieve the path of a season's poster.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="season">The season to retrieve the poster from.</param>
|
|
||||||
/// <returns>The path of the season's poster.</returns>
|
|
||||||
private async Task<string> _GetSeasonPoster(Season season)
|
|
||||||
{
|
|
||||||
if (season.Show == null)
|
|
||||||
await _library.Value.Load(season, x => x.Show);
|
|
||||||
return _files.Combine(_files.GetExtraDirectory(season.Show), $"season-{season.SeasonNumber}.jpg");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
public Task<string> GetThumbnail<T>(T item)
|
public async Task<string> GetImagePath<T>(T item, int imageID)
|
||||||
where T : IResource
|
where T : IThumbnails
|
||||||
{
|
{
|
||||||
if (item == null)
|
string basePath = await _GetPrivateImagePath(item, imageID);
|
||||||
throw new ArgumentNullException(nameof(item));
|
string directory = Path.GetDirectoryName(basePath);
|
||||||
return item switch
|
string baseFile = Path.GetFileName(basePath);
|
||||||
{
|
return (await _files.ListFiles(directory!))
|
||||||
Show show => Task.FromResult(_files.Combine(_files.GetExtraDirectory(show), "backdrop.jpg")),
|
.FirstOrDefault(x => Path.GetFileNameWithoutExtension(x) == baseFile);
|
||||||
Episode episode => _GetEpisodeThumb(episode),
|
|
||||||
_ => throw new NotSupportedException($"The type {typeof(T).Name} does not have a thumbnail.")
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Get the path for an episode's thumbnail.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="episode">The episode to retrieve the thumbnail from</param>
|
|
||||||
/// <returns>The path of the given episode's thumbnail.</returns>
|
|
||||||
private async Task<string> _GetEpisodeThumb(Episode episode)
|
|
||||||
{
|
|
||||||
if (episode.Show == null)
|
|
||||||
await _library.Value.Load(episode, x => x.Show);
|
|
||||||
string dir = _files.Combine(_files.GetExtraDirectory(episode.Show), "Thumbnails");
|
|
||||||
await _files.CreateDirectory(dir);
|
|
||||||
return _files.Combine(dir, $"{Path.GetFileNameWithoutExtension(episode.Path)}.jpg");
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <inheritdoc />
|
|
||||||
public Task<string> GetLogo<T>(T item)
|
|
||||||
where T : IResource
|
|
||||||
{
|
|
||||||
if (item == null)
|
|
||||||
throw new ArgumentNullException(nameof(item));
|
|
||||||
return Task.FromResult(item switch
|
|
||||||
{
|
|
||||||
Show show => _files.Combine(_files.GetExtraDirectory(show), "logo.png"),
|
|
||||||
Provider provider => _files.Combine(_options.CurrentValue.ProviderPath,
|
|
||||||
$"{provider.Slug}.{provider.LogoExtension}"),
|
|
||||||
_ => throw new NotSupportedException($"The type {typeof(T).Name} does not have a thumbnail.")
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -88,10 +88,8 @@ namespace Kyoo.Controllers
|
|||||||
|
|
||||||
public async Task<Track[]> ExtractInfos(Episode episode, bool reextract)
|
public async Task<Track[]> ExtractInfos(Episode episode, bool reextract)
|
||||||
{
|
{
|
||||||
if (episode.Show == null)
|
|
||||||
await _library.Value.Load(episode, x => x.Show);
|
await _library.Value.Load(episode, x => x.Show);
|
||||||
|
string dir = await _files.GetExtraDirectory(episode.Show);
|
||||||
string dir = _files.GetExtraDirectory(episode.Show);
|
|
||||||
if (dir == null)
|
if (dir == null)
|
||||||
throw new ArgumentException("Invalid path.");
|
throw new ArgumentException("Invalid path.");
|
||||||
return await Task.Factory.StartNew(
|
return await Task.Factory.StartNew(
|
||||||
|
@ -101,13 +101,13 @@ namespace Kyoo
|
|||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
public void Configure(ContainerBuilder builder)
|
public void Configure(ContainerBuilder builder)
|
||||||
{
|
{
|
||||||
builder.RegisterComposite<FileSystemComposite, IFileSystem>();
|
builder.RegisterComposite<FileSystemComposite, IFileSystem>().InstancePerLifetimeScope();
|
||||||
builder.RegisterType<LocalFileSystem>().As<IFileSystem>().SingleInstance();
|
builder.RegisterType<LocalFileSystem>().As<IFileSystem>().SingleInstance();
|
||||||
builder.RegisterType<HttpFileSystem>().As<IFileSystem>().SingleInstance();
|
builder.RegisterType<HttpFileSystem>().As<IFileSystem>().SingleInstance();
|
||||||
|
|
||||||
builder.RegisterType<ConfigurationManager>().As<IConfigurationManager>().SingleInstance();
|
builder.RegisterType<ConfigurationManager>().As<IConfigurationManager>().SingleInstance();
|
||||||
builder.RegisterType<Transcoder>().As<ITranscoder>().SingleInstance();
|
builder.RegisterType<Transcoder>().As<ITranscoder>().SingleInstance();
|
||||||
builder.RegisterType<ThumbnailsManager>().As<IThumbnailsManager>().SingleInstance();
|
builder.RegisterType<ThumbnailsManager>().As<IThumbnailsManager>().InstancePerLifetimeScope();
|
||||||
builder.RegisterType<TaskManager>().As<ITaskManager>().SingleInstance();
|
builder.RegisterType<TaskManager>().As<ITaskManager>().SingleInstance();
|
||||||
builder.RegisterType<LibraryManager>().As<ILibraryManager>().InstancePerLifetimeScope();
|
builder.RegisterType<LibraryManager>().As<ILibraryManager>().InstancePerLifetimeScope();
|
||||||
builder.RegisterType<RegexIdentifier>().As<IIdentifier>().SingleInstance();
|
builder.RegisterType<RegexIdentifier>().As<IIdentifier>().SingleInstance();
|
||||||
|
@ -40,7 +40,7 @@
|
|||||||
<PackageReference Include="Autofac.Extras.AttributeMetadata" Version="6.0.0" />
|
<PackageReference Include="Autofac.Extras.AttributeMetadata" Version="6.0.0" />
|
||||||
<PackageReference Include="Microsoft.AspNet.WebApi.Client" Version="5.2.7" />
|
<PackageReference Include="Microsoft.AspNet.WebApi.Client" Version="5.2.7" />
|
||||||
<PackageReference Include="Microsoft.AspNetCore.Mvc.NewtonsoftJson" Version="5.0.8" />
|
<PackageReference Include="Microsoft.AspNetCore.Mvc.NewtonsoftJson" Version="5.0.8" />
|
||||||
<PackageReference Include="Microsoft.AspNetCore.SpaServices" Version="3.1.17" />
|
<PackageReference Include="Microsoft.AspNetCore.SpaServices" Version="5.0.0-preview.8.20414.8" />
|
||||||
<PackageReference Include="Microsoft.AspNetCore.SpaServices.Extensions" Version="5.0.8" />
|
<PackageReference Include="Microsoft.AspNetCore.SpaServices.Extensions" Version="5.0.8" />
|
||||||
<PackageReference Include="Newtonsoft.Json" Version="13.0.1" />
|
<PackageReference Include="Newtonsoft.Json" Version="13.0.1" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
@ -25,16 +25,6 @@ namespace Kyoo.Models.Options
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public string PluginPath { get; set; } = "plugins/";
|
public string PluginPath { get; set; } = "plugins/";
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// The path of the people pictures.
|
|
||||||
/// </summary>
|
|
||||||
public string PeoplePath { get; set; } = "people/";
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// The path of providers icons.
|
|
||||||
/// </summary>
|
|
||||||
public string ProviderPath { get; set; } = "providers/";
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The temporary folder to cache transmuxed file.
|
/// The temporary folder to cache transmuxed file.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@ -44,5 +34,22 @@ namespace Kyoo.Models.Options
|
|||||||
/// The temporary folder to cache transcoded file.
|
/// The temporary folder to cache transcoded file.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public string TranscodePath { get; set; } = "cached/transcode";
|
public string TranscodePath { get; set; } = "cached/transcode";
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// <c>true</c> if the metadata of a show/season/episode should be stored in the same directory as video files,
|
||||||
|
/// <c>false</c> to save them in a kyoo specific directory.
|
||||||
|
/// </summary>
|
||||||
|
/// <remarks>
|
||||||
|
/// Some file systems might discard this option to store them somewhere else.
|
||||||
|
/// For example, readonly file systems will probably store them in a kyoo specific directory.
|
||||||
|
/// </remarks>
|
||||||
|
public bool MetadataInShow { get; set; } = true;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The path for metadata if they are not stored near show (see <see cref="MetadataInShow"/>).
|
||||||
|
/// Some resources can't be stored near a show and they are stored in this directory
|
||||||
|
/// (like <see cref="Provider"/>).
|
||||||
|
/// </summary>
|
||||||
|
public string MetadataPath { get; set; } = "metadata/";
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -116,16 +116,11 @@ namespace Kyoo.Tasks
|
|||||||
else
|
else
|
||||||
show = registeredShow;
|
show = registeredShow;
|
||||||
|
|
||||||
// If they are not already loaded, load external ids to allow metadata providers to use them.
|
|
||||||
if (show.ExternalIDs == null)
|
|
||||||
await _libraryManager.Load(show, x => x.ExternalIDs);
|
|
||||||
progress.Report(50);
|
progress.Report(50);
|
||||||
|
|
||||||
if (season != null)
|
if (season != null)
|
||||||
season.Show = show;
|
season.Show = show;
|
||||||
season = await _RegisterAndFill(season);
|
season = await _RegisterAndFill(season);
|
||||||
if (season != null)
|
|
||||||
season.Title ??= $"Season {season.SeasonNumber}";
|
|
||||||
progress.Report(60);
|
progress.Report(60);
|
||||||
|
|
||||||
episode.Show = show;
|
episode.Show = show;
|
||||||
@ -163,16 +158,32 @@ namespace Kyoo.Tasks
|
|||||||
/// <typeparam name="T">The type of the item</typeparam>
|
/// <typeparam name="T">The type of the item</typeparam>
|
||||||
/// <returns>The existing or filled item.</returns>
|
/// <returns>The existing or filled item.</returns>
|
||||||
private async Task<T> _RegisterAndFill<T>(T item)
|
private async Task<T> _RegisterAndFill<T>(T item)
|
||||||
where T : class, IResource
|
where T : class, IResource, IThumbnails, IMetadata
|
||||||
{
|
{
|
||||||
if (item == null || string.IsNullOrEmpty(item.Slug))
|
if (item == null || string.IsNullOrEmpty(item.Slug))
|
||||||
return null;
|
return null;
|
||||||
|
|
||||||
T existing = await _libraryManager.GetOrDefault<T>(item.Slug);
|
T existing = await _libraryManager.GetOrDefault<T>(item.Slug);
|
||||||
if (existing != null)
|
if (existing != null)
|
||||||
|
{
|
||||||
|
await _libraryManager.Load(existing, x => x.ExternalIDs);
|
||||||
return existing;
|
return existing;
|
||||||
|
}
|
||||||
|
|
||||||
item = await _metadataProvider.Get(item);
|
item = await _metadataProvider.Get(item);
|
||||||
await _thumbnailsManager.DownloadImages(item);
|
await _thumbnailsManager.DownloadImages(item);
|
||||||
|
|
||||||
|
switch (item)
|
||||||
|
{
|
||||||
|
case Show show when show.People != null:
|
||||||
|
foreach (PeopleRole role in show.People)
|
||||||
|
await _thumbnailsManager.DownloadImages(role.People);
|
||||||
|
break;
|
||||||
|
case Season season:
|
||||||
|
season.Title ??= $"Season {season.SeasonNumber}";
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
return await _libraryManager.CreateIfNotExists(item);
|
return await _libraryManager.CreateIfNotExists(item);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -6,6 +6,7 @@ using Kyoo.Models;
|
|||||||
using Microsoft.AspNetCore.Mvc;
|
using Microsoft.AspNetCore.Mvc;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using Kyoo.CommonApi;
|
using Kyoo.CommonApi;
|
||||||
|
using Kyoo.Models.Exceptions;
|
||||||
using Kyoo.Models.Options;
|
using Kyoo.Models.Options;
|
||||||
using Kyoo.Models.Permissions;
|
using Kyoo.Models.Permissions;
|
||||||
using Microsoft.Extensions.Options;
|
using Microsoft.Extensions.Options;
|
||||||
@ -19,11 +20,18 @@ namespace Kyoo.Api
|
|||||||
public class CollectionApi : CrudApi<Collection>
|
public class CollectionApi : CrudApi<Collection>
|
||||||
{
|
{
|
||||||
private readonly ILibraryManager _libraryManager;
|
private readonly ILibraryManager _libraryManager;
|
||||||
|
private readonly IFileSystem _files;
|
||||||
|
private readonly IThumbnailsManager _thumbs;
|
||||||
|
|
||||||
public CollectionApi(ILibraryManager libraryManager, IOptions<BasicOptions> options)
|
public CollectionApi(ILibraryManager libraryManager,
|
||||||
|
IFileSystem files,
|
||||||
|
IThumbnailsManager thumbs,
|
||||||
|
IOptions<BasicOptions> options)
|
||||||
: base(libraryManager.CollectionRepository, options.Value.PublicUrl)
|
: base(libraryManager.CollectionRepository, options.Value.PublicUrl)
|
||||||
{
|
{
|
||||||
_libraryManager = libraryManager;
|
_libraryManager = libraryManager;
|
||||||
|
_files = files;
|
||||||
|
_thumbs = thumbs;
|
||||||
}
|
}
|
||||||
|
|
||||||
[HttpGet("{id:int}/show")]
|
[HttpGet("{id:int}/show")]
|
||||||
@ -129,5 +137,48 @@ namespace Kyoo.Api
|
|||||||
return BadRequest(new {Error = ex.Message});
|
return BadRequest(new {Error = ex.Message});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[HttpGet("{slug}/poster")]
|
||||||
|
public async Task<IActionResult> GetPoster(string slug)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
Collection collection = await _libraryManager.Get<Collection>(slug);
|
||||||
|
return _files.FileResult(await _thumbs.GetImagePath(collection, Images.Poster));
|
||||||
|
}
|
||||||
|
catch (ItemNotFoundException)
|
||||||
|
{
|
||||||
|
return NotFound();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[HttpGet("{slug}/logo")]
|
||||||
|
public async Task<IActionResult> GetLogo(string slug)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
Collection collection = await _libraryManager.Get<Collection>(slug);
|
||||||
|
return _files.FileResult(await _thumbs.GetImagePath(collection, Images.Logo));
|
||||||
|
}
|
||||||
|
catch (ItemNotFoundException)
|
||||||
|
{
|
||||||
|
return NotFound();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[HttpGet("{slug}/backdrop")]
|
||||||
|
[HttpGet("{slug}/thumbnail")]
|
||||||
|
public async Task<IActionResult> GetBackdrop(string slug)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
Collection collection = await _libraryManager.Get<Collection>(slug);
|
||||||
|
return _files.FileResult(await _thumbs.GetImagePath(collection, Images.Thumbnail));
|
||||||
|
}
|
||||||
|
catch (ItemNotFoundException)
|
||||||
|
{
|
||||||
|
return NotFound();
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -195,7 +195,7 @@ namespace Kyoo.Api
|
|||||||
try
|
try
|
||||||
{
|
{
|
||||||
Episode episode = await _libraryManager.Get<Episode>(id);
|
Episode episode = await _libraryManager.Get<Episode>(id);
|
||||||
return _files.FileResult(await _thumbnails.GetThumbnail(episode));
|
return _files.FileResult(await _thumbnails.GetImagePath(episode, Images.Thumbnail));
|
||||||
}
|
}
|
||||||
catch (ItemNotFoundException)
|
catch (ItemNotFoundException)
|
||||||
{
|
{
|
||||||
@ -210,7 +210,7 @@ namespace Kyoo.Api
|
|||||||
try
|
try
|
||||||
{
|
{
|
||||||
Episode episode = await _libraryManager.Get<Episode>(slug);
|
Episode episode = await _libraryManager.Get<Episode>(slug);
|
||||||
return _files.FileResult(await _thumbnails.GetThumbnail(episode));
|
return _files.FileResult(await _thumbnails.GetImagePath(episode, Images.Thumbnail));
|
||||||
}
|
}
|
||||||
catch (ItemNotFoundException)
|
catch (ItemNotFoundException)
|
||||||
{
|
{
|
||||||
|
@ -94,7 +94,7 @@ namespace Kyoo.Api
|
|||||||
People people = await _libraryManager.GetOrDefault<People>(id);
|
People people = await _libraryManager.GetOrDefault<People>(id);
|
||||||
if (people == null)
|
if (people == null)
|
||||||
return NotFound();
|
return NotFound();
|
||||||
return _files.FileResult(await _thumbs.GetPoster(people));
|
return _files.FileResult(await _thumbs.GetImagePath(people, Images.Poster));
|
||||||
}
|
}
|
||||||
|
|
||||||
[HttpGet("{slug}/poster")]
|
[HttpGet("{slug}/poster")]
|
||||||
@ -103,7 +103,7 @@ namespace Kyoo.Api
|
|||||||
People people = await _libraryManager.GetOrDefault<People>(slug);
|
People people = await _libraryManager.GetOrDefault<People>(slug);
|
||||||
if (people == null)
|
if (people == null)
|
||||||
return NotFound();
|
return NotFound();
|
||||||
return _files.FileResult(await _thumbs.GetPoster(people));
|
return _files.FileResult(await _thumbs.GetImagePath(people, Images.Poster));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -36,7 +36,7 @@ namespace Kyoo.Api
|
|||||||
Provider provider = await _libraryManager.GetOrDefault<Provider>(id);
|
Provider provider = await _libraryManager.GetOrDefault<Provider>(id);
|
||||||
if (provider == null)
|
if (provider == null)
|
||||||
return NotFound();
|
return NotFound();
|
||||||
return _files.FileResult(await _thumbnails.GetLogo(provider));
|
return _files.FileResult(await _thumbnails.GetImagePath(provider, Images.Logo));
|
||||||
}
|
}
|
||||||
|
|
||||||
[HttpGet("{slug}/logo")]
|
[HttpGet("{slug}/logo")]
|
||||||
@ -45,7 +45,7 @@ namespace Kyoo.Api
|
|||||||
Provider provider = await _libraryManager.GetOrDefault<Provider>(slug);
|
Provider provider = await _libraryManager.GetOrDefault<Provider>(slug);
|
||||||
if (provider == null)
|
if (provider == null)
|
||||||
return NotFound();
|
return NotFound();
|
||||||
return _files.FileResult(await _thumbnails.GetLogo(provider));
|
return _files.FileResult(await _thumbnails.GetImagePath(provider, Images.Logo));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -151,7 +151,7 @@ namespace Kyoo.Api
|
|||||||
if (season == null)
|
if (season == null)
|
||||||
return NotFound();
|
return NotFound();
|
||||||
await _libraryManager.Load(season, x => x.Show);
|
await _libraryManager.Load(season, x => x.Show);
|
||||||
return _files.FileResult(await _thumbs.GetPoster(season));
|
return _files.FileResult(await _thumbs.GetImagePath(season, Images.Poster));
|
||||||
}
|
}
|
||||||
|
|
||||||
[HttpGet("{slug}/poster")]
|
[HttpGet("{slug}/poster")]
|
||||||
@ -161,7 +161,7 @@ namespace Kyoo.Api
|
|||||||
if (season == null)
|
if (season == null)
|
||||||
return NotFound();
|
return NotFound();
|
||||||
await _libraryManager.Load(season, x => x.Show);
|
await _libraryManager.Load(season, x => x.Show);
|
||||||
return _files.FileResult(await _thumbs.GetPoster(season));
|
return _files.FileResult(await _thumbs.GetImagePath(season, Images.Poster));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -383,7 +383,7 @@ namespace Kyoo.Api
|
|||||||
try
|
try
|
||||||
{
|
{
|
||||||
Show show = await _libraryManager.Get<Show>(slug);
|
Show show = await _libraryManager.Get<Show>(slug);
|
||||||
string path = Path.Combine(_files.GetExtraDirectory(show), "Attachments");
|
string path = _files.Combine(await _files.GetExtraDirectory(show), "Attachments");
|
||||||
return (await _files.ListFiles(path))
|
return (await _files.ListFiles(path))
|
||||||
.ToDictionary(Path.GetFileNameWithoutExtension,
|
.ToDictionary(Path.GetFileNameWithoutExtension,
|
||||||
x => $"{BaseURL}api/shows/{slug}/fonts/{Path.GetFileName(x)}");
|
x => $"{BaseURL}api/shows/{slug}/fonts/{Path.GetFileName(x)}");
|
||||||
@ -402,7 +402,7 @@ namespace Kyoo.Api
|
|||||||
try
|
try
|
||||||
{
|
{
|
||||||
Show show = await _libraryManager.Get<Show>(showSlug);
|
Show show = await _libraryManager.Get<Show>(showSlug);
|
||||||
string path = Path.Combine(_files.GetExtraDirectory(show), "Attachments", slug);
|
string path = _files.Combine(await _files.GetExtraDirectory(show), "Attachments", slug);
|
||||||
return _files.FileResult(path);
|
return _files.FileResult(path);
|
||||||
}
|
}
|
||||||
catch (ItemNotFoundException)
|
catch (ItemNotFoundException)
|
||||||
@ -417,7 +417,7 @@ namespace Kyoo.Api
|
|||||||
try
|
try
|
||||||
{
|
{
|
||||||
Show show = await _libraryManager.Get<Show>(slug);
|
Show show = await _libraryManager.Get<Show>(slug);
|
||||||
return _files.FileResult(await _thumbs.GetPoster(show));
|
return _files.FileResult(await _thumbs.GetImagePath(show, Images.Poster));
|
||||||
}
|
}
|
||||||
catch (ItemNotFoundException)
|
catch (ItemNotFoundException)
|
||||||
{
|
{
|
||||||
@ -431,7 +431,7 @@ namespace Kyoo.Api
|
|||||||
try
|
try
|
||||||
{
|
{
|
||||||
Show show = await _libraryManager.Get<Show>(slug);
|
Show show = await _libraryManager.Get<Show>(slug);
|
||||||
return _files.FileResult(await _thumbs.GetLogo(show));
|
return _files.FileResult(await _thumbs.GetImagePath(show, Images.Logo));
|
||||||
}
|
}
|
||||||
catch (ItemNotFoundException)
|
catch (ItemNotFoundException)
|
||||||
{
|
{
|
||||||
@ -446,7 +446,7 @@ namespace Kyoo.Api
|
|||||||
try
|
try
|
||||||
{
|
{
|
||||||
Show show = await _libraryManager.Get<Show>(slug);
|
Show show = await _libraryManager.Get<Show>(slug);
|
||||||
return _files.FileResult(await _thumbs.GetThumbnail(show));
|
return _files.FileResult(await _thumbs.GetImagePath(show, Images.Thumbnail));
|
||||||
}
|
}
|
||||||
catch (ItemNotFoundException)
|
catch (ItemNotFoundException)
|
||||||
{
|
{
|
||||||
|
@ -3,10 +3,10 @@
|
|||||||
"url": "http://*:5000",
|
"url": "http://*:5000",
|
||||||
"publicUrl": "http://localhost:5000/",
|
"publicUrl": "http://localhost:5000/",
|
||||||
"pluginsPath": "plugins/",
|
"pluginsPath": "plugins/",
|
||||||
"peoplePath": "people/",
|
|
||||||
"providerPath": "providers/",
|
|
||||||
"transmuxPath": "cached/transmux",
|
"transmuxPath": "cached/transmux",
|
||||||
"transcodePath": "cached/transcode"
|
"transcodePath": "cached/transcode",
|
||||||
|
"metadataInShow": true,
|
||||||
|
"metadataPath": "metadata/"
|
||||||
},
|
},
|
||||||
|
|
||||||
"database": {
|
"database": {
|
||||||
@ -70,5 +70,8 @@
|
|||||||
|
|
||||||
"tvdb": {
|
"tvdb": {
|
||||||
"apiKey": "REDACTED"
|
"apiKey": "REDACTED"
|
||||||
|
},
|
||||||
|
"the-moviedb": {
|
||||||
|
"apiKey": "REDACTED"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
76
tests/Kyoo.Tests/Database/RepositoryActivator.cs
Normal file
76
tests/Kyoo.Tests/Database/RepositoryActivator.cs
Normal file
@ -0,0 +1,76 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using Kyoo.Controllers;
|
||||||
|
using Xunit.Abstractions;
|
||||||
|
|
||||||
|
namespace Kyoo.Tests
|
||||||
|
{
|
||||||
|
public class RepositoryActivator : IDisposable, IAsyncDisposable
|
||||||
|
{
|
||||||
|
public TestContext Context { get; }
|
||||||
|
public ILibraryManager LibraryManager { get; }
|
||||||
|
|
||||||
|
|
||||||
|
private readonly List<DatabaseContext> _databases = new();
|
||||||
|
|
||||||
|
public RepositoryActivator(ITestOutputHelper output, PostgresFixture postgres = null)
|
||||||
|
{
|
||||||
|
Context = postgres == null
|
||||||
|
? new SqLiteTestContext(output)
|
||||||
|
: new PostgresTestContext(postgres, output);
|
||||||
|
|
||||||
|
ProviderRepository provider = new(_NewContext());
|
||||||
|
LibraryRepository library = new(_NewContext(), provider);
|
||||||
|
CollectionRepository collection = new(_NewContext(), provider);
|
||||||
|
GenreRepository genre = new(_NewContext());
|
||||||
|
StudioRepository studio = new(_NewContext(), provider);
|
||||||
|
PeopleRepository people = new(_NewContext(), provider,
|
||||||
|
new Lazy<IShowRepository>(() => LibraryManager.ShowRepository));
|
||||||
|
ShowRepository show = new(_NewContext(), studio, people, genre, provider);
|
||||||
|
SeasonRepository season = new(_NewContext(), provider);
|
||||||
|
LibraryItemRepository libraryItem = new(_NewContext(),
|
||||||
|
new Lazy<ILibraryRepository>(() => LibraryManager.LibraryRepository));
|
||||||
|
TrackRepository track = new(_NewContext());
|
||||||
|
EpisodeRepository episode = new(_NewContext(), provider, track);
|
||||||
|
UserRepository user = new(_NewContext());
|
||||||
|
|
||||||
|
LibraryManager = new LibraryManager(new IBaseRepository[] {
|
||||||
|
provider,
|
||||||
|
library,
|
||||||
|
libraryItem,
|
||||||
|
collection,
|
||||||
|
show,
|
||||||
|
season,
|
||||||
|
episode,
|
||||||
|
track,
|
||||||
|
people,
|
||||||
|
studio,
|
||||||
|
genre,
|
||||||
|
user
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private DatabaseContext _NewContext()
|
||||||
|
{
|
||||||
|
DatabaseContext context = Context.New();
|
||||||
|
_databases.Add(context);
|
||||||
|
return context;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Dispose()
|
||||||
|
{
|
||||||
|
foreach (DatabaseContext context in _databases)
|
||||||
|
context.Dispose();
|
||||||
|
Context.Dispose();
|
||||||
|
GC.SuppressFinalize(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async ValueTask DisposeAsync()
|
||||||
|
{
|
||||||
|
foreach (DatabaseContext context in _databases)
|
||||||
|
await context.DisposeAsync();
|
||||||
|
await Context.DisposeAsync();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
198
tests/Kyoo.Tests/Database/SpecificTests/CollectionsTests.cs
Normal file
198
tests/Kyoo.Tests/Database/SpecificTests/CollectionsTests.cs
Normal file
@ -0,0 +1,198 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using Kyoo.Controllers;
|
||||||
|
using Kyoo.Models;
|
||||||
|
using Microsoft.EntityFrameworkCore;
|
||||||
|
using Xunit;
|
||||||
|
using Xunit.Abstractions;
|
||||||
|
|
||||||
|
namespace Kyoo.Tests.Database
|
||||||
|
{
|
||||||
|
namespace SqLite
|
||||||
|
{
|
||||||
|
public class CollectionTests : ACollectionTests
|
||||||
|
{
|
||||||
|
public CollectionTests(ITestOutputHelper output)
|
||||||
|
: base(new RepositoryActivator(output)) { }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
namespace PostgreSQL
|
||||||
|
{
|
||||||
|
[Collection(nameof(Postgresql))]
|
||||||
|
public class CollectionTests : ACollectionTests
|
||||||
|
{
|
||||||
|
public CollectionTests(PostgresFixture postgres, ITestOutputHelper output)
|
||||||
|
: base(new RepositoryActivator(output, postgres)) { }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public abstract class ACollectionTests : RepositoryTests<Collection>
|
||||||
|
{
|
||||||
|
private readonly ICollectionRepository _repository;
|
||||||
|
|
||||||
|
protected ACollectionTests(RepositoryActivator repositories)
|
||||||
|
: base(repositories)
|
||||||
|
{
|
||||||
|
_repository = Repositories.LibraryManager.CollectionRepository;
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task CreateWithEmptySlugTest()
|
||||||
|
{
|
||||||
|
Collection collection = TestSample.GetNew<Collection>();
|
||||||
|
collection.Slug = "";
|
||||||
|
await Assert.ThrowsAsync<ArgumentException>(() => _repository.Create(collection));
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task CreateWithNumberSlugTest()
|
||||||
|
{
|
||||||
|
Collection collection = TestSample.GetNew<Collection>();
|
||||||
|
collection.Slug = "2";
|
||||||
|
Collection ret = await _repository.Create(collection);
|
||||||
|
Assert.Equal("2!", ret.Slug);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task CreateWithoutNameTest()
|
||||||
|
{
|
||||||
|
Collection collection = TestSample.GetNew<Collection>();
|
||||||
|
collection.Name = null;
|
||||||
|
await Assert.ThrowsAsync<ArgumentException>(() => _repository.Create(collection));
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task CreateWithExternalIdTest()
|
||||||
|
{
|
||||||
|
Collection collection = TestSample.GetNew<Collection>();
|
||||||
|
collection.ExternalIDs = new[]
|
||||||
|
{
|
||||||
|
new MetadataID
|
||||||
|
{
|
||||||
|
Provider = TestSample.Get<Provider>(),
|
||||||
|
Link = "link",
|
||||||
|
DataID = "id"
|
||||||
|
},
|
||||||
|
new MetadataID
|
||||||
|
{
|
||||||
|
Provider = TestSample.GetNew<Provider>(),
|
||||||
|
Link = "new-provider-link",
|
||||||
|
DataID = "new-id"
|
||||||
|
}
|
||||||
|
};
|
||||||
|
await _repository.Create(collection);
|
||||||
|
|
||||||
|
Collection retrieved = await _repository.Get(2);
|
||||||
|
await Repositories.LibraryManager.Load(retrieved, x => x.ExternalIDs);
|
||||||
|
Assert.Equal(2, retrieved.ExternalIDs.Count);
|
||||||
|
KAssert.DeepEqual(collection.ExternalIDs.First(), retrieved.ExternalIDs.First());
|
||||||
|
KAssert.DeepEqual(collection.ExternalIDs.Last(), retrieved.ExternalIDs.Last());
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task EditTest()
|
||||||
|
{
|
||||||
|
Collection value = await _repository.Get(TestSample.Get<Collection>().Slug);
|
||||||
|
value.Name = "New Title";
|
||||||
|
value.Images = new Dictionary<int, string>
|
||||||
|
{
|
||||||
|
[Images.Poster] = "new-poster"
|
||||||
|
};
|
||||||
|
await _repository.Edit(value, false);
|
||||||
|
|
||||||
|
await using DatabaseContext database = Repositories.Context.New();
|
||||||
|
Collection retrieved = await database.Collections.FirstAsync();
|
||||||
|
|
||||||
|
KAssert.DeepEqual(value, retrieved);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task EditMetadataTest()
|
||||||
|
{
|
||||||
|
Collection value = await _repository.Get(TestSample.Get<Collection>().Slug);
|
||||||
|
value.ExternalIDs = new[]
|
||||||
|
{
|
||||||
|
new MetadataID
|
||||||
|
{
|
||||||
|
Provider = TestSample.Get<Provider>(),
|
||||||
|
Link = "link",
|
||||||
|
DataID = "id"
|
||||||
|
},
|
||||||
|
};
|
||||||
|
await _repository.Edit(value, false);
|
||||||
|
|
||||||
|
await using DatabaseContext database = Repositories.Context.New();
|
||||||
|
Collection retrieved = await database.Collections
|
||||||
|
.Include(x => x.ExternalIDs)
|
||||||
|
.ThenInclude(x => x.Provider)
|
||||||
|
.FirstAsync();
|
||||||
|
|
||||||
|
KAssert.DeepEqual(value, retrieved);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task AddMetadataTest()
|
||||||
|
{
|
||||||
|
Collection value = await _repository.Get(TestSample.Get<Collection>().Slug);
|
||||||
|
value.ExternalIDs = new List<MetadataID>
|
||||||
|
{
|
||||||
|
new()
|
||||||
|
{
|
||||||
|
Provider = TestSample.Get<Provider>(),
|
||||||
|
Link = "link",
|
||||||
|
DataID = "id"
|
||||||
|
},
|
||||||
|
};
|
||||||
|
await _repository.Edit(value, false);
|
||||||
|
|
||||||
|
{
|
||||||
|
await using DatabaseContext database = Repositories.Context.New();
|
||||||
|
Collection retrieved = await database.Collections
|
||||||
|
.Include(x => x.ExternalIDs)
|
||||||
|
.ThenInclude(x => x.Provider)
|
||||||
|
.FirstAsync();
|
||||||
|
|
||||||
|
KAssert.DeepEqual(value, retrieved);
|
||||||
|
}
|
||||||
|
|
||||||
|
value.ExternalIDs.Add(new MetadataID
|
||||||
|
{
|
||||||
|
Provider = TestSample.GetNew<Provider>(),
|
||||||
|
Link = "link",
|
||||||
|
DataID = "id"
|
||||||
|
});
|
||||||
|
await _repository.Edit(value, false);
|
||||||
|
|
||||||
|
{
|
||||||
|
await using DatabaseContext database = Repositories.Context.New();
|
||||||
|
Collection retrieved = await database.Collections
|
||||||
|
.Include(x => x.ExternalIDs)
|
||||||
|
.ThenInclude(x => x.Provider)
|
||||||
|
.FirstAsync();
|
||||||
|
|
||||||
|
KAssert.DeepEqual(value, retrieved);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[Theory]
|
||||||
|
[InlineData("test")]
|
||||||
|
[InlineData("super")]
|
||||||
|
[InlineData("title")]
|
||||||
|
[InlineData("TiTlE")]
|
||||||
|
[InlineData("SuPeR")]
|
||||||
|
public async Task SearchTest(string query)
|
||||||
|
{
|
||||||
|
Collection value = new()
|
||||||
|
{
|
||||||
|
Slug = "super-test",
|
||||||
|
Name = "This is a test title",
|
||||||
|
};
|
||||||
|
await _repository.Create(value);
|
||||||
|
ICollection<Collection> ret = await _repository.Search(query);
|
||||||
|
KAssert.DeepEqual(value, ret.First());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -1,6 +1,9 @@
|
|||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using Kyoo.Controllers;
|
using Kyoo.Controllers;
|
||||||
using Kyoo.Models;
|
using Kyoo.Models;
|
||||||
|
using Microsoft.EntityFrameworkCore;
|
||||||
using Xunit;
|
using Xunit;
|
||||||
using Xunit.Abstractions;
|
using Xunit.Abstractions;
|
||||||
|
|
||||||
@ -59,7 +62,8 @@ namespace Kyoo.Tests.Database
|
|||||||
episode = await _repository.Edit(new Episode
|
episode = await _repository.Edit(new Episode
|
||||||
{
|
{
|
||||||
ID = 1,
|
ID = 1,
|
||||||
SeasonNumber = 2
|
SeasonNumber = 2,
|
||||||
|
ShowID = 1
|
||||||
}, false);
|
}, false);
|
||||||
Assert.Equal($"{TestSample.Get<Show>().Slug}-s2e1", episode.Slug);
|
Assert.Equal($"{TestSample.Get<Show>().Slug}-s2e1", episode.Slug);
|
||||||
episode = await _repository.Get(1);
|
episode = await _repository.Get(1);
|
||||||
@ -74,7 +78,8 @@ namespace Kyoo.Tests.Database
|
|||||||
episode = await _repository.Edit(new Episode
|
episode = await _repository.Edit(new Episode
|
||||||
{
|
{
|
||||||
ID = 1,
|
ID = 1,
|
||||||
EpisodeNumber = 2
|
EpisodeNumber = 2,
|
||||||
|
ShowID = 1
|
||||||
}, false);
|
}, false);
|
||||||
Assert.Equal($"{TestSample.Get<Show>().Slug}-s1e2", episode.Slug);
|
Assert.Equal($"{TestSample.Get<Show>().Slug}-s1e2", episode.Slug);
|
||||||
episode = await _repository.Get(1);
|
episode = await _repository.Get(1);
|
||||||
@ -93,10 +98,6 @@ namespace Kyoo.Tests.Database
|
|||||||
Assert.Equal($"{TestSample.Get<Show>().Slug}-s2e4", episode.Slug);
|
Assert.Equal($"{TestSample.Get<Show>().Slug}-s2e4", episode.Slug);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
// TODO absolute numbering tests
|
|
||||||
|
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
public void AbsoluteSlugTest()
|
public void AbsoluteSlugTest()
|
||||||
{
|
{
|
||||||
@ -133,7 +134,8 @@ namespace Kyoo.Tests.Database
|
|||||||
Episode episode = await _repository.Edit(new Episode
|
Episode episode = await _repository.Edit(new Episode
|
||||||
{
|
{
|
||||||
ID = 2,
|
ID = 2,
|
||||||
AbsoluteNumber = 56
|
AbsoluteNumber = 56,
|
||||||
|
ShowID = 1
|
||||||
}, false);
|
}, false);
|
||||||
Assert.Equal($"{TestSample.Get<Show>().Slug}-56", episode.Slug);
|
Assert.Equal($"{TestSample.Get<Show>().Slug}-56", episode.Slug);
|
||||||
episode = await _repository.Get(2);
|
episode = await _repository.Get(2);
|
||||||
@ -148,7 +150,8 @@ namespace Kyoo.Tests.Database
|
|||||||
{
|
{
|
||||||
ID = 2,
|
ID = 2,
|
||||||
SeasonNumber = 1,
|
SeasonNumber = 1,
|
||||||
EpisodeNumber = 2
|
EpisodeNumber = 2,
|
||||||
|
ShowID = 1
|
||||||
}, false);
|
}, false);
|
||||||
Assert.Equal($"{TestSample.Get<Show>().Slug}-s1e2", episode.Slug);
|
Assert.Equal($"{TestSample.Get<Show>().Slug}-s1e2", episode.Slug);
|
||||||
episode = await _repository.Get(2);
|
episode = await _repository.Get(2);
|
||||||
@ -188,5 +191,137 @@ namespace Kyoo.Tests.Database
|
|||||||
Episode episode = await _repository.Get(3);
|
Episode episode = await _repository.Get(3);
|
||||||
Assert.Equal("john-wick", episode.Slug);
|
Assert.Equal("john-wick", episode.Slug);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task CreateWithExternalIdTest()
|
||||||
|
{
|
||||||
|
Episode value = TestSample.GetNew<Episode>();
|
||||||
|
value.ExternalIDs = new[]
|
||||||
|
{
|
||||||
|
new MetadataID
|
||||||
|
{
|
||||||
|
Provider = TestSample.Get<Provider>(),
|
||||||
|
Link = "link",
|
||||||
|
DataID = "id"
|
||||||
|
},
|
||||||
|
new MetadataID
|
||||||
|
{
|
||||||
|
Provider = TestSample.GetNew<Provider>(),
|
||||||
|
Link = "new-provider-link",
|
||||||
|
DataID = "new-id"
|
||||||
|
}
|
||||||
|
};
|
||||||
|
await _repository.Create(value);
|
||||||
|
|
||||||
|
Episode retrieved = await _repository.Get(2);
|
||||||
|
await Repositories.LibraryManager.Load(retrieved, x => x.ExternalIDs);
|
||||||
|
Assert.Equal(2, retrieved.ExternalIDs.Count);
|
||||||
|
KAssert.DeepEqual(value.ExternalIDs.First(), retrieved.ExternalIDs.First());
|
||||||
|
KAssert.DeepEqual(value.ExternalIDs.Last(), retrieved.ExternalIDs.Last());
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task EditTest()
|
||||||
|
{
|
||||||
|
Episode value = await _repository.Get(TestSample.Get<Episode>().Slug);
|
||||||
|
value.Title = "New Title";
|
||||||
|
value.Images = new Dictionary<int, string>
|
||||||
|
{
|
||||||
|
[Images.Poster] = "new-poster"
|
||||||
|
};
|
||||||
|
await _repository.Edit(value, false);
|
||||||
|
|
||||||
|
await using DatabaseContext database = Repositories.Context.New();
|
||||||
|
Episode retrieved = await database.Episodes.FirstAsync();
|
||||||
|
|
||||||
|
KAssert.DeepEqual(value, retrieved);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task EditMetadataTest()
|
||||||
|
{
|
||||||
|
Episode value = await _repository.Get(TestSample.Get<Episode>().Slug);
|
||||||
|
value.ExternalIDs = new[]
|
||||||
|
{
|
||||||
|
new MetadataID
|
||||||
|
{
|
||||||
|
Provider = TestSample.Get<Provider>(),
|
||||||
|
Link = "link",
|
||||||
|
DataID = "id"
|
||||||
|
},
|
||||||
|
};
|
||||||
|
await _repository.Edit(value, false);
|
||||||
|
|
||||||
|
await using DatabaseContext database = Repositories.Context.New();
|
||||||
|
Episode retrieved = await database.Episodes
|
||||||
|
.Include(x => x.ExternalIDs)
|
||||||
|
.ThenInclude(x => x.Provider)
|
||||||
|
.FirstAsync();
|
||||||
|
|
||||||
|
KAssert.DeepEqual(value, retrieved);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task AddMetadataTest()
|
||||||
|
{
|
||||||
|
Episode value = await _repository.Get(TestSample.Get<Episode>().Slug);
|
||||||
|
value.ExternalIDs = new List<MetadataID>
|
||||||
|
{
|
||||||
|
new()
|
||||||
|
{
|
||||||
|
Provider = TestSample.Get<Provider>(),
|
||||||
|
Link = "link",
|
||||||
|
DataID = "id"
|
||||||
|
},
|
||||||
|
};
|
||||||
|
await _repository.Edit(value, false);
|
||||||
|
|
||||||
|
{
|
||||||
|
await using DatabaseContext database = Repositories.Context.New();
|
||||||
|
Episode retrieved = await database.Episodes
|
||||||
|
.Include(x => x.ExternalIDs)
|
||||||
|
.ThenInclude(x => x.Provider)
|
||||||
|
.FirstAsync();
|
||||||
|
|
||||||
|
KAssert.DeepEqual(value, retrieved);
|
||||||
|
}
|
||||||
|
|
||||||
|
value.ExternalIDs.Add(new MetadataID
|
||||||
|
{
|
||||||
|
Provider = TestSample.GetNew<Provider>(),
|
||||||
|
Link = "link",
|
||||||
|
DataID = "id"
|
||||||
|
});
|
||||||
|
await _repository.Edit(value, false);
|
||||||
|
|
||||||
|
{
|
||||||
|
await using DatabaseContext database = Repositories.Context.New();
|
||||||
|
Episode retrieved = await database.Episodes
|
||||||
|
.Include(x => x.ExternalIDs)
|
||||||
|
.ThenInclude(x => x.Provider)
|
||||||
|
.FirstAsync();
|
||||||
|
|
||||||
|
KAssert.DeepEqual(value, retrieved);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[Theory]
|
||||||
|
[InlineData("test")]
|
||||||
|
[InlineData("super")]
|
||||||
|
[InlineData("title")]
|
||||||
|
[InlineData("TiTlE")]
|
||||||
|
[InlineData("SuPeR")]
|
||||||
|
public async Task SearchTest(string query)
|
||||||
|
{
|
||||||
|
Episode value = new()
|
||||||
|
{
|
||||||
|
Title = "This is a test super title",
|
||||||
|
ShowID = 1,
|
||||||
|
AbsoluteNumber = 2
|
||||||
|
};
|
||||||
|
await _repository.Create(value);
|
||||||
|
ICollection<Episode> ret = await _repository.Search(query);
|
||||||
|
KAssert.DeepEqual(value, ret.First());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -55,7 +55,7 @@ namespace Kyoo.Tests.Database
|
|||||||
[Fact]
|
[Fact]
|
||||||
public async Task GetCollectionTests()
|
public async Task GetCollectionTests()
|
||||||
{
|
{
|
||||||
LibraryItem expected = new(TestSample.Get<Show>());
|
LibraryItem expected = new(TestSample.Get<Collection>());
|
||||||
LibraryItem actual = await _repository.Get(-1);
|
LibraryItem actual = await _repository.Get(-1);
|
||||||
KAssert.DeepEqual(expected, actual);
|
KAssert.DeepEqual(expected, actual);
|
||||||
}
|
}
|
||||||
@ -79,9 +79,10 @@ namespace Kyoo.Tests.Database
|
|||||||
[Fact]
|
[Fact]
|
||||||
public async Task GetDuplicatedSlugTests()
|
public async Task GetDuplicatedSlugTests()
|
||||||
{
|
{
|
||||||
await _repositories.LibraryManager.Create(new Collection()
|
await _repositories.LibraryManager.Create(new Collection
|
||||||
{
|
{
|
||||||
Slug = TestSample.Get<Show>().Slug
|
Slug = TestSample.Get<Show>().Slug,
|
||||||
|
Name = "name"
|
||||||
});
|
});
|
||||||
await Assert.ThrowsAsync<InvalidOperationException>(() => _repository.Get(TestSample.Get<Show>().Slug));
|
await Assert.ThrowsAsync<InvalidOperationException>(() => _repository.Get(TestSample.Get<Show>().Slug));
|
||||||
}
|
}
|
158
tests/Kyoo.Tests/Database/SpecificTests/LibraryTests.cs
Normal file
158
tests/Kyoo.Tests/Database/SpecificTests/LibraryTests.cs
Normal file
@ -0,0 +1,158 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using Kyoo.Controllers;
|
||||||
|
using Kyoo.Models;
|
||||||
|
using Microsoft.EntityFrameworkCore;
|
||||||
|
using Xunit;
|
||||||
|
using Xunit.Abstractions;
|
||||||
|
|
||||||
|
namespace Kyoo.Tests.Database
|
||||||
|
{
|
||||||
|
namespace SqLite
|
||||||
|
{
|
||||||
|
public class LibraryTests : ALibraryTests
|
||||||
|
{
|
||||||
|
public LibraryTests(ITestOutputHelper output)
|
||||||
|
: base(new RepositoryActivator(output)) { }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
namespace PostgreSQL
|
||||||
|
{
|
||||||
|
[Collection(nameof(Postgresql))]
|
||||||
|
public class LibraryTests : ALibraryTests
|
||||||
|
{
|
||||||
|
public LibraryTests(PostgresFixture postgres, ITestOutputHelper output)
|
||||||
|
: base(new RepositoryActivator(output, postgres)) { }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public abstract class ALibraryTests : RepositoryTests<Library>
|
||||||
|
{
|
||||||
|
private readonly ILibraryRepository _repository;
|
||||||
|
|
||||||
|
protected ALibraryTests(RepositoryActivator repositories)
|
||||||
|
: base(repositories)
|
||||||
|
{
|
||||||
|
_repository = Repositories.LibraryManager.LibraryRepository;
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task CreateWithoutPathTest()
|
||||||
|
{
|
||||||
|
Library library = TestSample.GetNew<Library>();
|
||||||
|
library.Paths = null;
|
||||||
|
await Assert.ThrowsAsync<ArgumentException>(() => _repository.Create(library));
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task CreateWithEmptySlugTest()
|
||||||
|
{
|
||||||
|
Library library = TestSample.GetNew<Library>();
|
||||||
|
library.Slug = "";
|
||||||
|
await Assert.ThrowsAsync<ArgumentException>(() => _repository.Create(library));
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task CreateWithNumberSlugTest()
|
||||||
|
{
|
||||||
|
Library library = TestSample.GetNew<Library>();
|
||||||
|
library.Slug = "2";
|
||||||
|
Library ret = await _repository.Create(library);
|
||||||
|
Assert.Equal("2!", ret.Slug);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task CreateWithoutNameTest()
|
||||||
|
{
|
||||||
|
Library library = TestSample.GetNew<Library>();
|
||||||
|
library.Name = null;
|
||||||
|
await Assert.ThrowsAsync<ArgumentException>(() => _repository.Create(library));
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task CreateWithProvider()
|
||||||
|
{
|
||||||
|
Library library = TestSample.GetNew<Library>();
|
||||||
|
library.Providers = new[] { TestSample.Get<Provider>() };
|
||||||
|
await _repository.Create(library);
|
||||||
|
Library retrieved = await _repository.Get(2);
|
||||||
|
await Repositories.LibraryManager.Load(retrieved, x => x.Providers);
|
||||||
|
Assert.Equal(1, retrieved.Providers.Count);
|
||||||
|
Assert.Equal(TestSample.Get<Provider>().Slug, retrieved.Providers.First().Slug);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task EditTest()
|
||||||
|
{
|
||||||
|
Library value = await _repository.Get(TestSample.Get<Library>().Slug);
|
||||||
|
value.Paths = new [] {"/super", "/test"};
|
||||||
|
value.Name = "New Title";
|
||||||
|
Library edited = await _repository.Edit(value, false);
|
||||||
|
|
||||||
|
await using DatabaseContext database = Repositories.Context.New();
|
||||||
|
Library show = await database.Libraries.FirstAsync();
|
||||||
|
|
||||||
|
KAssert.DeepEqual(show, edited);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task EditProvidersTest()
|
||||||
|
{
|
||||||
|
Library value = await _repository.Get(TestSample.Get<Library>().Slug);
|
||||||
|
value.Providers = new[]
|
||||||
|
{
|
||||||
|
TestSample.GetNew<Provider>()
|
||||||
|
};
|
||||||
|
Library edited = await _repository.Edit(value, false);
|
||||||
|
|
||||||
|
await using DatabaseContext database = Repositories.Context.New();
|
||||||
|
Library show = await database.Libraries
|
||||||
|
.Include(x => x.Providers)
|
||||||
|
.FirstAsync();
|
||||||
|
|
||||||
|
show.Providers.ForEach(x => x.Libraries = null);
|
||||||
|
edited.Providers.ForEach(x => x.Libraries = null);
|
||||||
|
KAssert.DeepEqual(show, edited);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task AddProvidersTest()
|
||||||
|
{
|
||||||
|
Library value = await _repository.Get(TestSample.Get<Library>().Slug);
|
||||||
|
await Repositories.LibraryManager.Load(value, x => x.Providers);
|
||||||
|
value.Providers.Add(TestSample.GetNew<Provider>());
|
||||||
|
Library edited = await _repository.Edit(value, false);
|
||||||
|
|
||||||
|
await using DatabaseContext database = Repositories.Context.New();
|
||||||
|
Library show = await database.Libraries
|
||||||
|
.Include(x => x.Providers)
|
||||||
|
.FirstAsync();
|
||||||
|
|
||||||
|
show.Providers.ForEach(x => x.Libraries = null);
|
||||||
|
edited.Providers.ForEach(x => x.Libraries = null);
|
||||||
|
KAssert.DeepEqual(show, edited);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Theory]
|
||||||
|
[InlineData("test")]
|
||||||
|
[InlineData("super")]
|
||||||
|
[InlineData("title")]
|
||||||
|
[InlineData("TiTlE")]
|
||||||
|
[InlineData("SuPeR")]
|
||||||
|
public async Task SearchTest(string query)
|
||||||
|
{
|
||||||
|
Library value = new()
|
||||||
|
{
|
||||||
|
Slug = "super-test",
|
||||||
|
Name = "This is a test title",
|
||||||
|
Paths = new [] {"path"}
|
||||||
|
};
|
||||||
|
await _repository.Create(value);
|
||||||
|
ICollection<Library> ret = await _repository.Search(query);
|
||||||
|
KAssert.DeepEqual(value, ret.First());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
170
tests/Kyoo.Tests/Database/SpecificTests/PeopleTests.cs
Normal file
170
tests/Kyoo.Tests/Database/SpecificTests/PeopleTests.cs
Normal file
@ -0,0 +1,170 @@
|
|||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using Kyoo.Controllers;
|
||||||
|
using Kyoo.Models;
|
||||||
|
using Microsoft.EntityFrameworkCore;
|
||||||
|
using Xunit;
|
||||||
|
using Xunit.Abstractions;
|
||||||
|
|
||||||
|
namespace Kyoo.Tests.Database
|
||||||
|
{
|
||||||
|
namespace SqLite
|
||||||
|
{
|
||||||
|
public class PeopleTests : APeopleTests
|
||||||
|
{
|
||||||
|
public PeopleTests(ITestOutputHelper output)
|
||||||
|
: base(new RepositoryActivator(output)) { }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
namespace PostgreSQL
|
||||||
|
{
|
||||||
|
[Collection(nameof(Postgresql))]
|
||||||
|
public class PeopleTests : APeopleTests
|
||||||
|
{
|
||||||
|
public PeopleTests(PostgresFixture postgres, ITestOutputHelper output)
|
||||||
|
: base(new RepositoryActivator(output, postgres)) { }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public abstract class APeopleTests : RepositoryTests<People>
|
||||||
|
{
|
||||||
|
private readonly IPeopleRepository _repository;
|
||||||
|
|
||||||
|
protected APeopleTests(RepositoryActivator repositories)
|
||||||
|
: base(repositories)
|
||||||
|
{
|
||||||
|
_repository = Repositories.LibraryManager.PeopleRepository;
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task CreateWithExternalIdTest()
|
||||||
|
{
|
||||||
|
People value = TestSample.GetNew<People>();
|
||||||
|
value.ExternalIDs = new[]
|
||||||
|
{
|
||||||
|
new MetadataID
|
||||||
|
{
|
||||||
|
Provider = TestSample.Get<Provider>(),
|
||||||
|
Link = "link",
|
||||||
|
DataID = "id"
|
||||||
|
},
|
||||||
|
new MetadataID
|
||||||
|
{
|
||||||
|
Provider = TestSample.GetNew<Provider>(),
|
||||||
|
Link = "new-provider-link",
|
||||||
|
DataID = "new-id"
|
||||||
|
}
|
||||||
|
};
|
||||||
|
await _repository.Create(value);
|
||||||
|
|
||||||
|
People retrieved = await _repository.Get(2);
|
||||||
|
await Repositories.LibraryManager.Load(retrieved, x => x.ExternalIDs);
|
||||||
|
Assert.Equal(2, retrieved.ExternalIDs.Count);
|
||||||
|
KAssert.DeepEqual(value.ExternalIDs.First(), retrieved.ExternalIDs.First());
|
||||||
|
KAssert.DeepEqual(value.ExternalIDs.Last(), retrieved.ExternalIDs.Last());
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task EditTest()
|
||||||
|
{
|
||||||
|
People value = await _repository.Get(TestSample.Get<People>().Slug);
|
||||||
|
value.Name = "New Name";
|
||||||
|
value.Images = new Dictionary<int, string>
|
||||||
|
{
|
||||||
|
[Images.Poster] = "new-poster"
|
||||||
|
};
|
||||||
|
await _repository.Edit(value, false);
|
||||||
|
|
||||||
|
await using DatabaseContext database = Repositories.Context.New();
|
||||||
|
People retrieved = await database.People.FirstAsync();
|
||||||
|
|
||||||
|
KAssert.DeepEqual(value, retrieved);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task EditMetadataTest()
|
||||||
|
{
|
||||||
|
People value = await _repository.Get(TestSample.Get<People>().Slug);
|
||||||
|
value.ExternalIDs = new[]
|
||||||
|
{
|
||||||
|
new MetadataID
|
||||||
|
{
|
||||||
|
Provider = TestSample.Get<Provider>(),
|
||||||
|
Link = "link",
|
||||||
|
DataID = "id"
|
||||||
|
},
|
||||||
|
};
|
||||||
|
await _repository.Edit(value, false);
|
||||||
|
|
||||||
|
await using DatabaseContext database = Repositories.Context.New();
|
||||||
|
People retrieved = await database.People
|
||||||
|
.Include(x => x.ExternalIDs)
|
||||||
|
.ThenInclude(x => x.Provider)
|
||||||
|
.FirstAsync();
|
||||||
|
|
||||||
|
KAssert.DeepEqual(value, retrieved);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task AddMetadataTest()
|
||||||
|
{
|
||||||
|
People value = await _repository.Get(TestSample.Get<People>().Slug);
|
||||||
|
value.ExternalIDs = new List<MetadataID>
|
||||||
|
{
|
||||||
|
new()
|
||||||
|
{
|
||||||
|
Provider = TestSample.Get<Provider>(),
|
||||||
|
Link = "link",
|
||||||
|
DataID = "id"
|
||||||
|
},
|
||||||
|
};
|
||||||
|
await _repository.Edit(value, false);
|
||||||
|
|
||||||
|
{
|
||||||
|
await using DatabaseContext database = Repositories.Context.New();
|
||||||
|
People retrieved = await database.People
|
||||||
|
.Include(x => x.ExternalIDs)
|
||||||
|
.ThenInclude(x => x.Provider)
|
||||||
|
.FirstAsync();
|
||||||
|
|
||||||
|
KAssert.DeepEqual(value, retrieved);
|
||||||
|
}
|
||||||
|
|
||||||
|
value.ExternalIDs.Add(new MetadataID
|
||||||
|
{
|
||||||
|
Provider = TestSample.GetNew<Provider>(),
|
||||||
|
Link = "link",
|
||||||
|
DataID = "id"
|
||||||
|
});
|
||||||
|
await _repository.Edit(value, false);
|
||||||
|
|
||||||
|
{
|
||||||
|
await using DatabaseContext database = Repositories.Context.New();
|
||||||
|
People retrieved = await database.People
|
||||||
|
.Include(x => x.ExternalIDs)
|
||||||
|
.ThenInclude(x => x.Provider)
|
||||||
|
.FirstAsync();
|
||||||
|
|
||||||
|
KAssert.DeepEqual(value, retrieved);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[Theory]
|
||||||
|
[InlineData("Me")]
|
||||||
|
[InlineData("me")]
|
||||||
|
[InlineData("na")]
|
||||||
|
public async Task SearchTest(string query)
|
||||||
|
{
|
||||||
|
People value = new()
|
||||||
|
{
|
||||||
|
Slug = "slug",
|
||||||
|
Name = "name",
|
||||||
|
};
|
||||||
|
await _repository.Create(value);
|
||||||
|
ICollection<People> ret = await _repository.Search(query);
|
||||||
|
KAssert.DeepEqual(value, ret.First());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -16,13 +16,6 @@ namespace Kyoo.Tests.Database
|
|||||||
_repositories = new RepositoryActivator(output);
|
_repositories = new RepositoryActivator(output);
|
||||||
}
|
}
|
||||||
|
|
||||||
[Fact]
|
|
||||||
[SuppressMessage("ReSharper", "EqualExpressionComparison")]
|
|
||||||
public void SampleTest()
|
|
||||||
{
|
|
||||||
Assert.False(ReferenceEquals(TestSample.Get<Show>(), TestSample.Get<Show>()));
|
|
||||||
}
|
|
||||||
|
|
||||||
public void Dispose()
|
public void Dispose()
|
||||||
{
|
{
|
||||||
_repositories.Dispose();
|
_repositories.Dispose();
|
||||||
@ -33,5 +26,12 @@ namespace Kyoo.Tests.Database
|
|||||||
{
|
{
|
||||||
return _repositories.DisposeAsync();
|
return _repositories.DisposeAsync();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
[SuppressMessage("ReSharper", "EqualExpressionComparison")]
|
||||||
|
public void SampleTest()
|
||||||
|
{
|
||||||
|
Assert.False(ReferenceEquals(TestSample.Get<Show>(), TestSample.Get<Show>()));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
x
Reference in New Issue
Block a user