This commit is contained in:
Jesse Bannon 2023-03-20 23:06:06 -07:00
parent 189b54964b
commit ff3707adb9
7 changed files with 107 additions and 5 deletions

View file

@ -14,6 +14,7 @@ from ytdl_sub.cli.download_args_parser import DownloadArgsParser
from ytdl_sub.cli.main_args_parser import parser from ytdl_sub.cli.main_args_parser import parser
from ytdl_sub.config.config_file import ConfigFile from ytdl_sub.config.config_file import ConfigFile
from ytdl_sub.subscriptions.subscription import Subscription from ytdl_sub.subscriptions.subscription import Subscription
from ytdl_sub.utils.exceptions import ExperimentalFeatureNotEnabled
from ytdl_sub.utils.exceptions import ValidationException from ytdl_sub.utils.exceptions import ValidationException
from ytdl_sub.utils.file_handler import FileHandler from ytdl_sub.utils.file_handler import FileHandler
from ytdl_sub.utils.file_handler import FileHandlerTransactionLog from ytdl_sub.utils.file_handler import FileHandlerTransactionLog
@ -62,7 +63,7 @@ def _maybe_write_subscription_log_file(
def _download_subscriptions_from_yaml_files( def _download_subscriptions_from_yaml_files(
config: ConfigFile, subscription_paths: List[str], dry_run: bool config: ConfigFile, subscription_paths: List[str], update_with_info_json: bool, dry_run: bool
) -> List[Tuple[Subscription, FileHandlerTransactionLog]]: ) -> List[Tuple[Subscription, FileHandlerTransactionLog]]:
""" """
Downloads all subscriptions from one or many subscription yaml files. Downloads all subscriptions from one or many subscription yaml files.
@ -73,6 +74,8 @@ def _download_subscriptions_from_yaml_files(
Configuration file Configuration file
subscription_paths subscription_paths
Path to subscription files to download Path to subscription files to download
update_with_info_json
Whether to actually download or update using existing info json
dry_run dry_run
Whether to dry run or not Whether to dry run or not
@ -101,6 +104,9 @@ def _download_subscriptions_from_yaml_files(
logger.debug("Subscription full yaml:\n%s", subscription.as_yaml()) logger.debug("Subscription full yaml:\n%s", subscription.as_yaml())
try: try:
if update_with_info_json:
transaction_log = subscription.update_with_info_json(dry_run=dry_run)
else:
transaction_log = subscription.download(dry_run=dry_run) transaction_log = subscription.download(dry_run=dry_run)
except Exception as exc: # pylint: disable=broad-except except Exception as exc: # pylint: disable=broad-except
_maybe_write_subscription_log_file( _maybe_write_subscription_log_file(
@ -305,9 +311,21 @@ def main() -> List[Tuple[Subscription, FileHandlerTransactionLog]]:
with working_directory_lock(config=config): with working_directory_lock(config=config):
if args.subparser == "sub": if args.subparser == "sub":
if (
args.update_with_info_json
and not config.config_options.experimental.enable_update_with_info_json
):
raise ExperimentalFeatureNotEnabled(
"--update-with-info-json requires setting "
"configuration.experimental.update_with_info_json to True. This feature is ",
"still being tested and has the ability to destroy files. Ensure you have a ",
"full backup before usage. You have been warned!",
)
transaction_logs = _download_subscriptions_from_yaml_files( transaction_logs = _download_subscriptions_from_yaml_files(
config=config, config=config,
subscription_paths=args.subscription_paths, subscription_paths=args.subscription_paths,
update_with_info_json=args.update_with_info_json,
dry_run=args.dry_run, dry_run=args.dry_run,
) )

View file

@ -135,6 +135,13 @@ _add_shared_arguments(parser, suppress_defaults=False)
subparsers = parser.add_subparsers(dest="subparser") subparsers = parser.add_subparsers(dest="subparser")
################################################################################################### ###################################################################################################
# SUBSCRIPTION PARSER # SUBSCRIPTION PARSER
class SubArguments:
UPDATE_WITH_INFO_JSON = CLIArgument(
short="-u",
long="--update-with-info-json",
)
subscription_parser = subparsers.add_parser("sub") subscription_parser = subparsers.add_parser("sub")
_add_shared_arguments(subscription_parser, suppress_defaults=True) _add_shared_arguments(subscription_parser, suppress_defaults=True)
subscription_parser.add_argument( subscription_parser.add_argument(
@ -144,6 +151,14 @@ subscription_parser.add_argument(
help="path to subscription files, uses subscriptions.yaml if not provided", help="path to subscription files, uses subscriptions.yaml if not provided",
default=["subscriptions.yaml"], default=["subscriptions.yaml"],
) )
subscription_parser.add_argument(
SubArguments.UPDATE_WITH_INFO_JSON.short,
SubArguments.UPDATE_WITH_INFO_JSON.long,
action="store_true",
help="update all subscriptions with the current config using info.json files",
default=False,
)
################################################################################################### ###################################################################################################
# DOWNLOAD PARSER # DOWNLOAD PARSER
download_parser = subparsers.add_parser("dl") download_parser = subparsers.add_parser("dl")

View file

@ -24,6 +24,27 @@ else:
_DEFAULT_FFPROBE_PATH = "/usr/bin/ffprobe" _DEFAULT_FFPROBE_PATH = "/usr/bin/ffprobe"
class ExperimentalValidator(StrictDictValidator):
_optional_keys = {"enable_update_with_info_json"}
_allow_extra_keys = True
def __init__(self, name: str, value: Any):
super().__init__(name, value)
self._enable_update_with_info_json = self._validate_key(
key="enable_update_with_info_json", validator=BoolValidator, default=False
)
@property
def enable_update_with_info_json(self) -> bool:
"""
Enables modifying subscription files using info.json files using the argument
``--update-with-info-json``. This feature is still being tested and has the ability to
destroy files. Ensure you have a full backup before usage. You have been warned!
"""
return self._enable_update_with_info_json.value
class PersistLogsValidator(StrictDictValidator): class PersistLogsValidator(StrictDictValidator):
_required_keys = {"logs_directory"} _required_keys = {"logs_directory"}
_optional_keys = {"keep_logs_after", "keep_successful_logs"} _optional_keys = {"keep_logs_after", "keep_successful_logs"}
@ -79,6 +100,7 @@ class PersistLogsValidator(StrictDictValidator):
return self._keep_successful_logs.value return self._keep_successful_logs.value
# pylint: disable=too-many-instance-attributes
class ConfigOptions(StrictDictValidator): class ConfigOptions(StrictDictValidator):
_required_keys = {"working_directory"} _required_keys = {"working_directory"}
_optional_keys = { _optional_keys = {
@ -88,6 +110,7 @@ class ConfigOptions(StrictDictValidator):
"lock_directory", "lock_directory",
"ffmpeg_path", "ffmpeg_path",
"ffprobe_path", "ffprobe_path",
"experimental",
} }
def __init__(self, name: str, value: Any): def __init__(self, name: str, value: Any):
@ -114,6 +137,9 @@ class ConfigOptions(StrictDictValidator):
self._ffprobe_path = self._validate_key( self._ffprobe_path = self._validate_key(
key="ffprobe_path", validator=FFprobeFileValidator, default=_DEFAULT_FFPROBE_PATH key="ffprobe_path", validator=FFprobeFileValidator, default=_DEFAULT_FFPROBE_PATH
) )
self._experimental = self._validate_key(
key="experimental", validator=ExperimentalValidator, default={}
)
@property @property
def working_directory(self) -> str: def working_directory(self) -> str:
@ -167,6 +193,13 @@ class ConfigOptions(StrictDictValidator):
""" """
return self._persist_logs return self._persist_logs
@property
def experimental(self) -> ExperimentalValidator:
"""
Experimental validator. readthedocs in the validator itself!
"""
return self._experimental
@property @property
def lock_directory(self) -> str: def lock_directory(self) -> str:
""" """

View file

@ -88,7 +88,7 @@ class InfoJsonDownloader(BaseDownloader[InfoJsonDownloaderOptions]):
file_names_mtime: Dict[str, Dict[str, float]] = defaultdict(dict) file_names_mtime: Dict[str, Dict[str, float]] = defaultdict(dict)
entries: List[Entry] = [] entries: List[Entry] = []
for download_mapping in self._enhanced_download_archive.mapping._entry_mappings.values(): for download_mapping in self._enhanced_download_archive.mapping.entry_mappings.values():
entry = self._get_entry_from_download_mapping(download_mapping) entry = self._get_entry_from_download_mapping(download_mapping)
entries.append(entry) entries.append(entry)
@ -117,7 +117,7 @@ class InfoJsonDownloader(BaseDownloader[InfoJsonDownloaderOptions]):
the working directory the working directory
""" """
# Use original mapping since the live mapping gets wiped # Use original mapping since the live mapping gets wiped
entry_file_names = self._original_mapping._entry_mappings[entry.uid].file_names entry_file_names = self._original_mapping.entry_mappings[entry.uid].file_names
for file_name in entry_file_names: for file_name in entry_file_names:
ext = get_file_extension(file_name) ext = get_file_extension(file_name)

View file

@ -36,3 +36,7 @@ class InvalidDlArguments(ValidationException):
class FileNotDownloadedException(ValueError): class FileNotDownloadedException(ValueError):
"""ytdlp failed to download something""" """ytdlp failed to download something"""
class ExperimentalFeatureNotEnabled(ValidationException):
"""Feature is not enabled for usage"""

View file

@ -175,6 +175,15 @@ class DownloadMappings:
download_mappings._entry_mappings = entry_mappings_json download_mappings._entry_mappings = entry_mappings_json
return download_mappings return download_mappings
@property
def entry_mappings(self) -> Dict[str, DownloadMapping]:
"""
Returns
-------
Mapping of entries to files
"""
return self._entry_mappings
@property @property
def entry_ids(self) -> List[str]: def entry_ids(self) -> List[str]:
""" """

View file

@ -23,6 +23,7 @@ from ytdl_sub.cli.main import logger as main_logger
from ytdl_sub.cli.main import main from ytdl_sub.cli.main import main
from ytdl_sub.config.config_file import ConfigFile from ytdl_sub.config.config_file import ConfigFile
from ytdl_sub.subscriptions.subscription import Subscription from ytdl_sub.subscriptions.subscription import Subscription
from ytdl_sub.utils.exceptions import ExperimentalFeatureNotEnabled
from ytdl_sub.utils.file_handler import FileHandler from ytdl_sub.utils.file_handler import FileHandler
from ytdl_sub.utils.file_handler import FileHandlerTransactionLog from ytdl_sub.utils.file_handler import FileHandlerTransactionLog
from ytdl_sub.utils.file_handler import FileMetadata from ytdl_sub.utils.file_handler import FileMetadata
@ -127,7 +128,10 @@ def test_subscription_logs_write_to_file(
): ):
try: try:
_download_subscriptions_from_yaml_files( _download_subscriptions_from_yaml_files(
config=config, subscription_paths=subscription_paths, dry_run=dry_run config=config,
subscription_paths=subscription_paths,
update_with_info_json=False,
dry_run=dry_run,
) )
except ValueError: except ValueError:
assert not mock_success_output assert not mock_success_output
@ -275,3 +279,22 @@ def test_output_summary():
_ = _output_summary(transaction_logs=mock_subscriptions) _ = _output_summary(transaction_logs=mock_subscriptions)
assert True # Test used for manual inspection - too hard to test ansi color codes assert True # Test used for manual inspection - too hard to test ansi color codes
def test_update_with_info_json_requires_experimental_flag(
music_video_config_path: Path,
music_video_subscription_path: Path,
) -> None:
with patch.object(
sys,
"argv",
[
"ytdl-sub",
"--config",
str(music_video_config_path),
"sub",
str(music_video_subscription_path),
"--update-with-info-json",
],
), pytest.raises(ExperimentalFeatureNotEnabled):
_ = main()