mirror of
https://github.com/zoriya/Kyoo.git
synced 2025-05-31 04:04:21 -04:00
Improve duplicated error handling
This commit is contained in:
parent
f59f9a7ba0
commit
9a5c4ab087
@ -26,7 +26,7 @@ namespace Kyoo.Abstractions.Controllers
|
|||||||
public interface ILibraryManager
|
public interface ILibraryManager
|
||||||
{
|
{
|
||||||
IRepository<T> Repository<T>()
|
IRepository<T> Repository<T>()
|
||||||
where T : class, IResource, IQuery;
|
where T : IResource, IQuery;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The repository that handle libraries items (a wrapper around shows and collections).
|
/// The repository that handle libraries items (a wrapper around shows and collections).
|
||||||
|
@ -30,7 +30,7 @@ namespace Kyoo.Abstractions.Controllers
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
/// <typeparam name="T">The resource's type that this repository manage.</typeparam>
|
/// <typeparam name="T">The resource's type that this repository manage.</typeparam>
|
||||||
public interface IRepository<T> : IBaseRepository
|
public interface IRepository<T> : IBaseRepository
|
||||||
where T : class, IResource, IQuery
|
where T : IResource, IQuery
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The event handler type for all events of this repository.
|
/// The event handler type for all events of this repository.
|
||||||
|
@ -36,7 +36,7 @@ namespace Kyoo.Abstractions.Controllers
|
|||||||
/// The item to cache images.
|
/// The item to cache images.
|
||||||
/// </param>
|
/// </param>
|
||||||
/// <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>A <see cref="Task"/> representing the asynchronous operation.</returns>
|
||||||
Task DownloadImages<T>(T item)
|
Task DownloadImages<T>(T item)
|
||||||
where T : IThumbnails;
|
where T : IThumbnails;
|
||||||
|
|
||||||
@ -50,5 +50,16 @@ namespace Kyoo.Abstractions.Controllers
|
|||||||
/// <returns>The path of the image for the given resource or null if it does not exists.</returns>
|
/// <returns>The path of the image for the given resource or null if it does not exists.</returns>
|
||||||
string GetImagePath<T>(T item, string image, ImageQuality quality)
|
string GetImagePath<T>(T item, string image, ImageQuality quality)
|
||||||
where T : IThumbnails;
|
where T : IThumbnails;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Delete images associated with the item.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="item">
|
||||||
|
/// The item with cached images.
|
||||||
|
/// </param>
|
||||||
|
/// <typeparam name="T">The type of the item</typeparam>
|
||||||
|
/// <returns>A <see cref="Task"/> representing the asynchronous operation.</returns>
|
||||||
|
Task DeleteImages<T>(T item)
|
||||||
|
where T : IThumbnails;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -25,7 +25,7 @@ namespace Kyoo.Abstractions.Models
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// An interface to represent a resource that can be retrieved from the database.
|
/// An interface to represent a resource that can be retrieved from the database.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public interface IResource
|
public interface IResource : IQuery
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// A unique ID for this type of resource. This can't be changed and duplicates are not allowed.
|
/// A unique ID for this type of resource. This can't be changed and duplicates are not allowed.
|
||||||
|
@ -92,7 +92,7 @@ namespace Kyoo.Core.Controllers
|
|||||||
public IRepository<User> Users { get; }
|
public IRepository<User> Users { get; }
|
||||||
|
|
||||||
public IRepository<T> Repository<T>()
|
public IRepository<T> Repository<T>()
|
||||||
where T : class, IResource, IQuery
|
where T : IResource, IQuery
|
||||||
{
|
{
|
||||||
return (IRepository<T>)_repositories.First(x => x.RepositoryType == typeof(T));
|
return (IRepository<T>)_repositories.First(x => x.RepositoryType == typeof(T));
|
||||||
}
|
}
|
||||||
|
@ -84,16 +84,20 @@ namespace Kyoo.Core.Controllers
|
|||||||
.ToListAsync();
|
.ToListAsync();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected override Task<Episode?> GetDuplicated(Episode item)
|
||||||
|
{
|
||||||
|
if (item is { SeasonNumber: not null, EpisodeNumber: not null })
|
||||||
|
return _database.Episodes.FirstOrDefaultAsync(x => x.ShowId == item.ShowId && x.SeasonNumber == item.SeasonNumber && x.EpisodeNumber == item.EpisodeNumber);
|
||||||
|
return _database.Episodes.FirstOrDefaultAsync(x => x.ShowId == item.ShowId && x.AbsoluteNumber == item.AbsoluteNumber);
|
||||||
|
}
|
||||||
|
|
||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
public override async Task<Episode> Create(Episode obj)
|
public override async Task<Episode> Create(Episode obj)
|
||||||
{
|
{
|
||||||
obj.ShowSlug = obj.Show?.Slug ?? (await _database.Shows.FirstAsync(x => x.Id == obj.ShowId)).Slug;
|
obj.ShowSlug = obj.Show?.Slug ?? (await _database.Shows.FirstAsync(x => x.Id == obj.ShowId)).Slug;
|
||||||
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(() => GetDuplicated(obj));
|
||||||
obj is { SeasonNumber: not null, EpisodeNumber: not null }
|
|
||||||
? _database.Episodes.FirstOrDefaultAsync(x => x.ShowId == obj.ShowId && x.SeasonNumber == obj.SeasonNumber && x.EpisodeNumber == obj.EpisodeNumber)
|
|
||||||
: _database.Episodes.FirstOrDefaultAsync(x => x.ShowId == obj.ShowId && x.AbsoluteNumber == obj.AbsoluteNumber));
|
|
||||||
await IRepository<Episode>.OnResourceCreated(obj);
|
await IRepository<Episode>.OnResourceCreated(obj);
|
||||||
return obj;
|
return obj;
|
||||||
}
|
}
|
||||||
|
@ -214,6 +214,11 @@ namespace Kyoo.Core.Controllers
|
|||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected virtual Task<T?> GetDuplicated(T item)
|
||||||
|
{
|
||||||
|
return GetOrDefault(item.Slug);
|
||||||
|
}
|
||||||
|
|
||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
public virtual Task<T?> GetOrDefault(Guid id, Include<T>? include = default)
|
public virtual Task<T?> GetOrDefault(Guid id, Include<T>? include = default)
|
||||||
{
|
{
|
||||||
@ -324,7 +329,14 @@ namespace Kyoo.Core.Controllers
|
|||||||
await Validate(obj);
|
await Validate(obj);
|
||||||
if (obj is IThumbnails thumbs)
|
if (obj is IThumbnails thumbs)
|
||||||
{
|
{
|
||||||
await _thumbs.DownloadImages(thumbs);
|
try
|
||||||
|
{
|
||||||
|
await _thumbs.DownloadImages(thumbs);
|
||||||
|
}
|
||||||
|
catch (DuplicatedItemException e) when (e.Existing is null)
|
||||||
|
{
|
||||||
|
throw new DuplicatedItemException(await GetDuplicated(obj));
|
||||||
|
}
|
||||||
if (thumbs.Poster != null)
|
if (thumbs.Poster != null)
|
||||||
Database.Entry(thumbs).Reference(x => x.Poster).TargetEntry!.State = EntityState.Added;
|
Database.Entry(thumbs).Reference(x => x.Poster).TargetEntry!.State = EntityState.Added;
|
||||||
if (thumbs.Thumbnail != null)
|
if (thumbs.Thumbnail != null)
|
||||||
@ -470,6 +482,8 @@ namespace Kyoo.Core.Controllers
|
|||||||
public virtual Task Delete(T obj)
|
public virtual Task Delete(T obj)
|
||||||
{
|
{
|
||||||
IRepository<T>.OnResourceDeleted(obj);
|
IRepository<T>.OnResourceDeleted(obj);
|
||||||
|
if (obj is IThumbnails thumbs)
|
||||||
|
return _thumbs.DeleteImages(thumbs);
|
||||||
return Task.CompletedTask;
|
return Task.CompletedTask;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -16,12 +16,13 @@
|
|||||||
// You should have received a copy of the GNU General Public License
|
// You should have received a copy of the GNU General Public License
|
||||||
// along with Kyoo. If not, see <https://www.gnu.org/licenses/>.
|
// along with Kyoo. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
|
using System.ComponentModel.DataAnnotations;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using Kyoo.Abstractions.Controllers;
|
using Kyoo.Abstractions.Controllers;
|
||||||
using Kyoo.Abstractions.Models;
|
using Kyoo.Abstractions.Models;
|
||||||
|
using Kyoo.Abstractions.Models.Exceptions;
|
||||||
using Kyoo.Abstractions.Models.Utils;
|
using Kyoo.Abstractions.Models.Utils;
|
||||||
using Kyoo.Postgresql;
|
using Kyoo.Postgresql;
|
||||||
using Microsoft.EntityFrameworkCore;
|
using Microsoft.EntityFrameworkCore;
|
||||||
@ -70,6 +71,11 @@ namespace Kyoo.Core.Controllers
|
|||||||
_database = database;
|
_database = database;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected override Task<Season?> GetDuplicated(Season item)
|
||||||
|
{
|
||||||
|
return _database.Seasons.FirstOrDefaultAsync(x => x.ShowId == item.ShowId && x.SeasonNumber == item.SeasonNumber);
|
||||||
|
}
|
||||||
|
|
||||||
/// <inheritdoc/>
|
/// <inheritdoc/>
|
||||||
public override async Task<ICollection<Season>> Search(string query, Include<Season>? include = default)
|
public override async Task<ICollection<Season>> Search(string query, Include<Season>? include = default)
|
||||||
{
|
{
|
||||||
@ -83,11 +89,10 @@ namespace Kyoo.Core.Controllers
|
|||||||
public override async Task<Season> Create(Season obj)
|
public override async Task<Season> Create(Season obj)
|
||||||
{
|
{
|
||||||
await base.Create(obj);
|
await base.Create(obj);
|
||||||
obj.ShowSlug = _database.Shows.First(x => x.Id == obj.ShowId).Slug;
|
obj.ShowSlug = (await _database.Shows.FirstOrDefaultAsync(x => x.Id == obj.ShowId))?.Slug
|
||||||
|
?? throw new ItemNotFoundException($"No show found with ID {obj.ShowId}");
|
||||||
_database.Entry(obj).State = EntityState.Added;
|
_database.Entry(obj).State = EntityState.Added;
|
||||||
await _database.SaveChangesAsync(() =>
|
await _database.SaveChangesAsync(() => GetDuplicated(obj));
|
||||||
_database.Seasons.FirstOrDefaultAsync(x => x.ShowId == obj.ShowId && x.SeasonNumber == obj.SeasonNumber)
|
|
||||||
);
|
|
||||||
await IRepository<Season>.OnResourceCreated(obj);
|
await IRepository<Season>.OnResourceCreated(obj);
|
||||||
return obj;
|
return obj;
|
||||||
}
|
}
|
||||||
@ -100,7 +105,7 @@ namespace Kyoo.Core.Controllers
|
|||||||
{
|
{
|
||||||
if (resource.Show == null)
|
if (resource.Show == null)
|
||||||
{
|
{
|
||||||
throw new ArgumentException($"Can't store a season not related to any show " +
|
throw new ValidationException($"Can't store a season not related to any show " +
|
||||||
$"(showID: {resource.ShowId}).");
|
$"(showID: {resource.ShowId}).");
|
||||||
}
|
}
|
||||||
resource.ShowId = resource.Show.Id;
|
resource.ShowId = resource.Show.Id;
|
||||||
|
@ -19,6 +19,7 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
|
using System.Linq;
|
||||||
using System.Net.Http;
|
using System.Net.Http;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using Blurhash.SkiaSharp;
|
using Blurhash.SkiaSharp;
|
||||||
@ -167,5 +168,24 @@ namespace Kyoo.Core.Controllers
|
|||||||
{
|
{
|
||||||
return $"{_GetBaseImagePath(item, image)}.{quality.ToString().ToLowerInvariant()}.webp";
|
return $"{_GetBaseImagePath(item, image)}.{quality.ToString().ToLowerInvariant()}.webp";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public Task DeleteImages<T>(T item)
|
||||||
|
where T : IThumbnails
|
||||||
|
{
|
||||||
|
IEnumerable<string> images = new[] { "poster", "thumbnail", "logo" }
|
||||||
|
.SelectMany(x => _GetBaseImagePath(item, x))
|
||||||
|
.SelectMany(x => new[]
|
||||||
|
{
|
||||||
|
ImageQuality.High.ToString().ToLowerInvariant(),
|
||||||
|
ImageQuality.Medium.ToString().ToLowerInvariant(),
|
||||||
|
ImageQuality.Low.ToString().ToLowerInvariant(),
|
||||||
|
}.Select(quality => $"{x}.{quality}.webp")
|
||||||
|
);
|
||||||
|
|
||||||
|
foreach (string image in images)
|
||||||
|
File.Delete(image);
|
||||||
|
return Task.CompletedTask;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -54,9 +54,13 @@ namespace Kyoo.Core
|
|||||||
case ItemNotFoundException ex:
|
case ItemNotFoundException ex:
|
||||||
context.Result = new NotFoundObjectResult(new RequestError(ex.Message));
|
context.Result = new NotFoundObjectResult(new RequestError(ex.Message));
|
||||||
break;
|
break;
|
||||||
case DuplicatedItemException ex:
|
case DuplicatedItemException ex when ex.Existing is not null:
|
||||||
context.Result = new ConflictObjectResult(ex.Existing);
|
context.Result = new ConflictObjectResult(ex.Existing);
|
||||||
break;
|
break;
|
||||||
|
case DuplicatedItemException:
|
||||||
|
// Should not happen but if it does, it is better than returning a 409 with no body since clients expect json content
|
||||||
|
context.Result = new ConflictObjectResult(new RequestError("Duplicated item"));
|
||||||
|
break;
|
||||||
case Exception ex:
|
case Exception ex:
|
||||||
_logger.LogError(ex, "Unhandled error");
|
_logger.LogError(ex, "Unhandled error");
|
||||||
context.Result = new ServerErrorObjectResult(new RequestError("Internal Server Error"));
|
context.Result = new ServerErrorObjectResult(new RequestError("Internal Server Error"));
|
||||||
|
Loading…
x
Reference in New Issue
Block a user