diff --git a/app/library/DownloadQueue.py b/app/library/DownloadQueue.py index 5652f549..bcfbfebd 100644 --- a/app/library/DownloadQueue.py +++ b/app/library/DownloadQueue.py @@ -24,6 +24,7 @@ from .Presets import Presets from .Scheduler import Scheduler from .Singleton import Singleton from .Utils import ( + archive_add, arg_converter, calc_download_path, dt_delta, @@ -675,8 +676,10 @@ class DownloadQueue(metaclass=Singleton): LOG.warning(f"Using external downloader '{yt_conf.get('external_downloader')}' for '{item.url}'.") item.extras.update({"external_downloader": True}) + archive_id = item.get_archive_id() + if item.is_archived(): - if archive_id := item.get_archive_id(): + if archive_id: store_type, _ = self.get_item(archive_id=archive_id) if not store_type: dlInfo = Download( @@ -750,7 +753,55 @@ class DownloadQueue(metaclass=Singleton): if not item.requeued and (condition := Conditions.get_instance().match(info=entry)): already.pop() - LOG.info(f"Matched '{condition.name}' for '{item.url}' Adding '{condition.cli}' to request.") + + item_title = entry.get("title") or entry.get("id") or item.url + message = f"Condition '{condition.name}' matched for '{item_title}'." + + if condition.cli: + message += f" Re-queuing with '{condition.cli}'." + + LOG.info(message) + + if condition.extras.get("ignore_download", False): + extra_msg: str = "" + if yt_conf.get("download_archive") and not condition.extras.get("no_archive", False): + archive_add(yt_conf.get("download_archive"), [archive_id]) + extra_msg = f" and added to archive '{yt_conf.get('download_archive')}'" + + log_message = f"Ignoring download of '{item_title}' as per condition '{condition.name}'{extra_msg}." + + store_type, _ = self.get_item(archive_id=archive_id) + if not store_type: + dlInfo = Download( + info=ItemDTO( + id=entry.get("id"), + title=item_title, + url=item.url, + preset=item.preset, + folder=item.folder, + status="skip", + cookies=item.cookies, + template=item.template, + msg=log_message, + extras=item.extras, + ) + ) + self.done.put(dlInfo) + + LOG.info(log_message) + await asyncio.gather( + *[ + self._notify.emit(Events.LOG_INFO, data={}, title="Ignored Download", message=log_message), + self._notify.emit( + Events.ITEM_MOVED, + data={"to": "history", "preset": dlInfo.info.preset, "item": dlInfo.info}, + title="Download History Update", + message=f"Download history updated with '{item.url}'.", + ), + ] + ) + return {"status": "ok"} + return await self.add(item=item.new_with(requeued=True, cli=condition.cli), already=already) _status, _msg = ytdlp_reject(entry=entry, yt_params=yt_conf) diff --git a/app/library/conditions.py b/app/library/conditions.py index 100b9a14..a86267e8 100644 --- a/app/library/conditions.py +++ b/app/library/conditions.py @@ -210,15 +210,12 @@ class Conditions(metaclass=Singleton): msg = f"Invalid filter. '{e!s}'." raise ValueError(msg) from e - if not item.get("cli"): - msg = "No command options for yt-dlp were found." - raise ValueError(msg) - - try: - arg_converter(args=item.get("cli")) - except Exception as e: - msg = f"Invalid command options for yt-dlp. '{e!s}'." - raise ValueError(msg) from e + if item.get("cli"): + try: + arg_converter(args=item.get("cli")) + except Exception as e: + msg = f"Invalid command options for yt-dlp. '{e!s}'." + raise ValueError(msg) from e if not isinstance(item.get("extras"), dict): msg = "Extras must be a dictionary." diff --git a/ui/app/components/ConditionForm.vue b/ui/app/components/ConditionForm.vue index 4059c84e..12f726c7 100644 --- a/ui/app/components/ConditionForm.vue +++ b/ui/app/components/ConditionForm.vue @@ -304,8 +304,7 @@ if (!form.extras) { watch(() => config.ytdlp_options, newOptions => ytDlpOpt.value = newOptions .filter(opt => !opt.ignored) - .flatMap(opt => opt.flags - .filter(flag => flag.startsWith('--')) + .flatMap(opt => opt.flags.filter(flag => flag.startsWith('--')) .map(flag => ({ value: flag, description: opt.description || '' }))), { immediate: true } ) @@ -313,7 +312,7 @@ watch(() => config.ytdlp_options, newOptions => ytDlpOpt.value = newOptions watch(() => form.filter, () => test_data.value.changed = true) const checkInfo = async (): Promise => { - const required: (keyof ConditionItem)[] = ['name', 'filter', 'cli'] + const required: (keyof ConditionItem)[] = ['name', 'filter'] for (const key of required) { if (!form[key]) { @@ -457,27 +456,20 @@ const logic_test = computed(() => { } }) -// Extras management functions -const validateKey = (key: string): boolean => { - // Key must be lowercase with underscores only - return /^[a-z][a-z0-9_]*$/.test(key) -} +const validateKey = (key: string): boolean => /^[a-z][a-z0-9_]*$/.test(key) const parseValue = (value: string): string | number | boolean => { - // Try to parse as number if (!isNaN(Number(value)) && !isNaN(parseFloat(value))) { return Number(value) } - // Try to parse as boolean - if (value.toLowerCase() === 'true') { + if ('true' === value.toLowerCase()) { return true } - if (value.toLowerCase() === 'false') { + if ('false' === value.toLowerCase()) { return false } - // Return as string return value } @@ -491,7 +483,7 @@ const addExtra = (): void => { } if (!validateKey(key)) { - toast.error('Key must be lowercase and contain only letters, numbers, and underscores (e.g., custom_field).') + toast.error('Key must be lower_case.') return } @@ -519,18 +511,17 @@ const updateExtraKey = (event: Event, oldKey: string): void => { if (!validateKey(newKey)) { toast.error('Key must be lowercase and contain only letters, numbers, and underscores.') - target.value = oldKey // Reset to old value + target.value = oldKey return } if (newKey !== oldKey) { if (form.extras[newKey]) { toast.error(`Key '${newKey}' already exists.`) - target.value = oldKey // Reset to old value + target.value = oldKey return } - // Move the value to the new key const value = form.extras[oldKey] delete form.extras[oldKey] form.extras[newKey] = value @@ -540,11 +531,6 @@ const updateExtraKey = (event: Event, oldKey: string): void => { const updateExtraValue = (key: string, event: Event): void => { const target = event.target as HTMLInputElement const value = target.value.trim() - - if (value) { - form.extras[key] = parseValue(value) - } else { - form.extras[key] = '' - } + form.extras[key] = value ? parseValue(value) : '' } diff --git a/ui/app/pages/conditions.vue b/ui/app/pages/conditions.vue index 1e1af7dc..3e236c9b 100644 --- a/ui/app/pages/conditions.vue +++ b/ui/app/pages/conditions.vue @@ -64,6 +64,14 @@ {{ cond.cli }}

+

+ + Extras: + + {{ key }}: {{ value }} + + +

@@ -278,7 +286,7 @@ const exportItem = (cond: ConditionItem): void => { const userData: ImportedConditionItem = { ...Object.fromEntries(Object.entries(clone).filter(([_, v]) => !!v)), _type: 'condition', - _version: '1.0', + _version: '1.1', } as ImportedConditionItem copyText(encode(userData))