make it possible to ignore item download via conditions
This commit is contained in:
parent
0d6532df5d
commit
d0a11cbcd6
4 changed files with 77 additions and 35 deletions
|
|
@ -24,6 +24,7 @@ from .Presets import Presets
|
||||||
from .Scheduler import Scheduler
|
from .Scheduler import Scheduler
|
||||||
from .Singleton import Singleton
|
from .Singleton import Singleton
|
||||||
from .Utils import (
|
from .Utils import (
|
||||||
|
archive_add,
|
||||||
arg_converter,
|
arg_converter,
|
||||||
calc_download_path,
|
calc_download_path,
|
||||||
dt_delta,
|
dt_delta,
|
||||||
|
|
@ -675,8 +676,10 @@ class DownloadQueue(metaclass=Singleton):
|
||||||
LOG.warning(f"Using external downloader '{yt_conf.get('external_downloader')}' for '{item.url}'.")
|
LOG.warning(f"Using external downloader '{yt_conf.get('external_downloader')}' for '{item.url}'.")
|
||||||
item.extras.update({"external_downloader": True})
|
item.extras.update({"external_downloader": True})
|
||||||
|
|
||||||
|
archive_id = item.get_archive_id()
|
||||||
|
|
||||||
if item.is_archived():
|
if item.is_archived():
|
||||||
if archive_id := item.get_archive_id():
|
if archive_id:
|
||||||
store_type, _ = self.get_item(archive_id=archive_id)
|
store_type, _ = self.get_item(archive_id=archive_id)
|
||||||
if not store_type:
|
if not store_type:
|
||||||
dlInfo = Download(
|
dlInfo = Download(
|
||||||
|
|
@ -750,7 +753,55 @@ class DownloadQueue(metaclass=Singleton):
|
||||||
|
|
||||||
if not item.requeued and (condition := Conditions.get_instance().match(info=entry)):
|
if not item.requeued and (condition := Conditions.get_instance().match(info=entry)):
|
||||||
already.pop()
|
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)
|
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)
|
_status, _msg = ytdlp_reject(entry=entry, yt_params=yt_conf)
|
||||||
|
|
|
||||||
|
|
@ -210,15 +210,12 @@ class Conditions(metaclass=Singleton):
|
||||||
msg = f"Invalid filter. '{e!s}'."
|
msg = f"Invalid filter. '{e!s}'."
|
||||||
raise ValueError(msg) from e
|
raise ValueError(msg) from e
|
||||||
|
|
||||||
if not item.get("cli"):
|
if item.get("cli"):
|
||||||
msg = "No command options for yt-dlp were found."
|
try:
|
||||||
raise ValueError(msg)
|
arg_converter(args=item.get("cli"))
|
||||||
|
except Exception as e:
|
||||||
try:
|
msg = f"Invalid command options for yt-dlp. '{e!s}'."
|
||||||
arg_converter(args=item.get("cli"))
|
raise ValueError(msg) from e
|
||||||
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):
|
if not isinstance(item.get("extras"), dict):
|
||||||
msg = "Extras must be a dictionary."
|
msg = "Extras must be a dictionary."
|
||||||
|
|
|
||||||
|
|
@ -304,8 +304,7 @@ if (!form.extras) {
|
||||||
|
|
||||||
watch(() => config.ytdlp_options, newOptions => ytDlpOpt.value = newOptions
|
watch(() => config.ytdlp_options, newOptions => ytDlpOpt.value = newOptions
|
||||||
.filter(opt => !opt.ignored)
|
.filter(opt => !opt.ignored)
|
||||||
.flatMap(opt => opt.flags
|
.flatMap(opt => opt.flags.filter(flag => flag.startsWith('--'))
|
||||||
.filter(flag => flag.startsWith('--'))
|
|
||||||
.map(flag => ({ value: flag, description: opt.description || '' }))),
|
.map(flag => ({ value: flag, description: opt.description || '' }))),
|
||||||
{ immediate: true }
|
{ immediate: true }
|
||||||
)
|
)
|
||||||
|
|
@ -313,7 +312,7 @@ watch(() => config.ytdlp_options, newOptions => ytDlpOpt.value = newOptions
|
||||||
watch(() => form.filter, () => test_data.value.changed = true)
|
watch(() => form.filter, () => test_data.value.changed = true)
|
||||||
|
|
||||||
const checkInfo = async (): Promise<void> => {
|
const checkInfo = async (): Promise<void> => {
|
||||||
const required: (keyof ConditionItem)[] = ['name', 'filter', 'cli']
|
const required: (keyof ConditionItem)[] = ['name', 'filter']
|
||||||
|
|
||||||
for (const key of required) {
|
for (const key of required) {
|
||||||
if (!form[key]) {
|
if (!form[key]) {
|
||||||
|
|
@ -457,27 +456,20 @@ const logic_test = computed(() => {
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
// Extras management functions
|
const validateKey = (key: string): boolean => /^[a-z][a-z0-9_]*$/.test(key)
|
||||||
const validateKey = (key: string): boolean => {
|
|
||||||
// Key must be lowercase with underscores only
|
|
||||||
return /^[a-z][a-z0-9_]*$/.test(key)
|
|
||||||
}
|
|
||||||
|
|
||||||
const parseValue = (value: string): string | number | boolean => {
|
const parseValue = (value: string): string | number | boolean => {
|
||||||
// Try to parse as number
|
|
||||||
if (!isNaN(Number(value)) && !isNaN(parseFloat(value))) {
|
if (!isNaN(Number(value)) && !isNaN(parseFloat(value))) {
|
||||||
return Number(value)
|
return Number(value)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Try to parse as boolean
|
if ('true' === value.toLowerCase()) {
|
||||||
if (value.toLowerCase() === 'true') {
|
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
if (value.toLowerCase() === 'false') {
|
if ('false' === value.toLowerCase()) {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
// Return as string
|
|
||||||
return value
|
return value
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -491,7 +483,7 @@ const addExtra = (): void => {
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!validateKey(key)) {
|
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
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -519,18 +511,17 @@ const updateExtraKey = (event: Event, oldKey: string): void => {
|
||||||
|
|
||||||
if (!validateKey(newKey)) {
|
if (!validateKey(newKey)) {
|
||||||
toast.error('Key must be lowercase and contain only letters, numbers, and underscores.')
|
toast.error('Key must be lowercase and contain only letters, numbers, and underscores.')
|
||||||
target.value = oldKey // Reset to old value
|
target.value = oldKey
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if (newKey !== oldKey) {
|
if (newKey !== oldKey) {
|
||||||
if (form.extras[newKey]) {
|
if (form.extras[newKey]) {
|
||||||
toast.error(`Key '${newKey}' already exists.`)
|
toast.error(`Key '${newKey}' already exists.`)
|
||||||
target.value = oldKey // Reset to old value
|
target.value = oldKey
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// Move the value to the new key
|
|
||||||
const value = form.extras[oldKey]
|
const value = form.extras[oldKey]
|
||||||
delete form.extras[oldKey]
|
delete form.extras[oldKey]
|
||||||
form.extras[newKey] = value
|
form.extras[newKey] = value
|
||||||
|
|
@ -540,11 +531,6 @@ const updateExtraKey = (event: Event, oldKey: string): void => {
|
||||||
const updateExtraValue = (key: string, event: Event): void => {
|
const updateExtraValue = (key: string, event: Event): void => {
|
||||||
const target = event.target as HTMLInputElement
|
const target = event.target as HTMLInputElement
|
||||||
const value = target.value.trim()
|
const value = target.value.trim()
|
||||||
|
form.extras[key] = value ? parseValue(value) : ''
|
||||||
if (value) {
|
|
||||||
form.extras[key] = parseValue(value)
|
|
||||||
} else {
|
|
||||||
form.extras[key] = ''
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
|
||||||
|
|
@ -64,6 +64,14 @@
|
||||||
<span class="icon"><i class="fa-solid fa-terminal" /></span>
|
<span class="icon"><i class="fa-solid fa-terminal" /></span>
|
||||||
<span>{{ cond.cli }}</span>
|
<span>{{ cond.cli }}</span>
|
||||||
</p>
|
</p>
|
||||||
|
<p class="is-text-overflow" v-if="cond.extras && Object.keys(cond.extras).length > 0">
|
||||||
|
<span class="icon"><i class="fa-solid fa-list" /></span>
|
||||||
|
<span>Extras:
|
||||||
|
<span v-for="(value, key) in cond.extras" :key="key" class="tag is-info mr-2">
|
||||||
|
<strong>{{ key }}</strong>: {{ value }}
|
||||||
|
</span>
|
||||||
|
</span>
|
||||||
|
</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="card-content" v-if="cond?.raw">
|
<div class="card-content" v-if="cond?.raw">
|
||||||
|
|
@ -278,7 +286,7 @@ const exportItem = (cond: ConditionItem): void => {
|
||||||
const userData: ImportedConditionItem = {
|
const userData: ImportedConditionItem = {
|
||||||
...Object.fromEntries(Object.entries(clone).filter(([_, v]) => !!v)),
|
...Object.fromEntries(Object.entries(clone).filter(([_, v]) => !!v)),
|
||||||
_type: 'condition',
|
_type: 'condition',
|
||||||
_version: '1.0',
|
_version: '1.1',
|
||||||
} as ImportedConditionItem
|
} as ImportedConditionItem
|
||||||
|
|
||||||
copyText(encode(userData))
|
copyText(encode(userData))
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue