mirror of
https://github.com/zoriya/Kyoo.git
synced 2025-05-24 02:02:36 -04:00
Delete files via the scanner/monitor. Add an ignore folder
This commit is contained in:
parent
f58597379b
commit
8e9cd2d2f3
@ -3,6 +3,9 @@ LIBRARY_ROOT=./video
|
||||
CACHE_ROOT=/tmp/kyoo_cache
|
||||
LIBRARY_LANGUAGES=en
|
||||
|
||||
# A pattern (regex) to ignore video files.
|
||||
LIBRARY_IGNORE_PATTERN=.*/[dD]ownloads?/.*
|
||||
|
||||
# The following two values should be set to a random sequence of characters.
|
||||
# You MUST change thoses when installing kyoo (for security)
|
||||
AUTHENTICATION_SECRET=4c@mraGB!KRfF@kpS8739y9FcHemKxBsqqxLbdR?
|
||||
|
@ -44,6 +44,8 @@ namespace Kyoo.Core.Controllers
|
||||
/// </summary>
|
||||
private readonly IProviderRepository _providers;
|
||||
|
||||
private readonly IShowRepository _shows;
|
||||
|
||||
/// <inheritdoc />
|
||||
// Use absolute numbers by default and fallback to season/episodes if it does not exists.
|
||||
protected override Sort<Episode> DefaultSort => new Sort<Episode>.Conglomerate(
|
||||
@ -65,6 +67,7 @@ namespace Kyoo.Core.Controllers
|
||||
{
|
||||
_database = database;
|
||||
_providers = providers;
|
||||
_shows = shows;
|
||||
|
||||
// Edit episode slugs when the show's slug changes.
|
||||
shows.OnEdited += (show) =>
|
||||
@ -201,10 +204,13 @@ namespace Kyoo.Core.Controllers
|
||||
if (obj == null)
|
||||
throw new ArgumentNullException(nameof(obj));
|
||||
|
||||
int epCount = await _database.Episodes.Where(x => x.ShowID == obj.ShowID).Take(2).CountAsync();
|
||||
_database.Entry(obj).State = EntityState.Deleted;
|
||||
obj.ExternalIDs.ForEach(x => _database.Entry(x).State = EntityState.Deleted);
|
||||
await _database.SaveChangesAsync();
|
||||
await base.Delete(obj);
|
||||
if (epCount == 1)
|
||||
await _shows.Delete(obj.ShowID);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -30,8 +30,9 @@ async def main():
|
||||
logging.basicConfig(level=logging.INFO)
|
||||
if len(sys.argv) > 1 and sys.argv[1] == "-vv":
|
||||
logging.basicConfig(level=logging.DEBUG)
|
||||
logging.getLogger('watchfiles').setLevel(logging.WARNING)
|
||||
|
||||
jsons.set_serializer(lambda x, **_: format_date(x), Optional[date | int])
|
||||
jsons.set_serializer(lambda x, **_: format_date(x), Optional[date | int]) # type: ignore
|
||||
async with ClientSession(
|
||||
json_serialize=lambda *args, **kwargs: jsons.dumps(
|
||||
*args, key_transformer=jsons.KEY_TRANSFORMER_CAMELCASE, **kwargs
|
||||
|
@ -10,10 +10,13 @@ async def monitor(path: str, scanner: Scanner):
|
||||
try:
|
||||
if event == Change.added:
|
||||
await scanner.identify(file)
|
||||
elif event == Change.deleted:
|
||||
await scanner.delete(file);
|
||||
elif event == Change.modified:
|
||||
pass
|
||||
else:
|
||||
print(f"Change {event} occured for file {file}")
|
||||
except ProviderError as e:
|
||||
logging.error(str(e))
|
||||
except Exception as e:
|
||||
logging.exception("Unhandled error", exc_info=e)
|
||||
print("end", flush=True)
|
||||
|
@ -2,6 +2,7 @@ import os
|
||||
import asyncio
|
||||
import logging
|
||||
import jsons
|
||||
import re
|
||||
from aiohttp import ClientSession
|
||||
from pathlib import Path
|
||||
from guessit import guessit
|
||||
@ -19,6 +20,11 @@ class Scanner:
|
||||
self._client = client
|
||||
self._api_key = api_key
|
||||
self._url = os.environ.get("KYOO_URL", "http://back:5000")
|
||||
try:
|
||||
self._ignore_pattern = re.compile(os.environ.get("LIBRARY_IGNORE_PATTERN", ""))
|
||||
except Exception as e:
|
||||
self._ignore_pattern = re.compile("")
|
||||
logging.error(f"Invalid ignore pattern. Ignoring. Error: {e}")
|
||||
self.provider = Provider.get_all(client)[0]
|
||||
self.cache = {"shows": {}, "seasons": {}}
|
||||
self.languages = languages
|
||||
@ -26,13 +32,20 @@ class Scanner:
|
||||
async def scan(self, path: str):
|
||||
logging.info("Starting the scan. It can take some times...")
|
||||
self.registered = await self.get_registered_paths()
|
||||
videos = (str(p) for p in Path(path).rglob("*") if p.is_file())
|
||||
videos = [str(p) for p in Path(path).rglob("*") if p.is_file()]
|
||||
deleted = [x for x in self.registered if x not in videos]
|
||||
|
||||
if len(deleted) != len(self.registered):
|
||||
for x in deleted:
|
||||
await self.delete(x)
|
||||
else:
|
||||
logging.warning("All video files are unavailable. Check your disks.")
|
||||
|
||||
# We batch videos by 20 because too mutch at once kinda DDOS everything.
|
||||
for group in batch(videos, 20):
|
||||
logging.info("Batch finished. Starting a new one")
|
||||
for group in batch(iter(videos), 20):
|
||||
await asyncio.gather(*map(self.identify, group))
|
||||
|
||||
async def get_registered_paths(self) -> List[Path]:
|
||||
async def get_registered_paths(self) -> List[str]:
|
||||
# TODO: Once movies are separated from the api, a new endpoint should be created to check for paths.
|
||||
async with self._client.get(
|
||||
f"{self._url}/episodes",
|
||||
@ -45,7 +58,7 @@ class Scanner:
|
||||
|
||||
@log_errors
|
||||
async def identify(self, path: str):
|
||||
if path in self.registered:
|
||||
if path in self.registered or self._ignore_pattern.match(path):
|
||||
return
|
||||
|
||||
raw = guessit(path, "--episode-prefer-number")
|
||||
@ -139,3 +152,13 @@ class Scanner:
|
||||
r.raise_for_status()
|
||||
ret = await r.json()
|
||||
return ret["id"]
|
||||
|
||||
async def delete(self, path: str):
|
||||
logging.info("Deleting %s", path)
|
||||
# TODO: Adapt this for movies as well when they are split
|
||||
async with self._client.delete(
|
||||
f"{self._url}/episodes?path={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()
|
||||
|
Loading…
x
Reference in New Issue
Block a user