diff --git a/.gitignore b/.gitignore index 42230edf..f7230e46 100644 --- a/.gitignore +++ b/.gitignore @@ -142,4 +142,6 @@ docker/*.whl docker/root/*.whl docker/root/defaults/examples -.local/ \ No newline at end of file +.local/ + +ffmpeg.exe \ No newline at end of file diff --git a/src/ytdl_sub/cli/main.py b/src/ytdl_sub/cli/main.py index f38dbe93..f88f74e8 100644 --- a/src/ytdl_sub/cli/main.py +++ b/src/ytdl_sub/cli/main.py @@ -1,11 +1,6 @@ import argparse -import errno -import fcntl import gc -import os import sys -from contextlib import contextmanager -from pathlib import Path from typing import List from typing import Tuple @@ -13,8 +8,8 @@ from ytdl_sub.cli.download_args_parser import DownloadArgsParser from ytdl_sub.cli.main_args_parser import parser from ytdl_sub.config.config_file import ConfigFile from ytdl_sub.subscriptions.subscription import Subscription -from ytdl_sub.utils.exceptions import ValidationException from ytdl_sub.utils.file_handler import FileHandlerTransactionLog +from ytdl_sub.utils.file_lock import working_directory_lock from ytdl_sub.utils.logger import Logger logger = Logger.get() @@ -119,45 +114,6 @@ def _view_url_from_cli( return subscription, subscription.download(dry_run=True) -@contextmanager -def _working_directory_lock(config: ConfigFile): - """ - Create and try to lock the file /tmp/working_directory_name - - Raises - ------ - ValidationException - Lock is acquired from another process running ytdl-sub in the same working directory - OSError - Other lock error occurred - """ - working_directory_path = Path(os.getcwd()) / config.config_options.working_directory - lock_file_path = ( - Path(os.getcwd()) - / config.config_options.lock_directory - / str(working_directory_path).replace("/", "_") - ) - - lock_file = open(lock_file_path, "w", encoding="utf-8") - - try: - fcntl.lockf(lock_file, fcntl.LOCK_EX | fcntl.LOCK_NB) - except OSError as exc: - if exc.errno in (errno.EACCES, errno.EAGAIN): - raise ValidationException( - "Cannot run two instances of ytdl-sub " - "with the same working directory at the same time" - ) from exc - lock_file.close() - raise exc - - try: - yield - finally: - fcntl.flock(lock_file, fcntl.LOCK_UN) - lock_file.close() - - def main() -> List[Tuple[Subscription, FileHandlerTransactionLog]]: """ Entrypoint for ytdl-sub, without the error handling @@ -172,7 +128,7 @@ def main() -> List[Tuple[Subscription, FileHandlerTransactionLog]]: config: ConfigFile = ConfigFile.from_file_path(args.config).initialize() transaction_logs: List[Tuple[Subscription, FileHandlerTransactionLog]] = [] - with _working_directory_lock(config=config): + with working_directory_lock(config=config): if args.subparser == "sub": transaction_logs = _download_subscriptions_from_yaml_files(config=config, args=args) diff --git a/src/ytdl_sub/utils/ffmpeg.py b/src/ytdl_sub/utils/ffmpeg.py index 2d63de8e..94017e95 100644 --- a/src/ytdl_sub/utils/ffmpeg.py +++ b/src/ytdl_sub/utils/ffmpeg.py @@ -1,4 +1,5 @@ import subprocess +import sys import tempfile from typing import Dict from typing import List @@ -26,7 +27,10 @@ class FFMPEG: @classmethod def _ensure_installed(cls): try: - subprocess.check_output(["which", "ffmpeg"]) + if sys.platform.startswith("win32"): + subprocess.check_output([".\\ffmpeg", "-version"]) + else: + subprocess.check_output(["which", "ffmpeg"]) except subprocess.CalledProcessError as subprocess_error: raise ValidationException( "Trying to use a feature which requires ffmpeg, but it cannot be found" diff --git a/src/ytdl_sub/utils/file_lock.py b/src/ytdl_sub/utils/file_lock.py new file mode 100644 index 00000000..0b18ac41 --- /dev/null +++ b/src/ytdl_sub/utils/file_lock.py @@ -0,0 +1,67 @@ +import errno +import os +import sys +from contextlib import contextmanager +from pathlib import Path + +from ytdl_sub.config.config_file import ConfigFile +from ytdl_sub.utils.exceptions import ValidationException +from ytdl_sub.utils.logger import Logger + +logger = Logger.get() + + +@contextmanager +def _working_directory_lock_unix(config: ConfigFile): + """ + Create and try to lock the file /tmp/working_directory_name + + Raises + ------ + ValidationException + Lock is acquired from another process running ytdl-sub in the same working directory + OSError + Other lock error occurred + """ + # pylint: disable=import-outside-toplevel + import fcntl + + working_directory_path = Path(os.getcwd()) / config.config_options.working_directory + lock_file_path = ( + Path(os.getcwd()) + / config.config_options.lock_directory + / str(working_directory_path).replace("/", "_") + ) + + lock_file = open(lock_file_path, "w", encoding="utf-8") + + try: + fcntl.lockf(lock_file, fcntl.LOCK_EX | fcntl.LOCK_NB) + except OSError as exc: + if exc.errno in (errno.EACCES, errno.EAGAIN): + raise ValidationException( + "Cannot run two instances of ytdl-sub " + "with the same working directory at the same time" + ) from exc + lock_file.close() + raise exc + + try: + yield + finally: + fcntl.flock(lock_file, fcntl.LOCK_UN) + lock_file.close() + + +@contextmanager +def working_directory_lock(config: ConfigFile): + if sys.platform.startswith("win32"): + logger.info( + "Working directory lock not supported in Windows. " + "Ensure only one instance of ytdl-sub runs at once using working directory %s", + config.config_options.working_directory, + ) + yield + else: + with _working_directory_lock_unix(config): + yield diff --git a/tests/resources.py b/tests/resources.py index c867bb71..911b330a 100644 --- a/tests/resources.py +++ b/tests/resources.py @@ -3,9 +3,9 @@ from pathlib import Path REGENERATE_FIXTURES: bool = False -RESOURCE_PATH = Path("tests/resources") -_FILE_FIXTURE_PATH = RESOURCE_PATH / "file_fixtures" +RESOURCE_PATH: Path = Path("tests") / "resources" +_FILE_FIXTURE_PATH: Path = RESOURCE_PATH / "file_fixtures" -def copy_file_fixture(fixture_name: str, output_file_path: str | Path) -> None: +def copy_file_fixture(fixture_name: str, output_file_path: Path) -> None: shutil.copy(_FILE_FIXTURE_PATH / fixture_name, output_file_path) diff --git a/tests/unit/cli/test_working_directory_lock.py b/tests/unit/cli/test_working_directory_lock.py index 31700458..98a72397 100644 --- a/tests/unit/cli/test_working_directory_lock.py +++ b/tests/unit/cli/test_working_directory_lock.py @@ -3,9 +3,9 @@ import time import pytest -from ytdl_sub.cli.main import _working_directory_lock from ytdl_sub.config.config_file import ConfigFile from ytdl_sub.utils.exceptions import ValidationException +from ytdl_sub.utils.file_lock import working_directory_lock @pytest.fixture @@ -18,15 +18,15 @@ def config() -> ConfigFile: def test_working_directory_lock(config: ConfigFile): new_pid = os.fork() if new_pid == 0: # is child - with _working_directory_lock(config=config): + with working_directory_lock(config=config): time.sleep(3) return time.sleep(1) with pytest.raises(ValidationException, match="Cannot run two instances of ytdl-sub"): - with _working_directory_lock(config=config): + with working_directory_lock(config=config): time.sleep(1) time.sleep(3) - with _working_directory_lock(config=config): + with working_directory_lock(config=config): pass