mirror of
https://github.com/janeczku/calibre-web
synced 2024-12-18 14:10:30 +00:00
Added Scheduled Tasks Settings
This commit is contained in:
parent
0bd544704d
commit
26071d4e7a
62
cps/admin.py
62
cps/admin.py
@ -159,23 +159,6 @@ def shutdown():
|
||||
return json.dumps(showtext), 400
|
||||
|
||||
|
||||
@admi.route("/clear-cache")
|
||||
@login_required
|
||||
@admin_required
|
||||
def clear_cache():
|
||||
cache_type = request.args.get('cache_type'.strip())
|
||||
showtext = {}
|
||||
|
||||
if cache_type == constants.CACHE_TYPE_THUMBNAILS:
|
||||
log.info('clearing cover thumbnail cache')
|
||||
showtext['text'] = _(u'Cleared cover thumbnail cache')
|
||||
helper.clear_cover_thumbnail_cache()
|
||||
return json.dumps(showtext)
|
||||
|
||||
showtext['text'] = _(u'Unknown command')
|
||||
return json.dumps(showtext)
|
||||
|
||||
|
||||
@admi.route("/admin/view")
|
||||
@login_required
|
||||
@admin_required
|
||||
@ -205,6 +188,7 @@ def admin():
|
||||
feature_support=feature_support, kobo_support=kobo_support,
|
||||
title=_(u"Admin page"), page="admin")
|
||||
|
||||
|
||||
@admi.route("/admin/dbconfig", methods=["GET", "POST"])
|
||||
@login_required
|
||||
@admin_required
|
||||
@ -245,6 +229,7 @@ def ajax_db_config():
|
||||
def calibreweb_alive():
|
||||
return "", 200
|
||||
|
||||
|
||||
@admi.route("/admin/viewconfig")
|
||||
@login_required
|
||||
@admin_required
|
||||
@ -257,6 +242,7 @@ def view_configuration():
|
||||
restrictColumns=restrict_columns,
|
||||
title=_(u"UI Configuration"), page="uiconfig")
|
||||
|
||||
|
||||
@admi.route("/admin/usertable")
|
||||
@login_required
|
||||
@admin_required
|
||||
@ -339,6 +325,7 @@ def list_users():
|
||||
response.headers["Content-Type"] = "application/json; charset=utf-8"
|
||||
return response
|
||||
|
||||
|
||||
@admi.route("/ajax/deleteuser", methods=['POST'])
|
||||
@login_required
|
||||
@admin_required
|
||||
@ -372,6 +359,7 @@ def delete_user():
|
||||
success.extend(errors)
|
||||
return Response(json.dumps(success), mimetype='application/json')
|
||||
|
||||
|
||||
@admi.route("/ajax/getlocale")
|
||||
@login_required
|
||||
@admin_required
|
||||
@ -517,6 +505,7 @@ def update_table_settings():
|
||||
return "Invalid request", 400
|
||||
return ""
|
||||
|
||||
|
||||
def check_valid_read_column(column):
|
||||
if column != "0":
|
||||
if not calibre_db.session.query(db.Custom_Columns).filter(db.Custom_Columns.id == column) \
|
||||
@ -524,6 +513,7 @@ def check_valid_read_column(column):
|
||||
return False
|
||||
return True
|
||||
|
||||
|
||||
def check_valid_restricted_column(column):
|
||||
if column != "0":
|
||||
if not calibre_db.session.query(db.Custom_Columns).filter(db.Custom_Columns.id == column) \
|
||||
@ -532,7 +522,6 @@ def check_valid_restricted_column(column):
|
||||
return True
|
||||
|
||||
|
||||
|
||||
@admi.route("/admin/viewconfig", methods=["POST"])
|
||||
@login_required
|
||||
@admin_required
|
||||
@ -564,7 +553,6 @@ def update_view_configuration():
|
||||
_config_int(to_save, "config_books_per_page")
|
||||
_config_int(to_save, "config_authors_max")
|
||||
|
||||
|
||||
config.config_default_role = constants.selected_roles(to_save)
|
||||
config.config_default_role &= ~constants.ROLE_ANONYMOUS
|
||||
|
||||
@ -1210,6 +1198,7 @@ def _db_configuration_update_helper():
|
||||
config.save()
|
||||
return _db_configuration_result(None, gdrive_error)
|
||||
|
||||
|
||||
def _configuration_update_helper():
|
||||
reboot_required = False
|
||||
to_save = request.form.to_dict()
|
||||
@ -1299,6 +1288,7 @@ def _configuration_update_helper():
|
||||
|
||||
return _configuration_result(None, reboot_required)
|
||||
|
||||
|
||||
def _configuration_result(error_flash=None, reboot=False):
|
||||
resp = {}
|
||||
if error_flash:
|
||||
@ -1388,6 +1378,7 @@ def _handle_new_user(to_save, content, languages, translations, kobo_support):
|
||||
log.error("Settings DB is not Writeable")
|
||||
flash(_("Settings DB is not Writeable"), category="error")
|
||||
|
||||
|
||||
def _delete_user(content):
|
||||
if ub.session.query(ub.User).filter(ub.User.role.op('&')(constants.ROLE_ADMIN) == constants.ROLE_ADMIN,
|
||||
ub.User.id != content.id).count():
|
||||
@ -1572,6 +1563,39 @@ def update_mailsettings():
|
||||
return edit_mailsettings()
|
||||
|
||||
|
||||
@admi.route("/admin/scheduledtasks")
|
||||
@login_required
|
||||
@admin_required
|
||||
def edit_scheduledtasks():
|
||||
content = config.get_scheduled_task_settings()
|
||||
return render_title_template("schedule_edit.html", content=content, title=_(u"Edit Scheduled Tasks Settings"))
|
||||
|
||||
|
||||
@admi.route("/admin/scheduledtasks", methods=["POST"])
|
||||
@login_required
|
||||
@admin_required
|
||||
def update_scheduledtasks():
|
||||
to_save = request.form.to_dict()
|
||||
_config_int(to_save, "schedule_start_time")
|
||||
_config_int(to_save, "schedule_end_time")
|
||||
_config_checkbox(to_save, "schedule_generate_book_covers")
|
||||
_config_checkbox(to_save, "schedule_generate_series_covers")
|
||||
|
||||
try:
|
||||
config.save()
|
||||
flash(_(u"Scheduled tasks settings updated"), category="success")
|
||||
except IntegrityError as ex:
|
||||
ub.session.rollback()
|
||||
log.error("An unknown error occurred while saving scheduled tasks settings")
|
||||
flash(_(u"An unknown error occurred. Please try again later."), category="error")
|
||||
except OperationalError:
|
||||
ub.session.rollback()
|
||||
log.error("Settings DB is not Writeable")
|
||||
flash(_("Settings DB is not Writeable"), category="error")
|
||||
|
||||
return edit_scheduledtasks()
|
||||
|
||||
|
||||
@admi.route("/admin/user/<int:user_id>", methods=["GET", "POST"])
|
||||
@login_required
|
||||
@admin_required
|
||||
|
@ -140,6 +140,11 @@ class _Settings(_Base):
|
||||
config_reverse_proxy_login_header_name = Column(String)
|
||||
config_allow_reverse_proxy_header_login = Column(Boolean, default=False)
|
||||
|
||||
schedule_start_time = Column(Integer, default=4)
|
||||
schedule_end_time = Column(Integer, default=6)
|
||||
schedule_generate_book_covers = Column(Boolean, default=False)
|
||||
schedule_generate_series_covers = Column(Boolean, default=False)
|
||||
|
||||
def __repr__(self):
|
||||
return self.__class__.__name__
|
||||
|
||||
@ -170,7 +175,6 @@ class _ConfigSQL(object):
|
||||
if change:
|
||||
self.save()
|
||||
|
||||
|
||||
def _read_from_storage(self):
|
||||
if self._settings is None:
|
||||
log.debug("_ConfigSQL._read_from_storage")
|
||||
@ -254,6 +258,8 @@ class _ConfigSQL(object):
|
||||
return bool((self.mail_server != constants.DEFAULT_MAIL_SERVER and self.mail_server_type == 0)
|
||||
or (self.mail_gmail_token != {} and self.mail_server_type == 1))
|
||||
|
||||
def get_scheduled_task_settings(self):
|
||||
return {k:v for k, v in self.__dict__.items() if k.startswith('schedule_')}
|
||||
|
||||
def set_from_dictionary(self, dictionary, field, convertor=None, default=None, encode=None):
|
||||
"""Possibly updates a field of this object.
|
||||
@ -289,7 +295,6 @@ class _ConfigSQL(object):
|
||||
storage[k] = v
|
||||
return storage
|
||||
|
||||
|
||||
def load(self):
|
||||
'''Load all configuration values from the underlying storage.'''
|
||||
s = self._read_from_storage() # type: _Settings
|
||||
@ -407,6 +412,7 @@ def autodetect_calibre_binary():
|
||||
return element
|
||||
return ""
|
||||
|
||||
|
||||
def autodetect_unrar_binary():
|
||||
if sys.platform == "win32":
|
||||
calibre_path = ["C:\\program files\\WinRar\\unRAR.exe",
|
||||
@ -418,6 +424,7 @@ def autodetect_unrar_binary():
|
||||
return element
|
||||
return ""
|
||||
|
||||
|
||||
def autodetect_kepubify_binary():
|
||||
if sys.platform == "win32":
|
||||
calibre_path = ["C:\\program files\\kepubify\\kepubify-windows-64Bit.exe",
|
||||
@ -429,6 +436,7 @@ def autodetect_kepubify_binary():
|
||||
return element
|
||||
return ""
|
||||
|
||||
|
||||
def _migrate_database(session):
|
||||
# make sure the table is created, if it does not exist
|
||||
_Base.metadata.create_all(session.bind)
|
||||
@ -452,6 +460,7 @@ def load_configuration(session):
|
||||
# session.commit()
|
||||
return conf
|
||||
|
||||
|
||||
def get_flask_session_key(session):
|
||||
flask_settings = session.query(_Flask_Settings).one_or_none()
|
||||
if flask_settings == None:
|
||||
|
@ -19,7 +19,6 @@
|
||||
from __future__ import division, print_function, unicode_literals
|
||||
|
||||
from .services.background_scheduler import BackgroundScheduler
|
||||
from .services.worker import WorkerThread
|
||||
from .tasks.database import TaskReconnectDatabase
|
||||
from .tasks.thumbnail import TaskGenerateCoverThumbnails, TaskGenerateSeriesThumbnails
|
||||
|
||||
@ -28,13 +27,19 @@ def register_jobs():
|
||||
scheduler = BackgroundScheduler()
|
||||
|
||||
if scheduler:
|
||||
# Reconnect metadata.db once every 12 hours
|
||||
scheduler.add_task(user=None, task=lambda: TaskReconnectDatabase(), trigger='cron', hour='4,16')
|
||||
# Reconnect Calibre database (metadata.db)
|
||||
scheduler.schedule_task(user=None, task=lambda: TaskReconnectDatabase(), trigger='cron', hour='4,16')
|
||||
|
||||
# Generate all missing book cover thumbnails once every 24 hours
|
||||
scheduler.add_task(user=None, task=lambda: TaskGenerateCoverThumbnails(), trigger='cron', hour=4)
|
||||
# Generate all missing book cover thumbnails
|
||||
scheduler.schedule_task(user=None, task=lambda: TaskGenerateCoverThumbnails(), trigger='cron', hour=4)
|
||||
|
||||
# Generate all missing series thumbnails
|
||||
scheduler.schedule_task(user=None, task=lambda: TaskGenerateSeriesThumbnails(), trigger='cron', hour=4)
|
||||
|
||||
|
||||
def register_startup_jobs():
|
||||
WorkerThread.add(None, TaskGenerateCoverThumbnails())
|
||||
# WorkerThread.add(None, TaskGenerateSeriesThumbnails())
|
||||
scheduler = BackgroundScheduler()
|
||||
|
||||
if scheduler:
|
||||
scheduler.schedule_task_immediately(None, task=lambda: TaskGenerateCoverThumbnails())
|
||||
scheduler.schedule_task_immediately(None, task=lambda: TaskGenerateSeriesThumbnails())
|
||||
|
@ -40,25 +40,31 @@ class BackgroundScheduler:
|
||||
|
||||
if cls._instance is None:
|
||||
cls._instance = super(BackgroundScheduler, cls).__new__(cls)
|
||||
|
||||
scheduler = BScheduler()
|
||||
atexit.register(lambda: scheduler.shutdown())
|
||||
|
||||
cls.log = logger.create()
|
||||
cls.scheduler = scheduler
|
||||
cls.scheduler = BScheduler()
|
||||
cls.scheduler.start()
|
||||
|
||||
atexit.register(lambda: cls.scheduler.shutdown())
|
||||
|
||||
return cls._instance
|
||||
|
||||
def add(self, func, trigger, **trigger_args):
|
||||
def _add(self, func, trigger, **trigger_args):
|
||||
if use_APScheduler:
|
||||
return self.scheduler.add_job(func=func, trigger=trigger, **trigger_args)
|
||||
|
||||
def add_task(self, user, task, trigger, **trigger_args):
|
||||
# Expects a lambda expression for the task, so that the task isn't instantiated before the task is scheduled
|
||||
def schedule_task(self, user, task, trigger, **trigger_args):
|
||||
if use_APScheduler:
|
||||
def scheduled_task():
|
||||
worker_task = task()
|
||||
self.log.info(f'Running scheduled task in background: {worker_task.name} - {worker_task.message}')
|
||||
WorkerThread.add(user, worker_task)
|
||||
|
||||
return self.add(func=scheduled_task, trigger=trigger, **trigger_args)
|
||||
return self._add(func=scheduled_task, trigger=trigger, **trigger_args)
|
||||
|
||||
# Expects a lambda expression for the task, so that the task isn't instantiated before the task is scheduled
|
||||
def schedule_task_immediately(self, user, task):
|
||||
if use_APScheduler:
|
||||
def scheduled_task():
|
||||
WorkerThread.add(user, task())
|
||||
|
||||
return self._add(func=scheduled_task, trigger='date')
|
||||
|
@ -167,9 +167,9 @@ class TaskGenerateCoverThumbnails(CalibreTask):
|
||||
try:
|
||||
stream = urlopen(web_content_link)
|
||||
with Image(file=stream) as img:
|
||||
height = self.get_thumbnail_height(thumbnail)
|
||||
height = get_resize_height(thumbnail.resolution)
|
||||
if img.height > height:
|
||||
width = self.get_thumbnail_width(height, img)
|
||||
width = get_resize_width(thumbnail.resolution, img.width, img.height)
|
||||
img.resize(width=width, height=height, filter='lanczos')
|
||||
img.format = thumbnail.format
|
||||
filename = self.cache.get_cache_file_path(thumbnail.filename,
|
||||
@ -212,16 +212,6 @@ class TaskGenerateSeriesThumbnails(CalibreTask):
|
||||
constants.COVER_THUMBNAIL_MEDIUM
|
||||
]
|
||||
|
||||
# get all series
|
||||
# get all books in series with covers and count >= 4 books
|
||||
# get the dimensions from the first book in the series & pop the first book from the series list of books
|
||||
# randomly select three other books in the series
|
||||
|
||||
# resize the covers in the sequence?
|
||||
# create an image sequence from the 4 selected books of the series
|
||||
# join pairs of books in the series with wand's concat
|
||||
# join the two sets of pairs with wand's
|
||||
|
||||
def run(self, worker_thread):
|
||||
if self.calibre_db.session and use_IM:
|
||||
all_series = self.get_series_with_four_plus_books()
|
||||
|
@ -156,6 +156,31 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row">
|
||||
<div class="col">
|
||||
<h2>{{_('Scheduled Tasks')}}</h2>
|
||||
<div class="col-xs-12 col-sm-12">
|
||||
<div class="row">
|
||||
<div class="col-xs-6 col-sm-3">{{_('Time at which tasks start to run')}}</div>
|
||||
<div class="col-xs-6 col-sm-3">{{config.schedule_start_time}}:00</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col-xs-6 col-sm-3">{{_('Time at which tasks stop running')}}</div>
|
||||
<div class="col-xs-6 col-sm-3">{{config.schedule_end_time}}:00</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col-xs-6 col-sm-3">{{_('Generate book cover thumbnails')}}</div>
|
||||
<div class="col-xs-6 col-sm-3">{{ display_bool_setting(config.schedule_generate_book_covers) }}</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col-xs-6 col-sm-3">{{_('Generate series cover thumbnails')}}</div>
|
||||
<div class="col-xs-6 col-sm-3">{{ display_bool_setting(config.schedule_generate_series_covers) }}</div>
|
||||
</div>
|
||||
</div>
|
||||
<a class="btn btn-default scheduledtasks" id="admin_edit_scheduled_tasks" href="{{url_for('admin.edit_scheduledtasks')}}">{{_('Edit Scheduled Tasks Settings')}}</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row form-group">
|
||||
<h2>{{_('Administration')}}</h2>
|
||||
<a class="btn btn-default" id="debug" href="{{url_for('admin.download_debug')}}">{{_('Download Debug Package')}}</a>
|
||||
@ -163,7 +188,6 @@
|
||||
</div>
|
||||
<div class="row form-group">
|
||||
<div class="btn btn-default" id="restart_database" data-toggle="modal" data-target="#StatusDialog">{{_('Reconnect Calibre Database')}}</div>
|
||||
<div class="btn btn-default" id="clear_cover_thumbnail_cache" data-toggle="modal" data-target="#ClearCacheDialog">{{_('Clear Cover Thumbnail Cache')}}</div>
|
||||
</div>
|
||||
<div class="row form-group">
|
||||
<div class="btn btn-default" id="admin_restart" data-toggle="modal" data-target="#RestartDialog">{{_('Restart')}}</div>
|
||||
@ -248,21 +272,4 @@
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div id="ClearCacheDialog" class="modal fade" role="dialog">
|
||||
<div class="modal-dialog modal-sm">
|
||||
<!-- Modal content-->
|
||||
<div class="modal-content">
|
||||
<div class="modal-header bg-info"></div>
|
||||
<div class="modal-body text-center">
|
||||
<p>{{_('Are you sure you want to clear the cover thumbnail cache?')}}</p>
|
||||
<div id="spinner3" class="spinner" style="display:none;">
|
||||
<img id="img-spinner3" src="{{ url_for('static', filename='css/libs/images/loading-icon.gif') }}"/>
|
||||
</div>
|
||||
<p></p>
|
||||
<button type="button" class="btn btn-default" id="clear_cache" >{{_('OK')}}</button>
|
||||
<button type="button" class="btn btn-default" data-dismiss="modal">{{_('Cancel')}}</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
||||
|
38
cps/templates/schedule_edit.html
Normal file
38
cps/templates/schedule_edit.html
Normal file
@ -0,0 +1,38 @@
|
||||
{% extends "layout.html" %}
|
||||
{% block header %}
|
||||
<link href="{{ url_for('static', filename='css/libs/bootstrap-table.min.css') }}" rel="stylesheet">
|
||||
<link href="{{ url_for('static', filename='css/libs/bootstrap-editable.css') }}" rel="stylesheet">
|
||||
{% endblock %}
|
||||
{% block body %}
|
||||
<div class="discover">
|
||||
<h1>{{title}}</h1>
|
||||
<form role="form" class="col-md-10 col-lg-6" method="POST" autocomplete="off">
|
||||
<div class="form-group">
|
||||
<label for="schedule_start_time">{{_('Time at which tasks start to run')}}</label>
|
||||
<select name="schedule_start_time" id="schedule_start_time" class="form-control">
|
||||
{% for n in range(24) %}
|
||||
<option value="{{n}}" {% if content.schedule_start_time == n %}selected{% endif %}>{{n}}{{_(':00')}}</option>
|
||||
{% endfor %}
|
||||
</select>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="schedule_end_time">{{_('Time at which tasks stop running')}}</label>
|
||||
<select name="schedule_end_time" id="schedule_end_time" class="form-control">
|
||||
{% for n in range(24) %}
|
||||
<option value="{{n}}" {% if content.schedule_end_time == n %}selected{% endif %}>{{n}}{{_(':00')}}</option>
|
||||
{% endfor %}
|
||||
</select>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<input type="checkbox" id="schedule_generate_book_covers" name="schedule_generate_book_covers" checked>
|
||||
<label for="schedule_generate_book_covers">{{_('Generate Book Cover Thumbnails')}}</label>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<input type="checkbox" id="schedule_generate_series_covers" name="schedule_generate_series_covers" {% if config.schedule_generate_series_covers %}checked{% endif %}>
|
||||
<label for="schedule_generate_series_covers">{{_('Generate Series Cover Thumbnails')}}</label>
|
||||
</div>
|
||||
<button type="submit" name="submit" value="submit" class="btn btn-default">{{_('Save')}}</button>
|
||||
<a href="{{ url_for('admin.admin') }}" id="email_back" class="btn btn-default">{{_('Cancel')}}</a>
|
||||
</form>
|
||||
</div>
|
||||
{% endblock %}
|
Loading…
Reference in New Issue
Block a user