Files
importarr/importarr/importer.py
T

64 lines
2.0 KiB
Python

from __future__ import annotations
import os
import shutil
from dataclasses import dataclass
from pathlib import Path
@dataclass
class ImportResult:
source: Path
target: Path
bytes: int
class Importer:
def __init__(self, movies_root: Path, tv_root: Path):
self.movies_root = movies_root
self.tv_root = tv_root
def target_for(self, source: Path) -> Path:
target_root = self.tv_root if _looks_like_tv(source) else self.movies_root
return _unique_path(target_root / source.name)
def import_file(self, source: Path) -> ImportResult:
target = self.target_for(source)
target.parent.mkdir(parents=True, exist_ok=True)
partial = target.with_name(target.name + ".partial")
with source.open("rb") as src, partial.open("wb") as dst:
shutil.copyfileobj(src, dst, length=1024 * 1024)
dst.flush()
os.fsync(dst.fileno())
if partial.stat().st_size != source.stat().st_size:
raise IOError("partial copy size mismatch")
partial.rename(target)
source.unlink()
_remove_empty_parents(source.parent)
return ImportResult(source=source, target=target, bytes=target.stat().st_size)
def _looks_like_tv(path: Path) -> bool:
text = str(path).lower()
return any(marker in text for marker in ("s01", "season", "episode"))
def _unique_path(path: Path) -> Path:
if not path.exists() and not path.with_name(path.name + ".partial").exists():
return path
stem, suffix = path.stem, path.suffix
for index in range(1, 10000):
candidate = path.with_name(f"{stem} ({index}){suffix}")
if not candidate.exists() and not candidate.with_name(candidate.name + ".partial").exists():
return candidate
raise RuntimeError(f"could not choose unique target for {path}")
def _remove_empty_parents(path: Path) -> None:
while True:
try:
path.rmdir()
except OSError:
return
path = path.parent