Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Issue 144: Option to copy instead of move file #150

Open
wants to merge 10 commits into
base: main
Choose a base branch
from
2 changes: 1 addition & 1 deletion mnamer/providers.py
Original file line number Diff line number Diff line change
Expand Up @@ -276,7 +276,7 @@ def _search_id(
season=entry["airedSeason"],
series=series_data["data"]["seriesName"],
language=language,
synopsis=(entry["overview"] or None)
synopsis=(entry["overview"] or "")
.replace("\r\n", "")
.replace(" ", "")
.strip(),
Expand Down
12 changes: 11 additions & 1 deletion mnamer/setting_store.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
from mnamer.exceptions import MnamerException
from mnamer.language import Language
from mnamer.setting_spec import SettingSpec
from mnamer.types import MediaType, ProviderType, SettingType
from mnamer.types import MediaType, ProviderType, SettingType, RelocateType
from mnamer.utils import crawl_out, json_loads, normalize_containers

__all__ = ["SettingStore"]
Expand Down Expand Up @@ -218,6 +218,16 @@ class SettingStore:
help="--episode-format: set episode renaming format specification",
)(),
)
relocation_strategy: Optional[Union[RelocateType, str]] = dataclasses.field(
default=RelocateType.DEFAULT.value,
metadata=SettingSpec(
dest="relocation_strategy",
choices=[ix.value for ix in RelocateType],
flags=["--relocation-operation"],
group=SettingType.PARAMETER,
help=f"--relocation-operation={'|'.join([ix.value for ix in RelocateType])}: when given, link, copy or move files. Default move.",
)(),
)

# directive attributes -----------------------------------------------------

Expand Down
25 changes: 21 additions & 4 deletions mnamer/target.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
from datetime import date
from os import path
from os import path, symlink, link
from pathlib import Path, PurePath
from shutil import move
from shutil import move, copy, copy2
from typing import Dict, List, Optional, Set, Union

from guessit import guessit
Expand All @@ -11,7 +11,7 @@
from mnamer.metadata import Metadata, MetadataEpisode, MetadataMovie
from mnamer.providers import Provider
from mnamer.setting_store import SettingStore
from mnamer.types import MediaType, ProviderType
from mnamer.types import MediaType, ProviderType, RelocateType
from mnamer.utils import (
crawl_in,
filename_replace,
Expand All @@ -37,6 +37,14 @@ class Target:
_parsed_metadata: Metadata
source: PurePath

_relocation_strategy = {
RelocateType.DEFAULT.value: move,
RelocateType.HARDLINK.value: link,
RelocateType.SYMBOLICLINK.value: symlink,
RelocateType.COPY2.value: copy2,
RelocateType.COPY.value: copy,
}

def __init__(self, file_path: Path, settings: SettingStore = None):
self._settings = settings or SettingStore()
self._has_moved: False
Expand Down Expand Up @@ -103,6 +111,12 @@ def destination(self) -> PurePath:
)
file_path = self._make_path(file_path)
dir_tail, filename = path.split(file_path)

# Required to sanitize paths that have been inserted into --episode-format
dir_tail = self._make_path(
*[str_sanitize(px) for px in self._make_path(dir_tail).parts]
)

filename = filename_replace(filename, self._settings.replace_after)
if self._settings.scene:
filename = str_scenify(filename)
Expand Down Expand Up @@ -244,6 +258,9 @@ def relocate(self) -> None:
destination_path = Path(self.destination).resolve()
destination_path.parent.mkdir(parents=True, exist_ok=True)
try:
move(self.source, destination_path)
relocate_function = self._relocation_strategy[
self._settings.relocation_strategy
]
relocate_function(self.source, destination_path)
except OSError: # pragma: no cover
raise MnamerException
8 changes: 8 additions & 0 deletions mnamer/types.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,14 @@ class ProviderType(Enum):
OMDB = "omdb"


class RelocateType(Enum):
DEFAULT = "move"
HARDLINK = "hardlink"
SYMBOLICLINK = "symlink"
COPY = "copy"
COPY2 = "copy-with-metadata"


class SettingType(Enum):
DIRECTIVE = "directive"
PARAMETER = "parameter"
Expand Down
67 changes: 67 additions & 0 deletions tests/e2e/test_moving.py → tests/e2e/test_relocation.py
Original file line number Diff line number Diff line change
@@ -1,12 +1,79 @@
from pathlib import Path

import pytest
from functools import partial

from mnamer.const import SUBTITLE_CONTAINERS

pytestmark = pytest.mark.e2e


@pytest.mark.usefixtures("setup_test_dir")
def test_relocation_operation_copy2(e2e_run, setup_test_files):
_run_relocation_operation(
partial(e2e_run, "--relocation-operation=copy-with-metadata"),
setup_test_files,
)


@pytest.mark.usefixtures("setup_test_dir")
def test_relocation_operation_copy(e2e_run, setup_test_files):
_run_relocation_operation(
partial(e2e_run, "--relocation-operation=copy"), setup_test_files
)


@pytest.mark.usefixtures("setup_test_dir")
def test_relocation_operation_symlink(e2e_run, setup_test_files):
_run_relocation_operation(
partial(e2e_run, "--relocation-operation=symlink"), setup_test_files
)


@pytest.mark.usefixtures("setup_test_dir")
def test_relocation_operation_hardlink(e2e_run, setup_test_files):
_run_relocation_operation(
partial(e2e_run, "--relocation-operation=hardlink"), setup_test_files
)


@pytest.mark.usefixtures("setup_test_dir")
def test_relocation_operation_move(e2e_run, setup_test_files):
_run_relocation_operation(
partial(e2e_run, "--relocation-operation=move"), setup_test_files
)


def _run_relocation_operation(e2e_run, setup_test_files):
setup_test_files(
"Quien a hierro mata [MicroHD 1080p][DTS 5.1-Castellano-AC3 5.1-Castellano+Subs][ES-EN].mkv"
)
result = e2e_run("--batch", "--media=movie", ".")
assert result.code == 0
assert "Quien a Hierro Mata (2019).mkv" in result.out
assert "1 out of 1 files processed successfully" in result.out


@pytest.mark.tvdb
@pytest.mark.usefixtures("setup_test_dir")
def test_sanitize_episode_format_path(e2e_run, setup_test_files):
setup_test_files(
"The.Great.Fire.In.Real.Time.S01E01.1080p.HEVC.x265-MeGusta.mkv"
)
result = e2e_run(
"--batch",
"--episode-api=tvdb",
"--episode-format='{series}/{series}'",
".",
)
print(result.out)
assert result.code == 0
assert (
"The Great Fire in Real Time/The Great Fire in Real Time" in result.out
)
assert "1 out of 1 files processed successfully" in result.out


@pytest.mark.usefixtures("setup_test_dir")
def test_complex_metadata(e2e_run, setup_test_files):
setup_test_files(
Expand Down