// Kyoo - A portable and vast media library solution.
// Copyright (c) Kyoo.
//
// See AUTHORS.md and LICENSE file in the project root for full license information.
//
// Kyoo is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// any later version.
//
// Kyoo is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Kyoo. If not, see .
using System;
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using System.Linq;
using System.Linq.Expressions;
using System.Threading.Tasks;
using Kyoo.Abstractions.Controllers;
using Kyoo.Abstractions.Models;
using Kyoo.Abstractions.Models.Exceptions;
using Kyoo.Utils;
namespace Kyoo.Core.Controllers
{
///
/// An class to interact with the database. Every repository is mapped through here.
///
public class LibraryManager : ILibraryManager
{
///
/// The list of repositories
///
private readonly IBaseRepository[] _repositories;
///
public ILibraryRepository LibraryRepository { get; }
///
public ILibraryItemRepository LibraryItemRepository { get; }
///
public ICollectionRepository CollectionRepository { get; }
///
public IShowRepository ShowRepository { get; }
///
public ISeasonRepository SeasonRepository { get; }
///
public IEpisodeRepository EpisodeRepository { get; }
///
public IPeopleRepository PeopleRepository { get; }
///
public IStudioRepository StudioRepository { get; }
///
public IGenreRepository GenreRepository { get; }
///
public IUserRepository UserRepository { get; }
///
/// Create a new instance with every repository available.
///
/// The list of repositories that this library manager should manage.
/// If a repository for every base type is not available, this instance won't be stable.
public LibraryManager(IEnumerable repositories)
{
_repositories = repositories.ToArray();
LibraryRepository = GetRepository() as ILibraryRepository;
LibraryItemRepository = GetRepository() as ILibraryItemRepository;
CollectionRepository = GetRepository() as ICollectionRepository;
ShowRepository = GetRepository() as IShowRepository;
SeasonRepository = GetRepository() as ISeasonRepository;
EpisodeRepository = GetRepository() as IEpisodeRepository;
PeopleRepository = GetRepository() as IPeopleRepository;
StudioRepository = GetRepository() as IStudioRepository;
GenreRepository = GetRepository() as IGenreRepository;
UserRepository = GetRepository() as IUserRepository;
}
///
public IRepository GetRepository()
where T : class, IResource
{
if (_repositories.FirstOrDefault(x => x.RepositoryType == typeof(T)) is IRepository ret)
return ret;
throw new ItemNotFoundException($"No repository found for the type {typeof(T).Name}.");
}
///
public Task Get(int id)
where T : class, IResource
{
return GetRepository().Get(id);
}
///
public Task Get(string slug)
where T : class, IResource
{
return GetRepository().Get(slug);
}
///
public Task Get(Expression> where)
where T : class, IResource
{
return GetRepository().Get(where);
}
///
public Task Get(int showID, int seasonNumber)
{
return SeasonRepository.Get(showID, seasonNumber);
}
///
public Task Get(string showSlug, int seasonNumber)
{
return SeasonRepository.Get(showSlug, seasonNumber);
}
///
public Task Get(int showID, int seasonNumber, int episodeNumber)
{
return EpisodeRepository.Get(showID, seasonNumber, episodeNumber);
}
///
public Task Get(string showSlug, int seasonNumber, int episodeNumber)
{
return EpisodeRepository.Get(showSlug, seasonNumber, episodeNumber);
}
///
public async Task GetOrDefault(int id)
where T : class, IResource
{
return await GetRepository().GetOrDefault(id);
}
///
public async Task GetOrDefault(string slug)
where T : class, IResource
{
return await GetRepository().GetOrDefault(slug);
}
///
public async Task GetOrDefault(Expression> where, Sort sortBy)
where T : class, IResource
{
return await GetRepository().GetOrDefault(where, sortBy);
}
///
public async Task GetOrDefault(int showID, int seasonNumber)
{
return await SeasonRepository.GetOrDefault(showID, seasonNumber);
}
///
public async Task GetOrDefault(string showSlug, int seasonNumber)
{
return await SeasonRepository.GetOrDefault(showSlug, seasonNumber);
}
///
public async Task GetOrDefault(int showID, int seasonNumber, int episodeNumber)
{
return await EpisodeRepository.GetOrDefault(showID, seasonNumber, episodeNumber);
}
///
public async Task GetOrDefault(string showSlug, int seasonNumber, int episodeNumber)
{
return await EpisodeRepository.GetOrDefault(showSlug, seasonNumber, episodeNumber);
}
///
/// Set relations between to objects.
///
/// The owner object
/// A Task to load a collection of related objects
/// A setter function to store the collection of related objects
/// A setter function to store the owner of a releated object loaded
/// The type of the owner object
/// The type of the related object
private static async Task _SetRelation(T1 obj,
Task> loader,
Action> setter,
Action inverse)
{
ICollection loaded = await loader;
setter(obj, loaded);
foreach (T2 item in loaded)
inverse(item, obj);
}
///
public Task Load(T obj, Expression> 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);
}
///
public Task Load(T obj, Expression>> 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);
}
///
public async Task Load(T obj, string memberName, bool force = false)
where T : class, IResource
{
await Load(obj as IResource, memberName, force);
return obj;
}
///
[SuppressMessage("StyleCop.CSharp.LayoutRules", "SA1507:CodeMustNotContainMultipleBlankLinesInARow",
Justification = "Separate the code by semantics and simplify the code read.")]
[SuppressMessage("StyleCop.CSharp.ReadabilityRules", "SA1107:Code should not contain multiple statements on one line",
Justification = "Assing IDs and Values in the same line.")]
public Task Load(IResource obj, string memberName, bool force = false)
{
if (obj == null)
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
{
(Library l, nameof(Library.Shows)) => ShowRepository
.GetAll(x => x.Libraries.Any(y => y.ID == obj.ID))
.Then(x => l.Shows = x),
(Library l, nameof(Library.Collections)) => CollectionRepository
.GetAll(x => x.Libraries.Any(y => y.ID == obj.ID))
.Then(x => l.Collections = x),
(Collection c, nameof(Collection.Shows)) => ShowRepository
.GetAll(x => x.Collections.Any(y => y.ID == obj.ID))
.Then(x => c.Shows = x),
(Collection c, nameof(Collection.Libraries)) => LibraryRepository
.GetAll(x => x.Collections.Any(y => y.ID == obj.ID))
.Then(x => c.Libraries = x),
(Show s, nameof(Show.Genres)) => GenreRepository
.GetAll(x => x.Shows.Any(y => y.ID == obj.ID))
.Then(x => s.Genres = x),
(Show s, nameof(Show.People)) => PeopleRepository
.GetFromShow(obj.ID)
.Then(x => s.People = x),
(Show s, nameof(Show.Seasons)) => _SetRelation(s,
SeasonRepository.GetAll(x => x.Show.ID == obj.ID),
(x, y) => x.Seasons = y,
(x, y) => { x.Show = y; x.ShowID = y.ID; }),
(Show s, nameof(Show.Episodes)) => _SetRelation(s,
EpisodeRepository.GetAll(x => x.Show.ID == obj.ID),
(x, y) => x.Episodes = y,
(x, y) => { x.Show = y; x.ShowID = y.ID; }),
(Show s, nameof(Show.Libraries)) => LibraryRepository
.GetAll(x => x.Shows.Any(y => y.ID == obj.ID))
.Then(x => s.Libraries = x),
(Show s, nameof(Show.Collections)) => CollectionRepository
.GetAll(x => x.Shows.Any(y => y.ID == obj.ID))
.Then(x => s.Collections = x),
(Show s, nameof(Show.Studio)) => StudioRepository
.GetOrDefault(x => x.Shows.Any(y => y.ID == obj.ID))
.Then(x =>
{
s.Studio = x;
s.StudioID = x?.ID ?? 0;
}),
(Season s, nameof(Season.Episodes)) => _SetRelation(s,
EpisodeRepository.GetAll(x => x.Season.ID == obj.ID),
(x, y) => x.Episodes = y,
(x, y) => { x.Season = y; x.SeasonID = y.ID; }),
(Season s, nameof(Season.Show)) => ShowRepository
.GetOrDefault(x => x.Seasons.Any(y => y.ID == obj.ID))
.Then(x =>
{
s.Show = x;
s.ShowID = x?.ID ?? 0;
}),
(Episode e, nameof(Episode.Show)) => ShowRepository
.GetOrDefault(x => x.Episodes.Any(y => y.ID == obj.ID))
.Then(x =>
{
e.Show = x;
e.ShowID = x?.ID ?? 0;
}),
(Episode e, nameof(Episode.Season)) => SeasonRepository
.GetOrDefault(x => x.Episodes.Any(y => y.ID == e.ID))
.Then(x =>
{
e.Season = x;
e.SeasonID = x?.ID ?? 0;
}),
(Genre g, nameof(Genre.Shows)) => ShowRepository
.GetAll(x => x.Genres.Any(y => y.ID == obj.ID))
.Then(x => g.Shows = x),
(Studio s, nameof(Studio.Shows)) => ShowRepository
.GetAll(x => x.Studio.ID == obj.ID)
.Then(x => s.Shows = x),
(People p, nameof(People.Roles)) => PeopleRepository
.GetFromPeople(obj.ID)
.Then(x => p.Roles = x),
_ => throw new ArgumentException($"Couldn't find a way to load {memberName} of {obj.Slug}.")
};
}
///
public Task> GetItemsFromLibrary(int id,
Expression> where = null,
Sort sort = default,
Pagination limit = default)
{
return LibraryItemRepository.GetFromLibrary(id, where, sort, limit);
}
///
public Task> GetItemsFromLibrary(string slug,
Expression> where = null,
Sort sort = default,
Pagination limit = default)
{
return LibraryItemRepository.GetFromLibrary(slug, where, sort, limit);
}
///
public Task> GetPeopleFromShow(int showID,
Expression> where = null,
Sort sort = default,
Pagination limit = default)
{
return PeopleRepository.GetFromShow(showID, where, sort, limit);
}
///
public Task> GetPeopleFromShow(string showSlug,
Expression> where = null,
Sort sort = default,
Pagination limit = default)
{
return PeopleRepository.GetFromShow(showSlug, where, sort, limit);
}
///
public Task> GetRolesFromPeople(int id,
Expression> where = null,
Sort sort = default,
Pagination limit = default)
{
return PeopleRepository.GetFromPeople(id, where, sort, limit);
}
///
public Task> GetRolesFromPeople(string slug,
Expression> where = null,
Sort sort = default,
Pagination limit = default)
{
return PeopleRepository.GetFromPeople(slug, where, sort, limit);
}
///
public Task AddShowLink(int showID, int? libraryID, int? collectionID)
{
return ShowRepository.AddShowLink(showID, libraryID, collectionID);
}
///
public Task AddShowLink(Show show, Library library, Collection collection)
{
if (show == null)
throw new ArgumentNullException(nameof(show));
return ShowRepository.AddShowLink(show.ID, library?.ID, collection?.ID);
}
///
public Task> GetAll(Expression> where = null,
Sort sort = default,
Pagination limit = default)
where T : class, IResource
{
return GetRepository().GetAll(where, sort, limit);
}
///
public Task GetCount(Expression> where = null)
where T : class, IResource
{
return GetRepository().GetCount(where);
}
///
public Task> Search(string query)
where T : class, IResource
{
return GetRepository().Search(query);
}
///
public Task Create(T item)
where T : class, IResource
{
return GetRepository().Create(item);
}
///
public Task CreateIfNotExists(T item)
where T : class, IResource
{
return GetRepository().CreateIfNotExists(item);
}
///
public Task Edit(T item, bool resetOld)
where T : class, IResource
{
return GetRepository().Edit(item, resetOld);
}
///
public Task Delete(T item)
where T : class, IResource
{
return GetRepository().Delete(item);
}
///
public Task Delete(int id)
where T : class, IResource
{
return GetRepository().Delete(id);
}
///
public Task Delete(string slug)
where T : class, IResource
{
return GetRepository().Delete(slug);
}
}
}