allow media to be skipped before its downloaded, add a master reset-all-tasks button, tweak signal and task behaviour ordering
This commit is contained in:
parent
dc5a8271e1
commit
3489d07289
|
@ -37,3 +37,8 @@ class SkipMediaForm(forms.Form):
|
||||||
class EnableMediaForm(forms.Form):
|
class EnableMediaForm(forms.Form):
|
||||||
|
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
class ResetTasksForm(forms.Form):
|
||||||
|
|
||||||
|
pass
|
||||||
|
|
|
@ -30,21 +30,21 @@ def source_pre_save(sender, instance, **kwargs):
|
||||||
repeat=instance.index_schedule,
|
repeat=instance.index_schedule,
|
||||||
queue=str(instance.pk),
|
queue=str(instance.pk),
|
||||||
priority=5,
|
priority=5,
|
||||||
verbose_name=verbose_name.format(instance.name)
|
verbose_name=verbose_name.format(instance.name),
|
||||||
|
remove_existing_tasks=True
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@receiver(post_save, sender=Source)
|
@receiver(post_save, sender=Source)
|
||||||
def source_post_save(sender, instance, created, **kwargs):
|
def source_post_save(sender, instance, created, **kwargs):
|
||||||
# Triggered after a source is saved, Create a new task to check the directory exists
|
# Check directory exists and create an indexing task for newly created sources
|
||||||
verbose_name = _('Check download directory exists for source "{}"')
|
|
||||||
check_source_directory_exists(
|
|
||||||
str(instance.pk),
|
|
||||||
priority=0,
|
|
||||||
verbose_name=verbose_name.format(instance.name)
|
|
||||||
)
|
|
||||||
if created:
|
if created:
|
||||||
# Create a new indexing task for newly created sources
|
verbose_name = _('Check download directory exists for source "{}"')
|
||||||
|
check_source_directory_exists(
|
||||||
|
str(instance.pk),
|
||||||
|
priority=0,
|
||||||
|
verbose_name=verbose_name.format(instance.name)
|
||||||
|
)
|
||||||
delete_task_by_source('sync.tasks.index_source_task', instance.pk)
|
delete_task_by_source('sync.tasks.index_source_task', instance.pk)
|
||||||
log.info(f'Scheduling media indexing for source: {instance.name}')
|
log.info(f'Scheduling media indexing for source: {instance.name}')
|
||||||
verbose_name = _('Index media from source "{}"')
|
verbose_name = _('Index media from source "{}"')
|
||||||
|
@ -53,7 +53,8 @@ def source_post_save(sender, instance, created, **kwargs):
|
||||||
repeat=instance.index_schedule,
|
repeat=instance.index_schedule,
|
||||||
queue=str(instance.pk),
|
queue=str(instance.pk),
|
||||||
priority=5,
|
priority=5,
|
||||||
verbose_name=verbose_name.format(instance.name)
|
verbose_name=verbose_name.format(instance.name),
|
||||||
|
remove_existing_tasks=True
|
||||||
)
|
)
|
||||||
# Trigger the post_save signal for each media item linked to this source as various
|
# Trigger the post_save signal for each media item linked to this source as various
|
||||||
# flags may need to be recalculated
|
# flags may need to be recalculated
|
||||||
|
@ -102,6 +103,8 @@ def media_post_save(sender, instance, created, **kwargs):
|
||||||
instance.save()
|
instance.save()
|
||||||
post_save.connect(media_post_save, sender=Media)
|
post_save.connect(media_post_save, sender=Media)
|
||||||
# If the media is missing a thumbnail schedule it to be downloaded
|
# If the media is missing a thumbnail schedule it to be downloaded
|
||||||
|
if not instance.thumb_file_exists:
|
||||||
|
instance.thumb = None
|
||||||
if not instance.thumb:
|
if not instance.thumb:
|
||||||
thumbnail_url = instance.thumbnail
|
thumbnail_url = instance.thumbnail
|
||||||
if thumbnail_url:
|
if thumbnail_url:
|
||||||
|
@ -113,9 +116,13 @@ def media_post_save(sender, instance, created, **kwargs):
|
||||||
thumbnail_url,
|
thumbnail_url,
|
||||||
queue=str(instance.source.pk),
|
queue=str(instance.source.pk),
|
||||||
priority=10,
|
priority=10,
|
||||||
verbose_name=verbose_name.format(instance.name)
|
verbose_name=verbose_name.format(instance.name),
|
||||||
|
remove_existing_tasks=True
|
||||||
)
|
)
|
||||||
# If the media has not yet been downloaded schedule it to be downloaded
|
# If the media has not yet been downloaded schedule it to be downloaded
|
||||||
|
if not instance.media_file_exists:
|
||||||
|
instance.downloaded = False
|
||||||
|
instance.media_file = None
|
||||||
if not instance.downloaded and instance.can_download and not instance.skip:
|
if not instance.downloaded and instance.can_download and not instance.skip:
|
||||||
delete_task_by_media('sync.tasks.download_media', (str(instance.pk),))
|
delete_task_by_media('sync.tasks.download_media', (str(instance.pk),))
|
||||||
verbose_name = _('Downloading media for "{}"')
|
verbose_name = _('Downloading media for "{}"')
|
||||||
|
@ -123,7 +130,8 @@ def media_post_save(sender, instance, created, **kwargs):
|
||||||
str(instance.pk),
|
str(instance.pk),
|
||||||
queue=str(instance.source.pk),
|
queue=str(instance.source.pk),
|
||||||
priority=15,
|
priority=15,
|
||||||
verbose_name=verbose_name.format(instance.name)
|
verbose_name=verbose_name.format(instance.name),
|
||||||
|
remove_existing_tasks=True
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -266,6 +266,11 @@ def download_media(media_id):
|
||||||
except Media.DoesNotExist:
|
except Media.DoesNotExist:
|
||||||
# Task triggered but the media no longer exists, do nothing
|
# Task triggered but the media no longer exists, do nothing
|
||||||
return
|
return
|
||||||
|
if media.skip:
|
||||||
|
# Media was toggled to be skipped after the task was scheduled
|
||||||
|
log.warn(f'Download task triggeredd media: {media} (UUID: {media.pk}) but it '
|
||||||
|
f'is now marked to be skipped, not downloading')
|
||||||
|
return
|
||||||
log.info(f'Downloading media: {media} (UUID: {media.pk}) to: "{media.filepath}"')
|
log.info(f'Downloading media: {media} (UUID: {media.pk}) to: "{media.filepath}"')
|
||||||
format_str, container = media.download_media()
|
format_str, container = media.download_media()
|
||||||
if os.path.exists(media.filepath):
|
if os.path.exists(media.filepath):
|
||||||
|
|
|
@ -143,10 +143,14 @@
|
||||||
<a href="{% url 'sync:skip-media' pk=media.pk %}" class="btn delete-button">Delete and skip media <i class="fas fa-times-circle"></i></a>
|
<a href="{% url 'sync:skip-media' pk=media.pk %}" class="btn delete-button">Delete and skip media <i class="fas fa-times-circle"></i></a>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{% elif media.skip %}
|
{% else %}
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<div class="col s12">
|
<div class="col s12">
|
||||||
<a href="{% url 'sync:enable-media' pk=media.pk %}" class="btn">Enable (unskip) media <i class="fas fa-cloud-download-alt"></i></a>
|
{% if media.skip %}
|
||||||
|
<a href="{% url 'sync:enable-media' pk=media.pk %}" class="btn">Enable (unskip) media <i class="fas fa-cloud-download-alt"></i></a>
|
||||||
|
{% else %}
|
||||||
|
<a href="{% url 'sync:skip-media' pk=media.pk %}" class="btn delete-button">Skip media <i class="fas fa-times-circle"></i></a>
|
||||||
|
{% endif %}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|
|
@ -13,7 +13,7 @@
|
||||||
{% for m in media %}
|
{% for m in media %}
|
||||||
<div class="col s12 m6 l4 xl3">
|
<div class="col s12 m6 l4 xl3">
|
||||||
<div class="card mediacard">
|
<div class="card mediacard">
|
||||||
<a href="{% url 'sync:media-item' pk=m.pk %}" class="">
|
<a href="{% url 'sync:media-item' pk=m.pk %}" title="{{ m.source.name }} / {{ m.name }}">
|
||||||
<div class="card-image">
|
<div class="card-image">
|
||||||
<img src="{% if m.thumb %}{% url 'sync:media-thumb' pk=m.pk %}{% else %}{% static 'images/nothumb.png' %}{% endif %}">
|
<img src="{% if m.thumb %}{% url 'sync:media-thumb' pk=m.pk %}{% else %}{% static 'images/nothumb.png' %}{% endif %}">
|
||||||
<span class="card-title truncate">{{ m.source }}<br>
|
<span class="card-title truncate">{{ m.source }}<br>
|
||||||
|
|
|
@ -0,0 +1,31 @@
|
||||||
|
{% extends 'base.html' %}
|
||||||
|
|
||||||
|
{% block headtitle %}Reset tasks{% endblock %}
|
||||||
|
|
||||||
|
{% block content %}
|
||||||
|
<div class="row no-margin-bottom">
|
||||||
|
<div class="col s12">
|
||||||
|
<h1>Reset tasks</h1>
|
||||||
|
<p>
|
||||||
|
If your TubeSync installation has gotten itself into any form of synchronisation
|
||||||
|
state issues (such you moved then restored files on disk) and the state in
|
||||||
|
TubeSync isn't up to date you can use this button to force a state reset.
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
This will delete all current tasks then all souces will be checked for their
|
||||||
|
states and new tasks to be created where required.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="row">
|
||||||
|
<form method="post" action="{% url 'sync:reset-tasks' %}" class="col s12 simpleform">
|
||||||
|
{% csrf_token %}
|
||||||
|
{% include 'simpleform.html' with form=form %}
|
||||||
|
<div class="row no-margin-bottom padding-top">
|
||||||
|
<div class="col s12">
|
||||||
|
<button class="btn" type="submit" name="action">Really reset tasks <i class="fas fa-history"></i></button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
{% endblock %}
|
|
@ -14,6 +14,7 @@
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
{% include 'infobox.html' with message=message %}
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<div class="col s12">
|
<div class="col s12">
|
||||||
<h2>{{ running|length }} Running</h2>
|
<h2>{{ running|length }} Running</h2>
|
||||||
|
@ -84,4 +85,13 @@
|
||||||
<a href="{% url 'sync:tasks-completed' %}" class="btn"><span class="hide-on-med-and-down">View </span>Completed tasks <i class="fas fa-check-double"></i></a>
|
<a href="{% url 'sync:tasks-completed' %}" class="btn"><span class="hide-on-med-and-down">View </span>Completed tasks <i class="fas fa-check-double"></i></a>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<div class="row">
|
||||||
|
<div class="col s12">
|
||||||
|
<h2>Reset</h2>
|
||||||
|
<p>
|
||||||
|
If you need to, you can reset and reschedule all tasks using the button below.
|
||||||
|
</p>
|
||||||
|
<a href="{% url 'sync:reset-tasks' %}" class="btn">Reset tasks <i class="fas fa-history"></i></a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
|
@ -2,7 +2,7 @@ from django.urls import path
|
||||||
from .views import (DashboardView, SourcesView, ValidateSourceView, AddSourceView,
|
from .views import (DashboardView, SourcesView, ValidateSourceView, AddSourceView,
|
||||||
SourceView, UpdateSourceView, DeleteSourceView, MediaView,
|
SourceView, UpdateSourceView, DeleteSourceView, MediaView,
|
||||||
MediaThumbView, MediaItemView, MediaRedownloadView, MediaSkipView,
|
MediaThumbView, MediaItemView, MediaRedownloadView, MediaSkipView,
|
||||||
MediaEnableView, TasksView, CompletedTasksView)
|
MediaEnableView, TasksView, CompletedTasksView, ResetTasks)
|
||||||
|
|
||||||
|
|
||||||
app_name = 'sync'
|
app_name = 'sync'
|
||||||
|
@ -78,4 +78,8 @@ urlpatterns = [
|
||||||
CompletedTasksView.as_view(),
|
CompletedTasksView.as_view(),
|
||||||
name='tasks-completed'),
|
name='tasks-completed'),
|
||||||
|
|
||||||
|
path('tasks-reset',
|
||||||
|
ResetTasks.as_view(),
|
||||||
|
name='reset-tasks'),
|
||||||
|
|
||||||
]
|
]
|
||||||
|
|
|
@ -16,11 +16,11 @@ from common.utils import append_uri_params
|
||||||
from background_task.models import Task, CompletedTask
|
from background_task.models import Task, CompletedTask
|
||||||
from .models import Source, Media
|
from .models import Source, Media
|
||||||
from .forms import (ValidateSourceForm, ConfirmDeleteSourceForm, RedownloadMediaForm,
|
from .forms import (ValidateSourceForm, ConfirmDeleteSourceForm, RedownloadMediaForm,
|
||||||
SkipMediaForm, EnableMediaForm)
|
SkipMediaForm, EnableMediaForm, ResetTasksForm)
|
||||||
from .utils import validate_url, delete_file
|
from .utils import validate_url, delete_file
|
||||||
from .tasks import (map_task_to_instance, get_error_message,
|
from .tasks import (map_task_to_instance, get_error_message,
|
||||||
get_source_completed_tasks, get_media_download_task,
|
get_source_completed_tasks, get_media_download_task,
|
||||||
delete_task_by_media)
|
delete_task_by_media, index_source_task)
|
||||||
from . import signals
|
from . import signals
|
||||||
from . import youtube
|
from . import youtube
|
||||||
|
|
||||||
|
@ -380,6 +380,9 @@ class MediaThumbView(DetailView):
|
||||||
|
|
||||||
|
|
||||||
class MediaItemView(DetailView):
|
class MediaItemView(DetailView):
|
||||||
|
'''
|
||||||
|
A single media item overview page.
|
||||||
|
'''
|
||||||
|
|
||||||
template_name = 'sync/media-item.html'
|
template_name = 'sync/media-item.html'
|
||||||
model = Media
|
model = Media
|
||||||
|
@ -419,6 +422,9 @@ class MediaItemView(DetailView):
|
||||||
|
|
||||||
|
|
||||||
class MediaRedownloadView(FormView, SingleObjectMixin):
|
class MediaRedownloadView(FormView, SingleObjectMixin):
|
||||||
|
'''
|
||||||
|
Confirm that the media file should be deleted and redownloaded.
|
||||||
|
'''
|
||||||
|
|
||||||
template_name = 'sync/media-redownload.html'
|
template_name = 'sync/media-redownload.html'
|
||||||
form_class = RedownloadMediaForm
|
form_class = RedownloadMediaForm
|
||||||
|
@ -461,6 +467,9 @@ class MediaRedownloadView(FormView, SingleObjectMixin):
|
||||||
|
|
||||||
|
|
||||||
class MediaSkipView(FormView, SingleObjectMixin):
|
class MediaSkipView(FormView, SingleObjectMixin):
|
||||||
|
'''
|
||||||
|
Confirm that the media file should be deleted and marked to skip.
|
||||||
|
'''
|
||||||
|
|
||||||
template_name = 'sync/media-skip.html'
|
template_name = 'sync/media-skip.html'
|
||||||
form_class = SkipMediaForm
|
form_class = SkipMediaForm
|
||||||
|
@ -500,6 +509,9 @@ class MediaSkipView(FormView, SingleObjectMixin):
|
||||||
|
|
||||||
|
|
||||||
class MediaEnableView(FormView, SingleObjectMixin):
|
class MediaEnableView(FormView, SingleObjectMixin):
|
||||||
|
'''
|
||||||
|
Confirm that the media item should be re-enabled (marked as unskipped).
|
||||||
|
'''
|
||||||
|
|
||||||
template_name = 'sync/media-enable.html'
|
template_name = 'sync/media-enable.html'
|
||||||
form_class = EnableMediaForm
|
form_class = EnableMediaForm
|
||||||
|
@ -532,12 +544,25 @@ class TasksView(ListView):
|
||||||
|
|
||||||
template_name = 'sync/tasks.html'
|
template_name = 'sync/tasks.html'
|
||||||
context_object_name = 'tasks'
|
context_object_name = 'tasks'
|
||||||
|
messages = {
|
||||||
|
'reset': _('All tasks have been reset'),
|
||||||
|
}
|
||||||
|
|
||||||
|
def __init__(self, *args, **kwargs):
|
||||||
|
self.message = None
|
||||||
|
super().__init__(*args, **kwargs)
|
||||||
|
|
||||||
|
def dispatch(self, request, *args, **kwargs):
|
||||||
|
message_key = request.GET.get('message', '')
|
||||||
|
self.message = self.messages.get(message_key, '')
|
||||||
|
return super().dispatch(request, *args, **kwargs)
|
||||||
|
|
||||||
def get_queryset(self):
|
def get_queryset(self):
|
||||||
return Task.objects.all().order_by('run_at')
|
return Task.objects.all().order_by('run_at')
|
||||||
|
|
||||||
def get_context_data(self, *args, **kwargs):
|
def get_context_data(self, *args, **kwargs):
|
||||||
data = super().get_context_data(*args, **kwargs)
|
data = super().get_context_data(*args, **kwargs)
|
||||||
|
data['message'] = self.message
|
||||||
data['running'] = []
|
data['running'] = []
|
||||||
data['errors'] = []
|
data['errors'] = []
|
||||||
data['scheduled'] = []
|
data['scheduled'] = []
|
||||||
|
@ -610,3 +635,36 @@ class CompletedTasksView(ListView):
|
||||||
data['message'] = message.format(name=self.filter_source.name)
|
data['message'] = message.format(name=self.filter_source.name)
|
||||||
data['source'] = self.filter_source
|
data['source'] = self.filter_source
|
||||||
return data
|
return data
|
||||||
|
|
||||||
|
|
||||||
|
class ResetTasks(FormView):
|
||||||
|
'''
|
||||||
|
Confirm that all tasks should be reset. As all tasks are triggered from
|
||||||
|
signals by checking for files existing etc. this can be done by just deleting
|
||||||
|
all tasks and then calling every Source objects .save() method.
|
||||||
|
'''
|
||||||
|
|
||||||
|
template_name = 'sync/tasks-reset.html'
|
||||||
|
form_class = ResetTasksForm
|
||||||
|
|
||||||
|
def form_valid(self, form):
|
||||||
|
# Delete all tasks
|
||||||
|
Task.objects.all().delete()
|
||||||
|
# Iter all tasks
|
||||||
|
for source in Source.objects.all():
|
||||||
|
# Recreate the initial indexing task
|
||||||
|
verbose_name = _('Index media from source "{}"')
|
||||||
|
index_source_task(
|
||||||
|
str(source.pk),
|
||||||
|
repeat=source.index_schedule,
|
||||||
|
queue=str(source.pk),
|
||||||
|
priority=5,
|
||||||
|
verbose_name=verbose_name.format(source.name)
|
||||||
|
)
|
||||||
|
# This also chains down to call each Media objects .save() as well
|
||||||
|
source.save()
|
||||||
|
return super().form_valid(form)
|
||||||
|
|
||||||
|
def get_success_url(self):
|
||||||
|
url = reverse_lazy('sync:tasks')
|
||||||
|
return append_uri_params(url, {'message': 'reset'})
|
||||||
|
|
|
@ -119,7 +119,7 @@ MAX_RUN_TIME = 1800 # Maximum amount of time in seconds
|
||||||
BACKGROUND_TASK_RUN_ASYNC = False # Run tasks async in the background
|
BACKGROUND_TASK_RUN_ASYNC = False # Run tasks async in the background
|
||||||
BACKGROUND_TASK_ASYNC_THREADS = 1 # Number of async tasks to run at once
|
BACKGROUND_TASK_ASYNC_THREADS = 1 # Number of async tasks to run at once
|
||||||
BACKGROUND_TASK_PRIORITY_ORDERING = 'ASC' # Use 'niceness' task priority ordering
|
BACKGROUND_TASK_PRIORITY_ORDERING = 'ASC' # Use 'niceness' task priority ordering
|
||||||
COMPLETED_TASKS_DAYS_TO_KEEP = 30 # Number of days to keep completed tasks
|
COMPLETED_TASKS_DAYS_TO_KEEP = 7 # Number of days to keep completed tasks
|
||||||
|
|
||||||
|
|
||||||
SOURCES_PER_PAGE = 36
|
SOURCES_PER_PAGE = 36
|
||||||
|
|
Loading…
Reference in New Issue