mirror of
https://github.com/meeb/tubesync.git
synced 2025-06-21 20:46:36 +00:00
Merge pull request #802 from tcely/patch-12
Display task errors from the current page
This commit is contained in:
commit
2b79cd2297
@ -35,7 +35,7 @@
|
|||||||
</div>
|
</div>
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<div class="col s12">
|
<div class="col s12">
|
||||||
<h2>{{ errors|length|intcomma }} Error{{ errors|length|pluralize }}</h2>
|
<h2>{{ total_errors|intcomma }} Total Error{{ total_errors|pluralize }} ({{ errors|length|intcomma }} on this page)</h2>
|
||||||
<p>
|
<p>
|
||||||
Tasks which generated an error are shown here. Tasks are retried a couple of
|
Tasks which generated an error are shown here. Tasks are retried a couple of
|
||||||
times, so if there was an intermittent error such as a download got interrupted
|
times, so if there was an intermittent error such as a download got interrupted
|
||||||
@ -49,14 +49,14 @@
|
|||||||
<i class="fas fa-history"></i> Task will be retried at <strong>{{ task.run_at|date:'Y-m-d H:i:s' }}</strong>
|
<i class="fas fa-history"></i> Task will be retried at <strong>{{ task.run_at|date:'Y-m-d H:i:s' }}</strong>
|
||||||
</a>
|
</a>
|
||||||
{% empty %}
|
{% empty %}
|
||||||
<span class="collection-item no-items"><i class="fas fa-info-circle"></i> There are no tasks with errors.</span>
|
<span class="collection-item no-items"><i class="fas fa-info-circle"></i> There are no tasks with errors on this page.</span>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<div class="col s12">
|
<div class="col s12">
|
||||||
<h2>{{ total_scheduled|intcomma }} Scheduled</h2>
|
<h2>{{ total_scheduled|intcomma }} Scheduled ({{ scheduled|length|intcomma }} on this page)</h2>
|
||||||
<p>
|
<p>
|
||||||
Tasks which are scheduled to run in the future or are waiting in a queue to be
|
Tasks which are scheduled to run in the future or are waiting in a queue to be
|
||||||
processed. They can be waiting for an available worker to run immediately, or
|
processed. They can be waiting for an available worker to run immediately, or
|
||||||
@ -70,7 +70,7 @@
|
|||||||
<i class="fas fa-redo"></i> Task will run {% if task.run_now %}<strong>immediately</strong>{% else %}at <strong>{{ task.run_at|date:'Y-m-d H:i:s' }}</strong>{% endif %}
|
<i class="fas fa-redo"></i> Task will run {% if task.run_now %}<strong>immediately</strong>{% else %}at <strong>{{ task.run_at|date:'Y-m-d H:i:s' }}</strong>{% endif %}
|
||||||
</a>
|
</a>
|
||||||
{% empty %}
|
{% empty %}
|
||||||
<span class="collection-item no-items"><i class="fas fa-info-circle"></i> There are no scheduled tasks.</span>
|
<span class="collection-item no-items"><i class="fas fa-info-circle"></i> There are no scheduled tasks on this page.</span>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -2,7 +2,7 @@ import os
|
|||||||
import re
|
import re
|
||||||
import math
|
import math
|
||||||
from copy import deepcopy
|
from copy import deepcopy
|
||||||
from operator import itemgetter
|
from operator import attrgetter, itemgetter
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
from tempfile import NamedTemporaryFile
|
from tempfile import NamedTemporaryFile
|
||||||
import requests
|
import requests
|
||||||
@ -179,10 +179,16 @@ def seconds_to_timestr(seconds):
|
|||||||
return '{:02d}:{:02d}:{:02d}'.format(hour, minutes, seconds)
|
return '{:02d}:{:02d}:{:02d}'.format(hour, minutes, seconds)
|
||||||
|
|
||||||
|
|
||||||
def multi_key_sort(sort_dict, specs, use_reversed=False):
|
def multi_key_sort(iterable, specs, /, use_reversed=False, *, item=False, attr=False, key_func=None):
|
||||||
result = list(sort_dict)
|
result = list(iterable)
|
||||||
|
if key_func is None:
|
||||||
|
# itemgetter is the default
|
||||||
|
if item or not (item or attr):
|
||||||
|
key_func = itemgetter
|
||||||
|
elif attr:
|
||||||
|
key_func = attrgetter
|
||||||
for key, reverse in reversed(specs):
|
for key, reverse in reversed(specs):
|
||||||
result = sorted(result, key=itemgetter(key), reverse=reverse)
|
result.sort(key=key_func(key), reverse=reverse)
|
||||||
if use_reversed:
|
if use_reversed:
|
||||||
return list(reversed(result))
|
return list(reversed(result))
|
||||||
return result
|
return result
|
||||||
|
@ -27,7 +27,7 @@ from .models import Source, Media, MediaServer
|
|||||||
from .forms import (ValidateSourceForm, ConfirmDeleteSourceForm, RedownloadMediaForm,
|
from .forms import (ValidateSourceForm, ConfirmDeleteSourceForm, RedownloadMediaForm,
|
||||||
SkipMediaForm, EnableMediaForm, ResetTasksForm,
|
SkipMediaForm, EnableMediaForm, ResetTasksForm,
|
||||||
ConfirmDeleteMediaServerForm)
|
ConfirmDeleteMediaServerForm)
|
||||||
from .utils import validate_url, delete_file
|
from .utils import validate_url, delete_file, multi_key_sort
|
||||||
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, index_source_task)
|
delete_task_by_media, index_source_task)
|
||||||
@ -782,24 +782,41 @@ class TasksView(ListView):
|
|||||||
prefix = '-' if 'ASC' != order else ''
|
prefix = '-' if 'ASC' != order else ''
|
||||||
_priority = f'{prefix}priority'
|
_priority = f'{prefix}priority'
|
||||||
return qs.order_by(
|
return qs.order_by(
|
||||||
'run_at',
|
|
||||||
_priority,
|
_priority,
|
||||||
|
'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)
|
||||||
now = timezone.now()
|
now = timezone.now()
|
||||||
qs = Task.objects.all()
|
qs = Task.objects.all()
|
||||||
|
errors_qs = qs.filter(attempts__gt=0, locked_by__isnull=True)
|
||||||
|
running_qs = qs.filter(locked_by__isnull=False)
|
||||||
|
scheduled_qs = qs.filter(locked_by__isnull=True)
|
||||||
|
|
||||||
# Add to context data from ListView
|
# Add to context data from ListView
|
||||||
data['message'] = self.message
|
data['message'] = self.message
|
||||||
data['source'] = self.filter_source
|
data['source'] = self.filter_source
|
||||||
data['running'] = []
|
data['running'] = list()
|
||||||
data['errors'] = []
|
data['errors'] = list()
|
||||||
data['scheduled'] = []
|
data['total_errors'] = errors_qs.count()
|
||||||
data['total_scheduled'] = qs.filter(locked_at__isnull=True).count()
|
data['scheduled'] = list()
|
||||||
|
data['total_scheduled'] = scheduled_qs.count()
|
||||||
|
|
||||||
for task in qs.filter(locked_at__isnull=False):
|
def add_to_task(task):
|
||||||
|
obj, url = map_task_to_instance(task)
|
||||||
|
if not obj:
|
||||||
|
return False
|
||||||
|
setattr(task, 'instance', obj)
|
||||||
|
setattr(task, 'url', url)
|
||||||
|
setattr(task, 'run_now', task.run_at < now)
|
||||||
|
if task.has_error():
|
||||||
|
error_message = get_error_message(task)
|
||||||
|
setattr(task, 'error_message', error_message)
|
||||||
|
return 'error'
|
||||||
|
return True
|
||||||
|
|
||||||
|
for task in running_qs:
|
||||||
# There was broken logic in `Task.objects.locked()`, work around it.
|
# There was broken logic in `Task.objects.locked()`, work around it.
|
||||||
# With that broken logic, the tasks never resume properly.
|
# With that broken logic, the tasks never resume properly.
|
||||||
# This check unlocks the tasks without a running process.
|
# This check unlocks the tasks without a running process.
|
||||||
@ -807,31 +824,27 @@ class TasksView(ListView):
|
|||||||
# - `True`: locked and PID exists
|
# - `True`: locked and PID exists
|
||||||
# - `False`: locked and PID does not exist
|
# - `False`: locked and PID does not exist
|
||||||
# - `None`: not `locked_by`, so there was no PID to check
|
# - `None`: not `locked_by`, so there was no PID to check
|
||||||
if task.locked_by_pid_running() is False:
|
locked_by_pid_running = task.locked_by_pid_running()
|
||||||
|
if locked_by_pid_running is False:
|
||||||
task.locked_by = None
|
task.locked_by = None
|
||||||
# do not wait for the task to expire
|
# do not wait for the task to expire
|
||||||
task.locked_at = None
|
task.locked_at = None
|
||||||
task.save()
|
task.save()
|
||||||
obj, url = map_task_to_instance(task)
|
if locked_by_pid_running and add_to_task(task):
|
||||||
if not obj:
|
|
||||||
# Orphaned task, ignore it (it will be deleted when it fires)
|
|
||||||
continue
|
|
||||||
setattr(task, 'instance', obj)
|
|
||||||
setattr(task, 'url', url)
|
|
||||||
setattr(task, 'run_now', task.run_at < now)
|
|
||||||
if task.locked_by_pid_running():
|
|
||||||
data['running'].append(task)
|
data['running'].append(task)
|
||||||
elif task.has_error():
|
|
||||||
error_message = get_error_message(task)
|
# show all the errors when they fit on one page
|
||||||
setattr(task, 'error_message', error_message)
|
if (data['total_errors'] + len(data['running'])) < self.paginate_by:
|
||||||
data['errors'].append(task)
|
for task in errors_qs:
|
||||||
else:
|
if task in data['running']:
|
||||||
data['scheduled'].append(task)
|
continue
|
||||||
|
mapped = add_to_task(task)
|
||||||
|
if 'error' == mapped:
|
||||||
|
data['errors'].append(task)
|
||||||
|
elif mapped:
|
||||||
|
data['scheduled'].append(task)
|
||||||
|
|
||||||
for task in data['tasks']:
|
for task in data['tasks']:
|
||||||
obj, url = map_task_to_instance(task)
|
|
||||||
if not obj:
|
|
||||||
continue
|
|
||||||
already_added = (
|
already_added = (
|
||||||
task in data['running'] or
|
task in data['running'] or
|
||||||
task in data['errors'] or
|
task in data['errors'] or
|
||||||
@ -839,10 +852,24 @@ class TasksView(ListView):
|
|||||||
)
|
)
|
||||||
if already_added:
|
if already_added:
|
||||||
continue
|
continue
|
||||||
setattr(task, 'instance', obj)
|
mapped = add_to_task(task)
|
||||||
setattr(task, 'url', url)
|
if 'error' == mapped:
|
||||||
setattr(task, 'run_now', task.run_at < now)
|
data['errors'].append(task)
|
||||||
data['scheduled'].append(task)
|
elif mapped:
|
||||||
|
data['scheduled'].append(task)
|
||||||
|
|
||||||
|
order = getattr(settings,
|
||||||
|
'BACKGROUND_TASK_PRIORITY_ORDERING',
|
||||||
|
'DESC'
|
||||||
|
)
|
||||||
|
sort_keys = (
|
||||||
|
# key, reverse
|
||||||
|
('run_now', True),
|
||||||
|
('priority', 'ASC' != order),
|
||||||
|
('run_at', False),
|
||||||
|
)
|
||||||
|
data['errors'] = multi_key_sort(data['errors'], sort_keys, attr=True)
|
||||||
|
data['scheduled'] = multi_key_sort(data['scheduled'], sort_keys, attr=True)
|
||||||
|
|
||||||
return data
|
return data
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user