mirror of
https://github.com/janeczku/calibre-web
synced 2024-11-29 04:49:58 +00:00
Added clear cache button to admin settings, updated cache busting for book cover images
This commit is contained in:
parent
541fc7e14e
commit
626051e489
19
cps/admin.py
19
cps/admin.py
@ -38,7 +38,7 @@ from sqlalchemy import and_
|
||||
from sqlalchemy.exc import IntegrityError, OperationalError, InvalidRequestError
|
||||
from sqlalchemy.sql.expression import func, or_
|
||||
|
||||
from . import constants, logger, helper, services
|
||||
from . import constants, logger, helper, services, fs
|
||||
from . import db, calibre_db, ub, web_server, get_locale, config, updater_thread, babel, gdriveutils
|
||||
from .helper import check_valid_domain, send_test_mail, reset_password, generate_password_hash
|
||||
from .gdriveutils import is_gdrive_ready, gdrive_support
|
||||
@ -157,6 +157,23 @@ 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 == fs.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
|
||||
|
@ -595,6 +595,7 @@ def upload_cover(request, book):
|
||||
abort(403)
|
||||
ret, message = helper.save_cover(requested_file, book.path)
|
||||
if ret is True:
|
||||
helper.clear_cover_thumbnail_cache(book.id)
|
||||
return True
|
||||
else:
|
||||
flash(message, category="error")
|
||||
@ -684,6 +685,7 @@ def edit_book(book_id):
|
||||
if result is True:
|
||||
book.has_cover = 1
|
||||
modif_date = True
|
||||
helper.clear_cover_thumbnail_cache(book.id)
|
||||
else:
|
||||
flash(error, category="error")
|
||||
|
||||
|
@ -58,6 +58,7 @@ from .constants import STATIC_DIR as _STATIC_DIR
|
||||
from .subproc_wrapper import process_wait
|
||||
from .services.worker import WorkerThread, STAT_WAITING, STAT_FAIL, STAT_STARTED, STAT_FINISH_SUCCESS
|
||||
from .tasks.mail import TaskEmail
|
||||
from .tasks.thumbnail import TaskClearCoverThumbnailCache
|
||||
|
||||
log = logger.create()
|
||||
|
||||
@ -525,6 +526,7 @@ def update_dir_stucture(book_id, calibrepath, first_author=None, orignal_filepat
|
||||
|
||||
|
||||
def delete_book(book, calibrepath, book_format):
|
||||
clear_cover_thumbnail_cache(book.id)
|
||||
if config.config_use_google_drive:
|
||||
return delete_book_gdrive(book, book_format)
|
||||
else:
|
||||
@ -538,9 +540,9 @@ def get_cover_on_failure(use_generic_cover):
|
||||
return None
|
||||
|
||||
|
||||
def get_book_cover(book_id, resolution=1):
|
||||
def get_book_cover(book_id):
|
||||
book = calibre_db.get_filtered_book(book_id, allow_show_archived=True)
|
||||
return get_book_cover_internal(book, use_generic_cover_on_failure=True, resolution=resolution)
|
||||
return get_book_cover_internal(book, use_generic_cover_on_failure=True)
|
||||
|
||||
|
||||
def get_book_cover_with_uuid(book_uuid, use_generic_cover_on_failure=True):
|
||||
@ -548,11 +550,19 @@ def get_book_cover_with_uuid(book_uuid, use_generic_cover_on_failure=True):
|
||||
return get_book_cover_internal(book, use_generic_cover_on_failure)
|
||||
|
||||
|
||||
def get_book_cover_internal(book, use_generic_cover_on_failure, resolution=1, disable_thumbnail=False):
|
||||
def get_cached_book_cover(cache_id):
|
||||
parts = cache_id.split('_')
|
||||
book_uuid = parts[0] if len(parts) else None
|
||||
resolution = parts[2] if len(parts) > 2 else None
|
||||
book = calibre_db.get_book_by_uuid(book_uuid) if book_uuid else None
|
||||
return get_book_cover_internal(book, use_generic_cover_on_failure=True, resolution=resolution)
|
||||
|
||||
|
||||
def get_book_cover_internal(book, use_generic_cover_on_failure, resolution=None):
|
||||
if book and book.has_cover:
|
||||
|
||||
# Send the book cover thumbnail if it exists in cache
|
||||
if not disable_thumbnail:
|
||||
if resolution:
|
||||
thumbnail = get_book_cover_thumbnail(book, resolution)
|
||||
if thumbnail:
|
||||
cache = fs.FileSystem()
|
||||
@ -585,7 +595,7 @@ def get_book_cover_internal(book, use_generic_cover_on_failure, resolution=1, di
|
||||
return get_cover_on_failure(use_generic_cover_on_failure)
|
||||
|
||||
|
||||
def get_book_cover_thumbnail(book, resolution=1):
|
||||
def get_book_cover_thumbnail(book, resolution):
|
||||
if book and book.has_cover:
|
||||
return ub.session\
|
||||
.query(ub.Thumbnail)\
|
||||
@ -846,3 +856,6 @@ def get_download_link(book_id, book_format, client):
|
||||
else:
|
||||
abort(404)
|
||||
|
||||
|
||||
def clear_cover_thumbnail_cache(book_id=None):
|
||||
WorkerThread.add(None, TaskClearCoverThumbnailCache(book_id))
|
||||
|
@ -128,8 +128,14 @@ def formatseriesindex_filter(series_index):
|
||||
return series_index
|
||||
return 0
|
||||
|
||||
|
||||
@jinjia.app_template_filter('uuidfilter')
|
||||
def uuidfilter(var):
|
||||
return uuid4()
|
||||
|
||||
|
||||
@jinjia.app_template_filter('book_cover_cache_id')
|
||||
def book_cover_cache_id(book, resolution=None):
|
||||
timestamp = int(book.last_modified.timestamp() * 1000)
|
||||
cache_bust = str(book.uuid) + '_' + str(timestamp)
|
||||
return cache_bust if not resolution else cache_bust + '_' + str(resolution)
|
||||
|
@ -18,12 +18,10 @@
|
||||
|
||||
from __future__ import division, print_function, unicode_literals
|
||||
|
||||
from . import config, db, logger, ub
|
||||
from .services.background_scheduler import BackgroundScheduler
|
||||
from .tasks.database import TaskReconnectDatabase
|
||||
from .tasks.thumbnail import TaskCleanupCoverThumbnailCache, TaskGenerateCoverThumbnails
|
||||
|
||||
log = logger.create()
|
||||
|
||||
|
||||
def register_jobs():
|
||||
scheduler = BackgroundScheduler()
|
||||
@ -35,10 +33,4 @@ def register_jobs():
|
||||
scheduler.add_task(user=None, task=lambda: TaskCleanupCoverThumbnailCache(), trigger='cron', hour=4)
|
||||
|
||||
# Reconnect metadata.db every 4 hours
|
||||
scheduler.add(func=reconnect_db_job, trigger='interval', hours=4)
|
||||
|
||||
|
||||
def reconnect_db_job():
|
||||
log.info('Running background task: reconnect to calibre database')
|
||||
calibre_db = db.CalibreDB()
|
||||
calibre_db.reconnect_db(config, ub.app_DB_path)
|
||||
scheduler.add_task(user=None, task=lambda: TaskReconnectDatabase(), trigger='interval', hours=4)
|
||||
|
@ -5167,7 +5167,7 @@ body.login > div.navbar.navbar-default.navbar-static-top > div > div.navbar-head
|
||||
pointer-events: none
|
||||
}
|
||||
|
||||
#DeleteDomain:hover:before, #RestartDialog:hover:before, #ShutdownDialog:hover:before, #StatusDialog:hover:before, #deleteButton, #deleteModal:hover:before, body.mailset > div.container-fluid > div > div.col-sm-10 > div.discover td > a:hover {
|
||||
#DeleteDomain:hover:before, #RestartDialog:hover:before, #ShutdownDialog:hover:before, #StatusDialog:hover:before, #ClearCacheDialog:hover:before, #deleteButton, #deleteModal:hover:before, body.mailset > div.container-fluid > div > div.col-sm-10 > div.discover td > a:hover {
|
||||
cursor: pointer
|
||||
}
|
||||
|
||||
@ -5254,7 +5254,7 @@ body.admin > div.container-fluid > div > div.col-sm-10 > div.container-fluid > d
|
||||
margin-bottom: 20px
|
||||
}
|
||||
|
||||
body.admin:not(.modal-open) .btn-default {
|
||||
body.admin .btn-default {
|
||||
margin-bottom: 10px
|
||||
}
|
||||
|
||||
@ -5485,7 +5485,7 @@ body.admin.modal-open .navbar {
|
||||
z-index: 0 !important
|
||||
}
|
||||
|
||||
#RestartDialog, #ShutdownDialog, #StatusDialog, #deleteModal {
|
||||
#RestartDialog, #ShutdownDialog, #StatusDialog, #ClearCacheDialog, #deleteModal {
|
||||
top: 0;
|
||||
overflow: hidden;
|
||||
padding-top: 70px;
|
||||
@ -5495,7 +5495,7 @@ body.admin.modal-open .navbar {
|
||||
background: rgba(0, 0, 0, .5)
|
||||
}
|
||||
|
||||
#RestartDialog:before, #ShutdownDialog:before, #StatusDialog:before, #deleteModal:before {
|
||||
#RestartDialog:before, #ShutdownDialog:before, #StatusDialog:before, #ClearCacheDialog:before, #deleteModal:before {
|
||||
content: "\E208";
|
||||
padding-right: 10px;
|
||||
display: block;
|
||||
@ -5517,18 +5517,18 @@ body.admin.modal-open .navbar {
|
||||
z-index: 99
|
||||
}
|
||||
|
||||
#RestartDialog.in:before, #ShutdownDialog.in:before, #StatusDialog.in:before, #deleteModal.in:before {
|
||||
#RestartDialog.in:before, #ShutdownDialog.in:before, #StatusDialog.in:before, #ClearCacheDialog.in:before, #deleteModal.in:before {
|
||||
-webkit-transform: translate(0, 0);
|
||||
-ms-transform: translate(0, 0);
|
||||
transform: translate(0, 0)
|
||||
}
|
||||
|
||||
#RestartDialog > .modal-dialog, #ShutdownDialog > .modal-dialog, #StatusDialog > .modal-dialog, #deleteModal > .modal-dialog {
|
||||
#RestartDialog > .modal-dialog, #ShutdownDialog > .modal-dialog, #StatusDialog > .modal-dialog, #ClearCacheDialog > .modal-dialog, #deleteModal > .modal-dialog {
|
||||
width: 450px;
|
||||
margin: auto
|
||||
}
|
||||
|
||||
#RestartDialog > .modal-dialog > .modal-content, #ShutdownDialog > .modal-dialog > .modal-content, #StatusDialog > .modal-dialog > .modal-content, #deleteModal > .modal-dialog > .modal-content {
|
||||
#RestartDialog > .modal-dialog > .modal-content, #ShutdownDialog > .modal-dialog > .modal-content, #StatusDialog > .modal-dialog > .modal-content, #ClearCacheDialog > .modal-dialog > .modal-content, #deleteModal > .modal-dialog > .modal-content {
|
||||
max-height: calc(100% - 90px);
|
||||
-webkit-box-shadow: 0 5px 15px rgba(0, 0, 0, .5);
|
||||
box-shadow: 0 5px 15px rgba(0, 0, 0, .5);
|
||||
@ -5539,7 +5539,7 @@ body.admin.modal-open .navbar {
|
||||
width: 450px
|
||||
}
|
||||
|
||||
#RestartDialog > .modal-dialog > .modal-content > .modal-header, #ShutdownDialog > .modal-dialog > .modal-content > .modal-header, #StatusDialog > .modal-dialog > .modal-content > .modal-header, #deleteModal > .modal-dialog > .modal-content > .modal-header {
|
||||
#RestartDialog > .modal-dialog > .modal-content > .modal-header, #ShutdownDialog > .modal-dialog > .modal-content > .modal-header, #StatusDialog > .modal-dialog > .modal-content > .modal-header, #ClearCacheDialog > .modal-dialog > .modal-content > .modal-header, #deleteModal > .modal-dialog > .modal-content > .modal-header {
|
||||
padding: 15px 20px;
|
||||
border-radius: 3px 3px 0 0;
|
||||
line-height: 1.71428571;
|
||||
@ -5552,7 +5552,7 @@ body.admin.modal-open .navbar {
|
||||
text-align: left
|
||||
}
|
||||
|
||||
#RestartDialog > .modal-dialog > .modal-content > .modal-header:before, #ShutdownDialog > .modal-dialog > .modal-content > .modal-header:before, #StatusDialog > .modal-dialog > .modal-content > .modal-header:before, #deleteModal > .modal-dialog > .modal-content > .modal-header:before {
|
||||
#RestartDialog > .modal-dialog > .modal-content > .modal-header:before, #ShutdownDialog > .modal-dialog > .modal-content > .modal-header:before, #StatusDialog > .modal-dialog > .modal-content > .modal-header:before, #ClearCacheDialog > .modal-dialog > .modal-content > .modal-header:before, #deleteModal > .modal-dialog > .modal-content > .modal-header:before {
|
||||
padding-right: 10px;
|
||||
font-size: 18px;
|
||||
color: #999;
|
||||
@ -5576,6 +5576,11 @@ body.admin.modal-open .navbar {
|
||||
font-family: plex-icons-new, serif
|
||||
}
|
||||
|
||||
#ClearCacheDialog > .modal-dialog > .modal-content > .modal-header:before {
|
||||
content: "\EA15";
|
||||
font-family: plex-icons-new, serif
|
||||
}
|
||||
|
||||
#deleteModal > .modal-dialog > .modal-content > .modal-header:before {
|
||||
content: "\EA6D";
|
||||
font-family: plex-icons-new, serif
|
||||
@ -5599,6 +5604,12 @@ body.admin.modal-open .navbar {
|
||||
font-size: 20px
|
||||
}
|
||||
|
||||
#ClearCacheDialog > .modal-dialog > .modal-content > .modal-header:after {
|
||||
content: "Clear Cover Thumbnail Cache";
|
||||
display: inline-block;
|
||||
font-size: 20px
|
||||
}
|
||||
|
||||
#deleteModal > .modal-dialog > .modal-content > .modal-header:after {
|
||||
content: "Delete Book";
|
||||
display: inline-block;
|
||||
@ -5629,7 +5640,17 @@ body.admin.modal-open .navbar {
|
||||
text-align: left
|
||||
}
|
||||
|
||||
#RestartDialog > .modal-dialog > .modal-content > .modal-body > p, #ShutdownDialog > .modal-dialog > .modal-content > .modal-body > p, #StatusDialog > .modal-dialog > .modal-content > .modal-body > p, #deleteModal > .modal-dialog > .modal-content > .modal-body > p {
|
||||
#ClearCacheDialog > .modal-dialog > .modal-content > .modal-body {
|
||||
padding: 20px 20px 10px;
|
||||
font-size: 16px;
|
||||
line-height: 1.6em;
|
||||
font-family: Open Sans Regular, Helvetica Neue, Helvetica, Arial, sans-serif;
|
||||
color: #eee;
|
||||
background: #282828;
|
||||
text-align: left
|
||||
}
|
||||
|
||||
#RestartDialog > .modal-dialog > .modal-content > .modal-body > p, #ShutdownDialog > .modal-dialog > .modal-content > .modal-body > p, #StatusDialog > .modal-dialog > .modal-content > .modal-body > p, #ClearCacheDialog > .modal-dialog > .modal-content > .modal-body > p, #deleteModal > .modal-dialog > .modal-content > .modal-body > p {
|
||||
padding: 20px 20px 0 0;
|
||||
font-size: 16px;
|
||||
line-height: 1.6em;
|
||||
@ -5638,7 +5659,7 @@ body.admin.modal-open .navbar {
|
||||
background: #282828
|
||||
}
|
||||
|
||||
#RestartDialog > .modal-dialog > .modal-content > .modal-body > .btn-default:not(#restart), #ShutdownDialog > .modal-dialog > .modal-content > .modal-body > .btn-default:not(#shutdown), #deleteModal > .modal-dialog > .modal-content > .modal-footer > .btn-default {
|
||||
#RestartDialog > .modal-dialog > .modal-content > .modal-body > .btn-default:not(#restart), #ShutdownDialog > .modal-dialog > .modal-content > .modal-body > .btn-default:not(#shutdown), #ClearCacheDialog > .modal-dialog > .modal-content > .modal-body > .btn-default:not(#clear_cache), #deleteModal > .modal-dialog > .modal-content > .modal-footer > .btn-default {
|
||||
float: right;
|
||||
z-index: 9;
|
||||
position: relative;
|
||||
@ -5674,6 +5695,18 @@ body.admin.modal-open .navbar {
|
||||
border-radius: 3px
|
||||
}
|
||||
|
||||
#ClearCacheDialog > .modal-dialog > .modal-content > .modal-body > #clear_cache {
|
||||
float: right;
|
||||
z-index: 9;
|
||||
position: relative;
|
||||
margin: 25px 0 0 10px;
|
||||
min-width: 80px;
|
||||
padding: 10px 18px;
|
||||
font-size: 16px;
|
||||
line-height: 1.33;
|
||||
border-radius: 3px
|
||||
}
|
||||
|
||||
#deleteModal > .modal-dialog > .modal-content > .modal-footer > .btn-danger {
|
||||
float: right;
|
||||
z-index: 9;
|
||||
@ -5694,11 +5727,15 @@ body.admin.modal-open .navbar {
|
||||
margin: 55px 0 0 10px
|
||||
}
|
||||
|
||||
#ClearCacheDialog > .modal-dialog > .modal-content > .modal-body > .btn-default:not(#clear_cache) {
|
||||
margin: 25px 0 0 10px
|
||||
}
|
||||
|
||||
#deleteModal > .modal-dialog > .modal-content > .modal-footer > .btn-default {
|
||||
margin: 0 0 0 10px
|
||||
}
|
||||
|
||||
#RestartDialog > .modal-dialog > .modal-content > .modal-body > .btn-default:not(#restart):hover, #ShutdownDialog > .modal-dialog > .modal-content > .modal-body > .btn-default:not(#shutdown):hover, #deleteModal > .modal-dialog > .modal-content > .modal-footer > .btn-default:hover {
|
||||
#RestartDialog > .modal-dialog > .modal-content > .modal-body > .btn-default:not(#restart):hover, #ShutdownDialog > .modal-dialog > .modal-content > .modal-body > .btn-default:not(#shutdown):hover, #ClearCacheDialog > .modal-dialog > .modal-content > .modal-body > .btn-default:not(#clear_cache):hover, #deleteModal > .modal-dialog > .modal-content > .modal-footer > .btn-default:hover {
|
||||
background-color: hsla(0, 0%, 100%, .3)
|
||||
}
|
||||
|
||||
@ -5732,6 +5769,21 @@ body.admin.modal-open .navbar {
|
||||
box-shadow: 0 5px 15px rgba(0, 0, 0, .5)
|
||||
}
|
||||
|
||||
#ClearCacheDialog > .modal-dialog > .modal-content > .modal-body:after {
|
||||
content: '';
|
||||
position: absolute;
|
||||
width: 100%;
|
||||
height: 72px;
|
||||
background-color: #323232;
|
||||
border-radius: 0 0 3px 3px;
|
||||
left: 0;
|
||||
margin-top: 10px;
|
||||
z-index: 0;
|
||||
border-top: 1px solid #222;
|
||||
-webkit-box-shadow: 0 5px 15px rgba(0, 0, 0, .5);
|
||||
box-shadow: 0 5px 15px rgba(0, 0, 0, .5)
|
||||
}
|
||||
|
||||
#deleteButton {
|
||||
position: fixed;
|
||||
top: 60px;
|
||||
@ -7322,11 +7374,11 @@ body.edituser.admin > div.container-fluid > div.row-fluid > div.col-sm-10 > div.
|
||||
background-color: transparent !important
|
||||
}
|
||||
|
||||
#RestartDialog > .modal-dialog, #ShutdownDialog > .modal-dialog, #StatusDialog > .modal-dialog, #deleteModal > .modal-dialog {
|
||||
#RestartDialog > .modal-dialog, #ShutdownDialog > .modal-dialog, #StatusDialog > .modal-dialog, #ClearCacheDialog > .modal-dialog, #deleteModal > .modal-dialog {
|
||||
max-width: calc(100vw - 40px)
|
||||
}
|
||||
|
||||
#RestartDialog > .modal-dialog > .modal-content, #ShutdownDialog > .modal-dialog > .modal-content, #StatusDialog > .modal-dialog > .modal-content, #deleteModal > .modal-dialog > .modal-content {
|
||||
#RestartDialog > .modal-dialog > .modal-content, #ShutdownDialog > .modal-dialog > .modal-content, #StatusDialog > .modal-dialog > .modal-content, #ClearCacheDialog > .modal-dialog > .modal-content, #deleteModal > .modal-dialog > .modal-content {
|
||||
max-width: calc(100vw - 40px);
|
||||
left: 0
|
||||
}
|
||||
@ -7476,7 +7528,7 @@ body.edituser.admin > div.container-fluid > div.row-fluid > div.col-sm-10 > div.
|
||||
padding: 30px 15px
|
||||
}
|
||||
|
||||
#RestartDialog.in:before, #ShutdownDialog.in:before, #StatusDialog.in:before, #deleteModal.in:before {
|
||||
#RestartDialog.in:before, #ShutdownDialog.in:before, #StatusDialog.in:before, #ClearCacheDialog.in:before, #deleteModal.in:before {
|
||||
left: auto;
|
||||
right: 34px
|
||||
}
|
||||
|
@ -405,6 +405,18 @@ $(function() {
|
||||
}
|
||||
});
|
||||
});
|
||||
$("#clear_cache").click(function () {
|
||||
$("#spinner3").show();
|
||||
$.ajax({
|
||||
dataType: "json",
|
||||
url: window.location.pathname + "/../../clear-cache",
|
||||
data: {"cache_type":"thumbnails"},
|
||||
success: function(data) {
|
||||
$("#spinner3").hide();
|
||||
$("#ClearCacheDialog").modal("hide");
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
// Init all data control handlers to default
|
||||
$("input[data-control]").trigger("change");
|
||||
|
49
cps/tasks/database.py
Normal file
49
cps/tasks/database.py
Normal file
@ -0,0 +1,49 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
# This file is part of the Calibre-Web (https://github.com/janeczku/calibre-web)
|
||||
# Copyright (C) 2020 mmonkey
|
||||
#
|
||||
# This program is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
from __future__ import division, print_function, unicode_literals
|
||||
|
||||
from cps import config, logger
|
||||
from cps.services.worker import CalibreTask
|
||||
|
||||
try:
|
||||
from urllib.request import urlopen
|
||||
except ImportError as e:
|
||||
from urllib2 import urlopen
|
||||
|
||||
|
||||
class TaskReconnectDatabase(CalibreTask):
|
||||
def __init__(self, task_message=u'Reconnecting Calibre database'):
|
||||
super(TaskReconnectDatabase, self).__init__(task_message)
|
||||
self.log = logger.create()
|
||||
self.listen_address = config.get_config_ipaddress()
|
||||
self.listen_port = config.config_port
|
||||
|
||||
def run(self, worker_thread):
|
||||
address = self.listen_address if self.listen_address else 'localhost'
|
||||
port = self.listen_port if self.listen_port else 8083
|
||||
|
||||
try:
|
||||
urlopen('http://' + address + ':' + str(port) + '/reconnect')
|
||||
self._handleSuccess()
|
||||
except Exception as ex:
|
||||
self._handleError(u'Unable to reconnect Calibre database: ' + str(ex))
|
||||
|
||||
@property
|
||||
def name(self):
|
||||
return "Reconnect Database"
|
@ -42,7 +42,6 @@ THUMBNAIL_RESOLUTION_2X = 2
|
||||
class TaskGenerateCoverThumbnails(CalibreTask):
|
||||
def __init__(self, limit=100, task_message=u'Generating cover thumbnails'):
|
||||
super(TaskGenerateCoverThumbnails, self).__init__(task_message)
|
||||
self.self_cleanup = True
|
||||
self.limit = limit
|
||||
self.log = logger.create()
|
||||
self.app_db_session = ub.get_new_session_instance()
|
||||
@ -186,7 +185,7 @@ class TaskGenerateCoverThumbnails(CalibreTask):
|
||||
|
||||
|
||||
class TaskCleanupCoverThumbnailCache(CalibreTask):
|
||||
def __init__(self, task_message=u'Validating cover thumbnail cache'):
|
||||
def __init__(self, task_message=u'Cleaning up cover thumbnail cache'):
|
||||
super(TaskCleanupCoverThumbnailCache, self).__init__(task_message)
|
||||
self.log = logger.create()
|
||||
self.app_db_session = ub.get_new_session_instance()
|
||||
@ -265,3 +264,58 @@ class TaskCleanupCoverThumbnailCache(CalibreTask):
|
||||
@property
|
||||
def name(self):
|
||||
return "CleanupCoverThumbnailCache"
|
||||
|
||||
|
||||
class TaskClearCoverThumbnailCache(CalibreTask):
|
||||
def __init__(self, book_id=None, task_message=u'Clearing cover thumbnail cache'):
|
||||
super(TaskClearCoverThumbnailCache, self).__init__(task_message)
|
||||
self.log = logger.create()
|
||||
self.book_id = book_id
|
||||
self.app_db_session = ub.get_new_session_instance()
|
||||
self.cache = fs.FileSystem()
|
||||
|
||||
def run(self, worker_thread):
|
||||
if self.app_db_session:
|
||||
if self.book_id:
|
||||
thumbnails = self.get_thumbnails_for_book(self.book_id)
|
||||
for thumbnail in thumbnails:
|
||||
self.expire_and_delete_thumbnail(thumbnail)
|
||||
else:
|
||||
self.expire_and_delete_all_thumbnails()
|
||||
|
||||
self._handleSuccess()
|
||||
self.app_db_session.remove()
|
||||
|
||||
def get_thumbnails_for_book(self, book_id):
|
||||
return self.app_db_session\
|
||||
.query(ub.Thumbnail)\
|
||||
.filter(ub.Thumbnail.book_id == book_id)\
|
||||
.all()
|
||||
|
||||
def expire_and_delete_thumbnail(self, thumbnail):
|
||||
thumbnail.expiration = datetime.utcnow()
|
||||
|
||||
try:
|
||||
self.app_db_session.commit()
|
||||
self.cache.delete_cache_file(thumbnail.filename, fs.CACHE_TYPE_THUMBNAILS)
|
||||
except Exception as ex:
|
||||
self.log.info(u'Error expiring book thumbnail: ' + str(ex))
|
||||
self._handleError(u'Error expiring book thumbnail: ' + str(ex))
|
||||
self.app_db_session.rollback()
|
||||
|
||||
def expire_and_delete_all_thumbnails(self):
|
||||
self.app_db_session\
|
||||
.query(ub.Thumbnail)\
|
||||
.update({'expiration': datetime.utcnow()})
|
||||
|
||||
try:
|
||||
self.app_db_session.commit()
|
||||
self.cache.delete_cache_dir(fs.CACHE_TYPE_THUMBNAILS)
|
||||
except Exception as ex:
|
||||
self.log.info(u'Error expiring book thumbnails: ' + str(ex))
|
||||
self._handleError(u'Error expiring book thumbnails: ' + str(ex))
|
||||
self.app_db_session.rollback()
|
||||
|
||||
@property
|
||||
def name(self):
|
||||
return "ClearCoverThumbnailCache"
|
||||
|
@ -146,6 +146,9 @@
|
||||
</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>
|
||||
<div class="btn btn-default" id="admin_stop" data-toggle="modal" data-target="#ShutdownDialog">{{_('Shutdown')}}</div>
|
||||
</div>
|
||||
@ -226,4 +229,21 @@
|
||||
</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 %}
|
||||
|
@ -36,7 +36,7 @@
|
||||
<div id="books" class="col-sm-3 col-lg-2 col-xs-6 book">
|
||||
<div class="cover">
|
||||
<a href="{{ url_for('web.show_book', book_id=entry.id) }}">
|
||||
{{ book_cover_image(entry.id, entry.title) }}
|
||||
{{ book_cover_image(entry, entry.title) }}
|
||||
</a>
|
||||
</div>
|
||||
<div class="meta">
|
||||
|
@ -1,8 +1,8 @@
|
||||
{% macro book_cover_image(book_id, book_title) -%}
|
||||
{% macro book_cover_image(book, book_title) -%}
|
||||
<img
|
||||
srcset="{{ url_for('web.get_cover', book_id=book_id, resolution=1) }} 1x,
|
||||
{{ url_for('web.get_cover', book_id=book_id, resolution=2) }} 2x"
|
||||
src="{{ url_for('web.get_cover', book_id=book_id) }}"
|
||||
srcset="{{ url_for('web.get_cached_cover', cache_id=book|book_cover_cache_id(1)) }} 1x,
|
||||
{{ url_for('web.get_cached_cover', cache_id=book|book_cover_cache_id(2)) }} 2x"
|
||||
src="{{ url_for('web.get_cached_cover', cache_id=book|book_cover_cache_id) }}"
|
||||
alt="{{ book_title }}"
|
||||
/>
|
||||
{%- endmacro %}
|
||||
|
@ -4,8 +4,7 @@
|
||||
{% if book %}
|
||||
<div class="col-sm-3 col-lg-3 col-xs-12">
|
||||
<div class="cover">
|
||||
{{ book_cover_image(book.id, book.title) }}
|
||||
<!-- <img src="{{ url_for('web.get_cover', book_id=book.id, edit=1|uuidfilter) }}" alt="{{ book.title }}"/>-->
|
||||
{{ book_cover_image(book, book.title) }}
|
||||
</div>
|
||||
{% if g.user.role_delete_books() %}
|
||||
<div class="text-center">
|
||||
|
@ -4,8 +4,7 @@
|
||||
<div class="row">
|
||||
<div class="col-sm-3 col-lg-3 col-xs-5">
|
||||
<div class="cover">
|
||||
{{ book_cover_image(entry.id, entry.title) }}
|
||||
<!-- <img src="{{ url_for('web.get_cover', book_id=entry.id, edit=1|uuidfilter) }}" alt="{{ entry.title }}" />-->
|
||||
{{ book_cover_image(entry, entry.title) }}
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-sm-9 col-lg-9 book-meta">
|
||||
|
@ -9,7 +9,7 @@
|
||||
<div class="cover">
|
||||
{% if entry.has_cover is defined %}
|
||||
<a href="{{ url_for('web.show_book', book_id=entry.id) }}" data-toggle="modal" data-target="#bookDetailsModal" data-remote="false">
|
||||
{{ book_cover_image(entry.id, entry.title) }}
|
||||
{{ book_cover_image(entry, entry.title) }}
|
||||
</a>
|
||||
{% endif %}
|
||||
</div>
|
||||
|
@ -29,7 +29,7 @@
|
||||
<div class="col-sm-3 col-lg-2 col-xs-6 book sortable" {% if entry[0].sort %}data-name="{{entry[0].series[0].name}}"{% endif %} data-id="{% if entry[0].series[0].name %}{{entry[0].series[0].name}}{% endif %}">
|
||||
<div class="cover">
|
||||
<a href="{{url_for('web.books_list', data=data, sort_param='new', book_id=entry[0].series[0].id )}}">
|
||||
{{ book_cover_image(entry[0].id, entry[0].name) }}
|
||||
{{ book_cover_image(entry[0], entry[0].name) }}
|
||||
<span class="badge">{{entry.count}}</span>
|
||||
</a>
|
||||
</div>
|
||||
|
@ -9,7 +9,7 @@
|
||||
<div class="col-sm-3 col-lg-2 col-xs-6 book" id="books_rand">
|
||||
<div class="cover">
|
||||
<a href="{{ url_for('web.show_book', book_id=entry.id) }}" data-toggle="modal" data-target="#bookDetailsModal" data-remote="false">
|
||||
{{ book_cover_image(entry.id, entry.title) }}
|
||||
{{ book_cover_image(entry, entry.title) }}
|
||||
</a>
|
||||
</div>
|
||||
<div class="meta">
|
||||
@ -83,7 +83,7 @@
|
||||
<div class="col-sm-3 col-lg-2 col-xs-6 book" id="books">
|
||||
<div class="cover">
|
||||
<a href="{{ url_for('web.show_book', book_id=entry.id) }}" data-toggle="modal" data-target="#bookDetailsModal" data-remote="false">
|
||||
{{ book_cover_image(entry.id, entry.title) }}
|
||||
{{ book_cover_image(entry, entry.title) }}
|
||||
</a>
|
||||
</div>
|
||||
<div class="meta">
|
||||
|
@ -44,7 +44,7 @@
|
||||
<div class="cover">
|
||||
{% if entry.has_cover is defined %}
|
||||
<a href="{{ url_for('web.show_book', book_id=entry.id) }}" data-toggle="modal" data-target="#bookDetailsModal" data-remote="false">
|
||||
{{ book_cover_image(entry.id, entry.title) }}
|
||||
{{ book_cover_image(entry, entry.title) }}
|
||||
</a>
|
||||
{% endif %}
|
||||
</div>
|
||||
|
@ -31,7 +31,7 @@
|
||||
<div class="col-sm-3 col-lg-2 col-xs-6 book">
|
||||
<div class="cover">
|
||||
<a href="{{ url_for('web.show_book', book_id=entry.id) }}" data-toggle="modal" data-target="#bookDetailsModal" data-remote="false">
|
||||
{{ book_cover_image(entry.id, entry.title) }}
|
||||
{{ book_cover_image(entry, entry.title) }}
|
||||
</a>
|
||||
</div>
|
||||
<div class="meta">
|
||||
|
@ -50,7 +50,7 @@ from . import babel, db, ub, config, get_locale, app
|
||||
from . import calibre_db, shelf
|
||||
from .gdriveutils import getFileFromEbooksFolder, do_gdrive_download
|
||||
from .helper import check_valid_domain, render_task_status, \
|
||||
get_cc_columns, get_book_cover, get_download_link, send_mail, generate_random_password, \
|
||||
get_cc_columns, get_book_cover, get_cached_book_cover, get_download_link, send_mail, generate_random_password, \
|
||||
send_registration_mail, check_send_to_kindle, check_read_formats, tags_filters, reset_password
|
||||
from .pagination import Pagination
|
||||
from .redirect import redirect_back
|
||||
@ -1177,6 +1177,12 @@ def get_cover(book_id, resolution=1):
|
||||
return get_book_cover(book_id, resolution)
|
||||
|
||||
|
||||
@web.route("/cached-cover/<string:cache_id>")
|
||||
@login_required_if_no_ano
|
||||
def get_cached_cover(cache_id):
|
||||
return get_cached_book_cover(cache_id)
|
||||
|
||||
|
||||
@web.route("/robots.txt")
|
||||
def get_robots():
|
||||
return send_from_directory(constants.STATIC_DIR, "robots.txt")
|
||||
|
Loading…
Reference in New Issue
Block a user