Merge pull request #538 from arabcoders/dev

Feat: add flag to ignore already archived items from showing in download history.
This commit is contained in:
Abdulmohsen 2026-01-07 18:03:29 +03:00 committed by GitHub
commit 1189d31f5c
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
10 changed files with 128 additions and 131 deletions

1
FAQ.md
View file

@ -55,6 +55,7 @@ or the `environment:` section in `compose.yaml` file.
| YTP_AUTO_CLEAR_HISTORY_DAYS | Number of days after which completed download history is cleared. | `0` |
| YTP_DEFAULT_PAGINATION | The default number of items per page for history. | `50` |
| YTP_TASK_HANDLER_RANDOM_DELAY | The maximum random delay in seconds before starting a task handler. | `60` |
| YTP_IGNORE_ARCHIVED_ITEMS | Don't report archived items in the download history. | `false` |
> [!NOTE]
> To raise the maximum workers for specific extractor, you need to add a ENV variable that follows the pattern `YTP_MAX_WORKERS_FOR_<EXTRACTOR_NAME>`.

View file

@ -734,7 +734,7 @@ class DownloadQueue(metaclass=Singleton):
# This sometimes can be different from the final extracted ID, so we need to verify again after extraction.
if archive_id and item.is_archived():
store_type, dlInfo = await self.get_item(archive_id=archive_id)
if not store_type:
if not store_type and not self.config.ignore_archived_items:
dlInfo = Download(
info=ItemDTO(
id=archive_id.split()[1],
@ -763,7 +763,7 @@ class DownloadQueue(metaclass=Singleton):
)
return {"status": "ok"}
message: str = f"The URL '{item.url}' is already downloaded and recorded in archive."
message: str = f"The URL '{item.url}':'{archive_id}' is already downloaded and recorded in archive."
LOG.warning(message)
self._notify.emit(
Events.LOG_INFO, data={"preset": item.preset}, title="Already Downloaded", message=message
@ -827,7 +827,7 @@ class DownloadQueue(metaclass=Singleton):
if store_type:
break
if not store_type:
if not store_type and not self.config.ignore_archived_items:
new_archive_id = new_archive_id or extra_ids.pop(0)
dlInfo = Download(
info=ItemDTO(
@ -855,7 +855,7 @@ class DownloadQueue(metaclass=Singleton):
)
return {"status": "ok"}
message: str = f"The URL '{item.url}' is already downloaded and recorded in archive."
message: str = f"The URL '{item.url}':'{archive_ids.pop(0)}' is already downloaded and recorded in archive."
LOG.warning(message)
self._notify.emit(
Events.LOG_INFO, data={"preset": item.preset}, title="Already Downloaded", message=message

View file

@ -216,6 +216,9 @@ class Config(metaclass=Singleton):
task_handler_random_delay: float = 60.0
"""The maximum random delay in seconds before starting a task handler."""
ignore_archived_items: bool = False
"""Dont report archived items in the download history."""
pictures_backends: list[str] = [
"https://unsplash.it/1920/1080?random",
"https://picsum.photos/1920/1080",
@ -280,6 +283,7 @@ class Config(metaclass=Singleton):
"temp_disabled",
"allow_internal_urls",
"simple_mode",
"ignore_archived_items",
)
"The variables that are booleans."

View file

@ -1,64 +1,48 @@
<template>
<div class="columns is-multiline is-mobile has-text-centered" v-if="hasItems">
<div class="column is-half-mobile">
<button type="button" class="button is-fullwidth is-ghost is-inverted"
@click="masterSelectAll = !masterSelectAll">
<span v-if="selectedElms.length > 0" class="mr-2 tag is-danger">
{{ selectedElms.length }}
<div class="columns is-multiline is-mobile has-text-centered is-justify-content-flex-end" v-if="hasItems">
<div class="column is-narrow">
<button type="button" class="button is-fullwidth" @click="masterSelectAll = !masterSelectAll"
:class="{ 'has-text-primary': !masterSelectAll, 'has-text-danger': masterSelectAll }">
<span class="icon">
<i :class="!masterSelectAll ? 'fa-regular fa-square-check' : 'fa-regular fa-square'" />
</span>
<span class="icon-text is-block">
<span class="icon">
<i :class="!masterSelectAll ? 'fa-regular fa-square-check' : 'fa-regular fa-square'" />
</span>
<span v-if="!masterSelectAll">Select All</span>
<span v-else>Unselect All</span>
<span v-if="!masterSelectAll">Select</span>
<span v-else>Unselect</span>
<span v-if="selectedElms.length > 0">
&nbsp;(<u class="has-text-danger">{{ selectedElms.length }}</u>)
</span>
</button>
</div>
<div class="column is-half-mobile">
<button type="button" class="button is-fullwidth is-link" @click="downloadSelected"
:disabled="!hasDownloaded || !hasSelected"
v-tooltip="!hasSelected || !hasDownloaded ? '' : 'Download items as zip'">
<span class="icon-text is-block">
<div class="column is-2-tablet is-5-mobile">
<Dropdown label="Actions" icons="fa-solid fa-list">
<a class="dropdown-item has-text-link" @click="(hasDownloaded && hasSelected) ? downloadSelected() : null"
:style="{ opacity: (!hasDownloaded || !hasSelected) ? 0.5 : 1, cursor: (!hasDownloaded || !hasSelected) ? 'not-allowed' : 'pointer' }"
v-tooltip="!hasSelected || !hasDownloaded ? '' : 'Download items as zip'">
<span class="icon"><i class="fa-solid fa-compress-alt" /></span>
<span>Download</span>
</span>
</button>
</div>
<div class="column is-half-mobile">
<button type="button" class="button is-fullwidth is-danger" @click="deleteSelectedItems" :disabled="!hasSelected">
<span class="icon-text is-block">
</a>
<a class="dropdown-item has-text-danger" @click="hasSelected ? deleteSelectedItems() : null"
:style="{ opacity: !hasSelected ? 0.5 : 1, cursor: !hasSelected ? 'not-allowed' : 'pointer' }">
<span class="icon"><i class="fa-solid fa-trash-can" /></span>
<span>{{ config.app.remove_files ? 'Remove' : 'Clear' }}</span>
</span>
</button>
</div>
<div class="column is-half-mobile" v-if="hasCompleted">
<button type="button" class="button is-fullwidth is-primary is-inverted" @click="clearCompleted">
<span class="icon-text is-block">
</a>
<hr class="dropdown-divider" v-if="hasCompleted || hasIncomplete">
<a v-if="hasCompleted" class="dropdown-item has-text-primary" @click="clearCompleted">
<span class="icon"><i class="fa-solid fa-circle-check" /></span>
<span>Clear Completed</span>
</span>
</button>
</div>
<div class="column is-half-mobile" v-if="hasIncomplete">
<button type="button" class="button is-fullwidth is-info is-inverted" @click="clearIncomplete">
<span class="icon-text is-block">
</a>
<a v-if="hasIncomplete" class="dropdown-item has-text-info" @click="clearIncomplete">
<span class="icon"><i class="fa-solid fa-circle-xmark" /></span>
<span>Clear Incomplete</span>
</span>
</button>
</div>
<div class="column is-half-mobile" v-if="hasIncomplete">
<button type="button" class="button is-fullwidth is-warning is-inverted" @click="retryIncomplete">
<span class="icon-text is-block">
</a>
<a v-if="hasIncomplete" class="dropdown-item has-text-warning" @click="retryIncomplete">
<span class="icon"><i class="fa-solid fa-rotate-right" /></span>
<span>Retry Incomplete</span>
</span>
</button>
</a>
</Dropdown>
</div>
<div class="column is-1-tablet">
<button type="button" class="button is-fullwidth" @click="direction = direction === 'desc' ? 'asc' : 'desc'">
<div class="column is-narrow">
<button type="button" class="button" @click="direction = direction === 'desc' ? 'asc' : 'desc'">
<span class="icon-text is-block">
<span class="icon">
<i class="fa-solid" :class="direction === 'desc' ? 'fa-arrow-down-a-z' : 'fa-arrow-up-a-z'" />
@ -725,7 +709,10 @@ const clearCompleted = async () => {
return
}
selectedElms.value = []
await stateStore.deleteItems('history', { status: 'finished', removeFile: false })
Promise.all([
stateStore.deleteItems('history', { status: 'finished', removeFile: false }),
stateStore.deleteItems('history', { status: 'skip', removeFile: false })
])
}
const clearIncomplete = async () => {

View file

@ -1,37 +1,37 @@
<template>
<div class="columns is-multiline is-mobile has-text-centered" v-if="filteredItems.length > 0">
<div class="column is-half-mobile">
<button type="button" class="button is-fullwidth is-ghost" @click="masterSelectAll = !masterSelectAll">
<span v-if="selectedElms.length > 0" class="mr-2 tag is-danger">
{{ selectedElms.length }}
<div class="columns is-multiline is-mobile has-text-centered is-justify-content-flex-end"
v-if="filteredItems.length > 0">
<div class="column is-narrow">
<button type="button" class="button" @click="masterSelectAll = !masterSelectAll"
:class="{ 'has-text-primary': !masterSelectAll, 'has-text-danger': masterSelectAll }">
<span class="icon">
<i :class="!masterSelectAll ? 'fa-regular fa-square-check' : 'fa-regular fa-square'" />
</span>
<span class="icon-text is-block">
<span class="icon">
<i class="fas" :class="!masterSelectAll ? 'fa-regular fa-square-check' : 'fa-regular fa-square'" />
</span>
<span v-if="!masterSelectAll">Select All</span>
<span v-else> Unselect All</span>
<span v-if="!masterSelectAll">Select</span>
<span v-else>Unselect</span>
<span v-if="selectedElms.length > 0">
&nbsp;(<u class="has-text-danger">{{ selectedElms.length }}</u>)
</span>
</button>
</div>
<div class="column is-half-mobile" v-if="hasManualStart">
<button type="button" class="button is-fullwidth is-success" :disabled="!hasSelected" @click="startItems">
<span class="icon"><i class="fa-solid fa-circle-play" /></span>
<span>Start</span>
</button>
</div>
<div class="column is-half-mobile" v-if="hasPausable">
<button type="button" class="button is-fullwidth is-warning is-background-warning-85" :disabled="!hasSelected"
@click="pauseSelected">
<span class="icon"><i class="fa-solid fa-pause" /></span>
<span>Pause</span>
</button>
</div>
<div class="column is-half-mobile">
<button type="button" class="button is-fullwidth is-warning" :disabled="!hasSelected" @click="cancelSelected">
<span class="icon"><i class="fa-solid fa-eject" /></span>
<span>Cancel</span>
</button>
<div class="column is-2-tablet is-5-mobile">
<Dropdown label="Actions" icons="fa-solid fa-list">
<a v-if="hasManualStart" class="dropdown-item has-text-success" @click="hasSelected ? startItems() : null"
:style="{ opacity: !hasSelected ? 0.5 : 1, cursor: !hasSelected ? 'not-allowed' : 'pointer' }">
<span class="icon"><i class="fa-solid fa-circle-play" /></span>
<span>Start</span>
</a>
<a v-if="hasPausable" class="dropdown-item has-text-warning" @click="hasSelected ? pauseSelected() : null"
:style="{ opacity: !hasSelected ? 0.5 : 1, cursor: !hasSelected ? 'not-allowed' : 'pointer' }">
<span class="icon"><i class="fa-solid fa-pause" /></span>
<span>Pause</span>
</a>
<a class="dropdown-item has-text-warning" @click="hasSelected ? cancelSelected() : null"
:style="{ opacity: !hasSelected ? 0.5 : 1, cursor: !hasSelected ? 'not-allowed' : 'pointer' }">
<span class="icon"><i class="fa-solid fa-eject" /></span>
<span>Cancel</span>
</a>
</Dropdown>
</div>
</div>

View file

@ -105,36 +105,36 @@
</div>
</div>
<div class="columns is-mobile is-multiline" v-if="config.app.browser_control_enabled && items && items.length > 0">
<div class="column is-12-mobile">
<button type="button" class="button is-fullwidth is-ghost is-inverted"
@click="masterSelectAll = !masterSelectAll" :disabled="isLoading || items.length < 1">
<span class="icon-text is-block">
<span class="icon">
<i :class="!masterSelectAll ? 'fa-regular fa-square-check' : 'fa-regular fa-square'" />
</span>
<span v-if="!masterSelectAll">Select All</span>
<span v-else>Unselect All</span>
<div class="columns is-mobile is-multiline is-justify-content-flex-end" v-if="config.app.browser_control_enabled && items && items.length > 0">
<div class="column is-narrow">
<button type="button" class="button" @click="masterSelectAll = !masterSelectAll"
:class="{ 'has-text-primary': !masterSelectAll, 'has-text-danger': masterSelectAll }"
:disabled="isLoading || items.length < 1">
<span class="icon">
<i :class="!masterSelectAll ? 'fa-regular fa-square-check' : 'fa-regular fa-square'" />
</span>
<span v-if="!masterSelectAll">Select</span>
<span v-else>Unselect</span>
<span v-if="selectedElms.length > 0">
&nbsp;(<u class="has-text-danger">{{ selectedElms.length }}</u>)
</span>
</button>
</div>
<div class="column is-half-mobile">
<button type="button" class="button is-fullwidth is-danger" @click="deleteSelected"
:disabled="selectedElms.length < 1 || isLoading || items.length < 1">
<span class="icon-text is-block">
<div class="column is-2-tablet is-5-mobile">
<Dropdown label="Actions" icons="fa-solid fa-list">
<a class="dropdown-item has-text-danger"
@click="(selectedElms.length > 0 && !isLoading) ? deleteSelected() : null"
:style="{ opacity: (selectedElms.length < 1 || isLoading) ? 0.5 : 1, cursor: (selectedElms.length < 1 || isLoading) ? 'not-allowed' : 'pointer' }">
<span class="icon"><i class="fa-solid fa-trash-can" /></span>
<span>Delete {{ selectedElms.length > 0 ? selectedElms.length : '' }} items</span>
</span>
</button>
</div>
<div class="column is-half-mobile">
<button type="button" class="button is-fullwidth is-link" @click="moveSelected"
:disabled="selectedElms.length < 1 || isLoading || items.length < 1">
<span class="icon-text is-block">
<span>Delete{{ selectedElms.length > 0 ? ` (${selectedElms.length})` : '' }}</span>
</a>
<a class="dropdown-item has-text-link"
@click="(selectedElms.length > 0 && !isLoading) ? moveSelected() : null"
:style="{ opacity: (selectedElms.length < 1 || isLoading) ? 0.5 : 1, cursor: (selectedElms.length < 1 || isLoading) ? 'not-allowed' : 'pointer' }">
<span class="icon"><i class="fa-solid fa-arrows-alt" /></span>
<span>Move {{ selectedElms.length > 0 ? selectedElms.length : '' }} items</span>
</span>
</button>
<span>Move{{ selectedElms.length > 0 ? ` (${selectedElms.length})` : '' }}</span>
</a>
</Dropdown>
</div>
</div>

View file

@ -197,7 +197,7 @@
<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 }}
<b>{{ key }}</b>: {{ value }}
</span>
</span>
</p>

View file

@ -72,36 +72,36 @@
</div>
</div>
<div class="columns is-multiline is-mobile" v-if="!toggleForm && filteredTasks && filteredTasks.length > 0">
<div class="column">
<button type="button" class="button is-fullwidth is-ghost is-inverted"
@click="masterSelectAll = !masterSelectAll">
<span class="icon-text is-block">
<span class="icon">
<i :class="!masterSelectAll ? 'fa-regular fa-square-check' : 'fa-regular fa-square'" />
</span>
<span v-if="!masterSelectAll">Select All</span>
<span v-else>Unselect All</span>
<div class="columns is-multiline is-mobile is-justify-content-flex-end" v-if="!toggleForm && filteredTasks && filteredTasks.length > 0">
<div class="column is-narrow">
<button type="button" class="button" @click="masterSelectAll = !masterSelectAll"
:class="{ 'has-text-primary': !masterSelectAll, 'has-text-danger': masterSelectAll }">
<span class="icon">
<i :class="!masterSelectAll ? 'fa-regular fa-square-check' : 'fa-regular fa-square'" />
</span>
<span v-if="!masterSelectAll">Select</span>
<span v-else>Unselect</span>
<span v-if="selectedElms.length > 0">
&nbsp;(<u class="has-text-danger">{{ selectedElms.length }}</u>)
</span>
</button>
</div>
<div class="column">
<button class="button is-purple is-fullwidth" @click="runConfirm()"
:disabled="selectedElms.length < 1 || massRun" :class="{ 'is-loading': massRun }">
<span class="icon"><i class="fa-solid fa-up-right-from-square" /></span>
<span>Run Selected</span>
</button>
</div>
<div class="column">
<button type="button" class="button is-fullwidth is-danger" @click="deleteConfirm()"
:disabled="selectedElms.length < 1 || massDelete" :class="{ 'is-loading': massDelete }">
<span class="icon-text is-block">
<div class="column is-2-tablet is-5-mobile">
<Dropdown label="Actions" icons="fa-solid fa-list">
<a class="dropdown-item has-text-purple"
@click="(selectedElms.length > 0 && !massRun) ? runConfirm() : null"
:style="{ opacity: (selectedElms.length < 1 || massRun) ? 0.5 : 1, cursor: (selectedElms.length < 1 || massRun) ? 'not-allowed' : 'pointer' }">
<span class="icon"><i class="fa-solid fa-up-right-from-square" /></span>
<span>Run Selected</span>
</a>
<a class="dropdown-item has-text-danger"
@click="(selectedElms.length > 0 && !massDelete) ? deleteConfirm() : null"
:style="{ opacity: (selectedElms.length < 1 || massDelete) ? 0.5 : 1, cursor: (selectedElms.length < 1 || massDelete) ? 'not-allowed' : 'pointer' }">
<span class="icon"><i class="fa-solid fa-trash-can" /></span>
<span>Remove Selected</span>
</span>
</button>
</a>
</Dropdown>
</div>
</div>

View file

@ -159,7 +159,7 @@ export const useSocketStore = defineStore('socket', () => {
return;
}
if (json.data?.folder) {
if (json.data?.folders) {
config.add('folders', json.data.folders)
}

View file

@ -176,7 +176,12 @@ export const useStateStore = defineStore('state', () => {
const getPagination = () => state.pagination
const setHistoryCount = (count: number) => state.pagination.total = count
const setHistoryCount = (count: number) => {
state.pagination.total = count
if (count > 0 && !state.pagination.isLoaded) {
state.pagination.isLoaded = false
}
}
/**
* Delete items by specific IDs or status filter.