From f59f9a7ba0e1dcb28178414402955d34c18da557 Mon Sep 17 00:00:00 2001 From: Zoe Roux Date: Tue, 28 Nov 2023 01:17:10 +0100 Subject: [PATCH] Handle movies with the same slug but not the same date --- .../Controllers/ThumbnailsManager.cs | 57 +++++++++++++++---- scanner/scanner/scanner.py | 21 +++++-- 2 files changed, 63 insertions(+), 15 deletions(-) diff --git a/back/src/Kyoo.Core/Controllers/ThumbnailsManager.cs b/back/src/Kyoo.Core/Controllers/ThumbnailsManager.cs index 6e22c7dc..6e4720ce 100644 --- a/back/src/Kyoo.Core/Controllers/ThumbnailsManager.cs +++ b/back/src/Kyoo.Core/Controllers/ThumbnailsManager.cs @@ -17,17 +17,17 @@ // along with Kyoo. If not, see . using System; +using System.Collections.Generic; using System.IO; using System.Net.Http; using System.Threading.Tasks; using Blurhash.SkiaSharp; using Kyoo.Abstractions.Controllers; using Kyoo.Abstractions.Models; +using Kyoo.Abstractions.Models.Exceptions; using Microsoft.Extensions.Logging; using SkiaSharp; -#nullable enable - namespace Kyoo.Core.Controllers { /// @@ -35,9 +35,8 @@ namespace Kyoo.Core.Controllers /// public class ThumbnailsManager : IThumbnailsManager { - /// - /// A logger to report errors. - /// + private static readonly Dictionary> _downloading = new(); + private readonly ILogger _logger; private readonly IHttpClientFactory _clientFactory; @@ -106,13 +105,49 @@ namespace Kyoo.Core.Controllers public async Task DownloadImages(T item) where T : IThumbnails { - if (item == null) - throw new ArgumentNullException(nameof(item)); - string name = item is IResource res ? res.Slug : "???"; - await _DownloadImage(item.Poster, _GetBaseImagePath(item, "poster"), $"The poster of {name}"); - await _DownloadImage(item.Thumbnail, _GetBaseImagePath(item, "thumbnail"), $"The poster of {name}"); - await _DownloadImage(item.Logo, _GetBaseImagePath(item, "logo"), $"The poster of {name}"); + + string posterPath = $"{_GetBaseImagePath(item, "poster")}.{ImageQuality.High.ToString().ToLowerInvariant()}.webp"; + bool duplicated = false; + TaskCompletionSource? sync = null; + try + { + lock (_downloading) + { + if (File.Exists(posterPath) || _downloading.ContainsKey(posterPath)) + { + duplicated = true; + sync = _downloading.GetValueOrDefault(posterPath); + } + else + { + sync = new(); + _downloading.Add(posterPath, sync); + } + } + if (duplicated) + { + object? dup = sync != null + ? await sync.Task + : null; + throw new DuplicatedItemException(dup); + } + + await _DownloadImage(item.Poster, _GetBaseImagePath(item, "poster"), $"The poster of {name}"); + await _DownloadImage(item.Thumbnail, _GetBaseImagePath(item, "thumbnail"), $"The poster of {name}"); + await _DownloadImage(item.Logo, _GetBaseImagePath(item, "logo"), $"The poster of {name}"); + } + finally + { + if (!duplicated) + { + lock (_downloading) + { + _downloading.Remove(posterPath); + sync!.SetResult(item); + } + } + } } private static string _GetBaseImagePath(T item, string image) diff --git a/scanner/scanner/scanner.py b/scanner/scanner/scanner.py index ad7432ae..c1823ac1 100644 --- a/scanner/scanner/scanner.py +++ b/scanner/scanner/scanner.py @@ -6,7 +6,7 @@ import re from aiohttp import ClientSession from pathlib import Path from guessit import guessit -from typing import List, Literal +from typing import List, Literal, Any from providers.provider import Provider from providers.types.collection import Collection from providers.types.episode import Episode, PartialShow @@ -186,7 +186,7 @@ class Scanner: ) return await self.post("seasons", data=season.to_kyoo()) - async def post(self, path: str, *, data: object) -> str: + async def post(self, path: str, *, data: dict[str, Any]) -> str: logging.debug( "Sending %s: %s", path, @@ -206,19 +206,32 @@ class Scanner: logging.error(f"Request error: {await r.text()}") r.raise_for_status() ret = await r.json() + + if r.status == 409 and ( + (path == "shows" and ret["startAir"][:4] != str(data["start_air"].year)) + or ( + path == "movies" and ret["airDate"][:4] != str(data["air_date"].year) + ) + ): + logging.info( + f"Found a {path} with the same slug ({ret['slug']}) and a different date, using the date as part of the slug" + ) + year = (data["start_air"] if path == "movie" else data["air_date"]).year + data["slug"] = f"{ret['slug']}-{year}" + return await self.post(path, data=data) return ret["id"] async def delete(self, path: str): logging.info("Deleting %s", path) async with self._client.delete( - f"{self._url}/movies?path={path}", headers={"X-API-Key": self._api_key} + f"{self._url}/movies?filter=path eq \"{path}\"", headers={"X-API-Key": self._api_key} ) as r: if not r.ok: logging.error(f"Request error: {await r.text()}") r.raise_for_status() async with self._client.delete( - f"{self._url}/episodes?path={path}", headers={"X-API-Key": self._api_key} + f"{self._url}/episodes?filter=path eq \"{path}\"", headers={"X-API-Key": self._api_key} ) as r: if not r.ok: logging.error(f"Request error: {await r.text()}")