// 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.Linq; using System.Linq.Expressions; using System.Threading.Tasks; using Kyoo.Abstractions.Controllers; using Kyoo.Abstractions.Models; using Kyoo.Abstractions.Models.Exceptions; using Kyoo.Database; using Kyoo.Utils; using Microsoft.EntityFrameworkCore; namespace Kyoo.Core.Controllers { /// /// A local repository to handle episodes. /// public class EpisodeRepository : LocalRepository, IEpisodeRepository { /// /// The database handle /// private readonly DatabaseContext _database; /// /// A provider repository to handle externalID creation and deletion /// private readonly IProviderRepository _providers; /// /// A track repository to handle creation and deletion of tracks related to the current episode. /// private readonly ITrackRepository _tracks; /// protected override Expression> DefaultSort => x => x.EpisodeNumber; /// /// Create a new . /// /// The database handle to use. /// A provider repository /// A track repository public EpisodeRepository(DatabaseContext database, IProviderRepository providers, ITrackRepository tracks) : base(database) { _database = database; _providers = providers; _tracks = tracks; } /// public Task GetOrDefault(int showID, int seasonNumber, int episodeNumber) { return _database.Episodes.FirstOrDefaultAsync(x => x.ShowID == showID && x.SeasonNumber == seasonNumber && x.EpisodeNumber == episodeNumber); } /// public Task GetOrDefault(string showSlug, int seasonNumber, int episodeNumber) { return _database.Episodes.FirstOrDefaultAsync(x => x.Show.Slug == showSlug && x.SeasonNumber == seasonNumber && x.EpisodeNumber == episodeNumber); } /// public async Task Get(int showID, int seasonNumber, int episodeNumber) { Episode ret = await GetOrDefault(showID, seasonNumber, episodeNumber); if (ret == null) throw new ItemNotFoundException($"No episode S{seasonNumber}E{episodeNumber} found on the show {showID}."); return ret; } /// public async Task Get(string showSlug, int seasonNumber, int episodeNumber) { Episode ret = await GetOrDefault(showSlug, seasonNumber, episodeNumber); if (ret == null) throw new ItemNotFoundException($"No episode S{seasonNumber}E{episodeNumber} found on the show {showSlug}."); return ret; } /// public Task GetAbsolute(int showID, int absoluteNumber) { return _database.Episodes.FirstOrDefaultAsync(x => x.ShowID == showID && x.AbsoluteNumber == absoluteNumber); } /// public Task GetAbsolute(string showSlug, int absoluteNumber) { return _database.Episodes.FirstOrDefaultAsync(x => x.Show.Slug == showSlug && x.AbsoluteNumber == absoluteNumber); } /// public override async Task> Search(string query) { return await _database.Episodes .Where(x => x.EpisodeNumber != null || x.AbsoluteNumber != null) .Where(_database.Like(x => x.Title, $"%{query}%")) .OrderBy(DefaultSort) .Take(20) .ToListAsync(); } /// public override async Task Create(Episode obj) { await base.Create(obj); _database.Entry(obj).State = EntityState.Added; await _database.SaveChangesAsync($"Trying to insert a duplicated episode (slug {obj.Slug} already exists)."); return await _ValidateTracks(obj); } /// protected override async Task EditRelations(Episode resource, Episode changed, bool resetOld) { await Validate(changed); if (changed.Tracks != null || resetOld) { await _tracks.DeleteAll(x => x.EpisodeID == resource.ID); resource.Tracks = changed.Tracks; await _ValidateTracks(resource); } if (changed.ExternalIDs != null || resetOld) { await Database.Entry(resource).Collection(x => x.ExternalIDs).LoadAsync(); resource.ExternalIDs = changed.ExternalIDs; } } /// /// Set track's index and ensure that every tracks is well-formed. /// /// The resource to fix. /// The parameter is returned. private async Task _ValidateTracks(Episode resource) { if (resource.Tracks == null) return resource; resource.Tracks = await resource.Tracks.SelectAsync(x => { x.Episode = resource; x.EpisodeSlug = resource.Slug; return _tracks.Create(x); }).ToListAsync(); _database.Tracks.AttachRange(resource.Tracks); return resource; } /// protected override async Task Validate(Episode resource) { await base.Validate(resource); if (resource.ShowID <= 0) { if (resource.Show == null) { throw new ArgumentException($"Can't store an episode not related " + $"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(id.Provider.Slug) ?? await _providers.CreateIfNotExists(id.Provider); id.ProviderID = id.Provider.ID; } _database.MetadataIds().AttachRange(resource.ExternalIDs); } } /// public override async Task Delete(Episode obj) { if (obj == null) throw new ArgumentNullException(nameof(obj)); _database.Entry(obj).State = EntityState.Deleted; await obj.Tracks.ForEachAsync(x => _tracks.Delete(x)); obj.ExternalIDs.ForEach(x => _database.Entry(x).State = EntityState.Deleted); await _database.SaveChangesAsync(); } } }