Added toggle for task handler.

This commit is contained in:
arabcoders 2025-07-25 20:17:51 +03:00
parent dcaeb7acc8
commit 584d4e107e
6 changed files with 80 additions and 23 deletions

View file

@ -111,7 +111,7 @@
"youtu" "youtu"
], ],
"css.styleSheets": [ "css.styleSheets": [
"ui/assets/css/*.css" "ui/app/assets/css/*.css"
], ],
"spellright.language": [ "spellright.language": [
"en" "en"

View file

@ -58,6 +58,7 @@ class Scheduler(metaclass=Singleton):
for job in self._jobs: for job in self._jobs:
try: try:
self._jobs[job].stop() self._jobs[job].stop()
LOG.debug(f"Stopped job '{job}'.")
except Exception as e: except Exception as e:
LOG.exception(e) LOG.exception(e)
LOG.error(f"Failed to stop job '{job}'. Error message '{e!s}'.") LOG.error(f"Failed to stop job '{job}'. Error message '{e!s}'.")

View file

@ -35,6 +35,7 @@ class Task:
template: str = "" template: str = ""
cli: str = "" cli: str = ""
auto_start: bool = True auto_start: bool = True
enabled_handler: bool = True
def serialize(self) -> dict: def serialize(self) -> dict:
return self.__dict__ return self.__dict__
@ -100,7 +101,8 @@ class Tasks(metaclass=Singleton):
return Tasks._instance return Tasks._instance
async def on_shutdown(self, _: web.Application): async def on_shutdown(self, _: web.Application):
pass self.clear(shutdown=True)
self._task_handler.on_shutdown(_)
def attach(self, _: web.Application): def attach(self, _: web.Application):
""" """
@ -116,6 +118,7 @@ class Tasks(metaclass=Singleton):
lambda data, _, **kwargs: self.save(**data.data), # noqa: ARG005 lambda data, _, **kwargs: self.save(**data.data), # noqa: ARG005
f"{__class__.__name__}.add", f"{__class__.__name__}.add",
) )
self._task_handler.load()
def get_all(self) -> list[Task]: def get_all(self) -> list[Task]:
"""Return the tasks.""" """Return the tasks."""
@ -155,7 +158,7 @@ class Tasks(metaclass=Singleton):
self._tasks.append(task) self._tasks.append(task)
if not task.timer or "[only_handler]" in task.name: if not task.timer:
continue continue
try: try:
@ -361,12 +364,21 @@ class Tasks(metaclass=Singleton):
class HandleTask: class HandleTask:
_tasks: Tasks _tasks: Tasks
_handlers: list[type]
_scheduler: Scheduler
_config: Config
_task_name: str
def __init__(self, scheduler: Scheduler, tasks: Tasks, config: Config) -> None: def __init__(self, scheduler: Scheduler, tasks: Tasks, config: Config) -> None:
self._tasks = tasks self._tasks = tasks
self._scheduler = scheduler
self._config = config
self._task_name: str = f"{__class__.__name__}._dispatcher"
def load(self) -> None:
self._handlers: list[type] = self._discover() self._handlers: list[type] = self._discover()
timer = config.tasks_handler_timer timer: str = self._config.tasks_handler_timer
try: try:
from cronsim import CronSim from cronsim import CronSim
@ -375,18 +387,26 @@ class HandleTask:
timer = "15 */1 * * *" timer = "15 */1 * * *"
LOG.error(f"Invalid timer format. '{e!s}'. Defaulting to '{timer}'.") LOG.error(f"Invalid timer format. '{e!s}'. Defaulting to '{timer}'.")
scheduler.add( self._scheduler.add(
timer=timer, timer=timer,
func=self._dispatcher, func=self._dispatcher,
id=f"{__class__.__name__}._dispatcher", id=f"{__class__.__name__}._dispatcher",
) )
def on_shutdown(self, _: web.Application) -> None:
"""
Handle shutdown event.
Args:
_: web.Application: The aiohttp application.
"""
if self._scheduler.has(self._task_name):
self._scheduler.remove(self._task_name)
def _dispatcher(self): def _dispatcher(self):
for task in self._tasks.get_all(): for task in self._tasks.get_all():
if "[no_handler]" in task.name: if not task.enabled_handler:
continue
if not task.timer and "[only_handler]" not in task.name:
continue continue
try: try:

View file

@ -215,6 +215,28 @@
</div> </div>
</div> </div>
<div class="column is-6-tablet is-12-mobile">
<div class="field">
<label class="label is-inline" for="output_template">
<span class="icon"><i class="fa-solid fa-rss" /></span>
Enable Handler
</label>
<div class="control is-unselectable">
<input id="auto_start" type="checkbox" v-model="form.enabled_handler" :disabled="addInProgress"
class="switch is-success" />
<label for="auto_start" class="is-unselectable">
{{ form.enabled_handler ? 'Yes' : 'No' }}
</label>
</div>
<span class="help">
<span class="icon"><i class="fa-solid fa-info" /></span>
<span>Some URLs like YouTube channels and playlists can be monitored for new videos using RSS
feed independent of the timer. If enabled, the task will monitor the RSS feed for new videos and
automatically queue them for download if they are not already downloaded.</span>
</span>
</div>
</div>
<div class="column is-12"> <div class="column is-12">
<div class="field"> <div class="field">
<label class="label is-inline" for="cli_options"> <label class="label is-inline" for="cli_options">
@ -260,22 +282,19 @@
</div> </div>
<div class="column is-12"> <div class="column is-12">
<Message title="Tips" class="is-background-info-80 has-text-dark" icon="fas fa-info-circle"> <Message title="Tips" class="is-info is-background-info-80 has-text-dark" icon="fas fa-info-circle">
<span> <span>
<ul> <ul>
<li>To enable YouTube RSS feed monitoring, The task URL must include a <code>channel_id</code> or <li>To enable YouTube RSS feed monitoring, The task URL must include a <code>channel_id</code> or
<code>playlist_id</code>. Other link types wont work. <code>playlist_id</code>. Other link types wont work.
</li> </li>
<li>RSS monitoring runs every hour alongside the actual task execution. It checks each feed for new videos <li>RSS monitoring runs every hour alongside the actual task execution. Regardless if the task has timer set
and automatically queues any you havent downloaded yet.</li> or not. To opt out of RSS monitoring for a specific task, simply disable the <code>Enable Handler</code>
<li>To opt out of RSS monitoring for a specific task, append <code>[no_handler]</code> to that tasks name. option. To have the task only monitor RSS feed, <b>do not set timer</b>.</li>
</li>
<li>To have the task only monitor RSS feed, do not set timer and add <code>[only_handler]</code> to that
tasks name.</li>
<li>RSS Feed monitoring will only work if you have <code>--download-archive</code> set in command options <li>RSS Feed monitoring will only work if you have <code>--download-archive</code> set in command options
for ytdlp.cli, preset or task.</li> for ytdlp.cli, preset or task. If you don't have <code>--download-archive</code> set but
<li>If you don't have <code>--download-archive</code> set but <code>YTP_KEEP_ARCHIVE</code> environment <code>YTP_KEEP_ARCHIVE</code> environment option is set to <code>true</code> which is the default, It will
option is set to <code>true</code> which is the default, It will also work. also work.
</li> </li>
</ul> </ul>
</span> </span>
@ -320,9 +339,15 @@ onMounted(() => {
if (!props.task?.preset || '' === props.task.preset) { if (!props.task?.preset || '' === props.task.preset) {
form.preset = toRaw(config.app.default_preset) form.preset = toRaw(config.app.default_preset)
} }
if (typeof form.auto_start === 'undefined' || null === form.auto_start) { if (typeof form.auto_start === 'undefined' || null === form.auto_start) {
form.auto_start = true form.auto_start = true
} }
if (typeof form.enabled_handler === 'undefined' || null === form.enabled_handler) {
form.enabled_handler = true
}
}) })
const checkInfo = async (): Promise<void> => { const checkInfo = async (): Promise<void> => {

View file

@ -66,8 +66,8 @@ div.is-centered {
<div class="columns is-multiline" v-if="toggleForm"> <div class="columns is-multiline" v-if="toggleForm">
<div class="column is-12"> <div class="column is-12">
<TaskForm :addInProgress="addInProgress" :reference="taskRef" :task="task" @cancel="resetForm(true);" <TaskForm :addInProgress="addInProgress" :reference="taskRef" :task="task as task_item"
@submit="updateItem" /> @cancel="resetForm(true);" @submit="updateItem" />
</div> </div>
</div> </div>
@ -149,7 +149,7 @@ div.is-centered {
{{ remove_tags(item.name) }} {{ remove_tags(item.name) }}
</NuxtLink> </NuxtLink>
</div> </div>
<div> <div class="is-unselectable">
<span class="icon-text"> <span class="icon-text">
<span class="icon"> <span class="icon">
<i class="fa-solid" <i class="fa-solid"
@ -160,6 +160,11 @@ div.is-centered {
</span> </span>
</span> </span>
&nbsp; &nbsp;
<span class="icon-text">
<span class="icon"><i class="fa-solid fa-rss" /></span>
<span>{{ item.enabled_handler ? 'Enabled' : 'Disabled' }}</span>
</span>
&nbsp;
<span class="icon-text" v-if="item.preset"> <span class="icon-text" v-if="item.preset">
<span class="icon"><i class="fa-solid fa-tv" /></span> <span class="icon"><i class="fa-solid fa-tv" /></span>
<span>{{ item.preset ?? config.app.default_preset }}</span> <span>{{ item.preset ?? config.app.default_preset }}</span>
@ -233,6 +238,11 @@ div.is-centered {
:class="{ 'fa-circle-pause': item.auto_start, 'fa-circle-play': !item.auto_start }" /> :class="{ 'fa-circle-pause': item.auto_start, 'fa-circle-play': !item.auto_start }" />
</span> </span>
</div> </div>
<div class="control">
<span class="icon" v-tooltip="`RSS monitoring is ${item.enabled_handler ? 'enabled' : 'disabled'}`">
<i class="fa-solid fa-rss has-text-success" />
</span>
</div>
<div class="control"> <div class="control">
<a class="has-text-primary" v-tooltip="'Export task.'" @click.prevent="exportItem(item)"> <a class="has-text-primary" v-tooltip="'Export task.'" @click.prevent="exportItem(item)">
<span class="icon"><i class="fa-solid fa-file-export" /></span> <span class="icon"><i class="fa-solid fa-file-export" /></span>
@ -544,7 +554,7 @@ const deleteItem = async (item: task_item) => {
toast.success('Task deleted.') toast.success('Task deleted.')
} }
const updateItem = async ({ reference, task }: { reference?: string, task: task_item }) => { const updateItem = async ({ reference, task }: { reference?: string | null | undefined, task: task_item }) => {
if (reference) { if (reference) {
// -- find the task index. // -- find the task index.
const index = tasks.value.findIndex((t) => t?.id === reference) const index = tasks.value.findIndex((t) => t?.id === reference)

View file

@ -9,6 +9,7 @@ type task_item = {
timer?: string, timer?: string,
in_progress?: boolean, in_progress?: boolean,
auto_start?: boolean, auto_start?: boolean,
enabled_handler?: boolean,
} }
type exported_task = task_item & { _type: string, _version: string } type exported_task = task_item & { _type: string, _version: string }