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.exc import IntegrityError, OperationalError, InvalidRequestError
|
||||||
from sqlalchemy.sql.expression import func, or_
|
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 . 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 .helper import check_valid_domain, send_test_mail, reset_password, generate_password_hash
|
||||||
from .gdriveutils import is_gdrive_ready, gdrive_support
|
from .gdriveutils import is_gdrive_ready, gdrive_support
|
||||||
|
@ -157,6 +157,23 @@ def shutdown():
|
||||||
return json.dumps(showtext), 400
|
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")
|
@admi.route("/admin/view")
|
||||||
@login_required
|
@login_required
|
||||||
@admin_required
|
@admin_required
|
||||||
|
|
|
@ -595,6 +595,7 @@ def upload_cover(request, book):
|
||||||
abort(403)
|
abort(403)
|
||||||
ret, message = helper.save_cover(requested_file, book.path)
|
ret, message = helper.save_cover(requested_file, book.path)
|
||||||
if ret is True:
|
if ret is True:
|
||||||
|
helper.clear_cover_thumbnail_cache(book.id)
|
||||||
return True
|
return True
|
||||||
else:
|
else:
|
||||||
flash(message, category="error")
|
flash(message, category="error")
|
||||||
|
@ -684,6 +685,7 @@ def edit_book(book_id):
|
||||||
if result is True:
|
if result is True:
|
||||||
book.has_cover = 1
|
book.has_cover = 1
|
||||||
modif_date = True
|
modif_date = True
|
||||||
|
helper.clear_cover_thumbnail_cache(book.id)
|
||||||
else:
|
else:
|
||||||
flash(error, category="error")
|
flash(error, category="error")
|
||||||
|
|
||||||
|
|
|
@ -58,6 +58,7 @@ from .constants import STATIC_DIR as _STATIC_DIR
|
||||||
from .subproc_wrapper import process_wait
|
from .subproc_wrapper import process_wait
|
||||||
from .services.worker import WorkerThread, STAT_WAITING, STAT_FAIL, STAT_STARTED, STAT_FINISH_SUCCESS
|
from .services.worker import WorkerThread, STAT_WAITING, STAT_FAIL, STAT_STARTED, STAT_FINISH_SUCCESS
|
||||||
from .tasks.mail import TaskEmail
|
from .tasks.mail import TaskEmail
|
||||||
|
from .tasks.thumbnail import TaskClearCoverThumbnailCache
|
||||||
|
|
||||||
log = logger.create()
|
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):
|
def delete_book(book, calibrepath, book_format):
|
||||||
|
clear_cover_thumbnail_cache(book.id)
|
||||||
if config.config_use_google_drive:
|
if config.config_use_google_drive:
|
||||||
return delete_book_gdrive(book, book_format)
|
return delete_book_gdrive(book, book_format)
|
||||||
else:
|
else:
|
||||||
|
@ -538,9 +540,9 @@ def get_cover_on_failure(use_generic_cover):
|
||||||
return None
|
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)
|
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):
|
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)
|
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:
|
if book and book.has_cover:
|
||||||
|
|
||||||
# Send the book cover thumbnail if it exists in cache
|
# Send the book cover thumbnail if it exists in cache
|
||||||
if not disable_thumbnail:
|
if resolution:
|
||||||
thumbnail = get_book_cover_thumbnail(book, resolution)
|
thumbnail = get_book_cover_thumbnail(book, resolution)
|
||||||
if thumbnail:
|
if thumbnail:
|
||||||
cache = fs.FileSystem()
|
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)
|
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:
|
if book and book.has_cover:
|
||||||
return ub.session\
|
return ub.session\
|
||||||
.query(ub.Thumbnail)\
|
.query(ub.Thumbnail)\
|
||||||
|
@ -846,3 +856,6 @@ def get_download_link(book_id, book_format, client):
|
||||||
else:
|
else:
|
||||||
abort(404)
|
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 series_index
|
||||||
return 0
|
return 0
|
||||||
|
|
||||||
|
|
||||||
@jinjia.app_template_filter('uuidfilter')
|
@jinjia.app_template_filter('uuidfilter')
|
||||||
def uuidfilter(var):
|
def uuidfilter(var):
|
||||||
return uuid4()
|
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 __future__ import division, print_function, unicode_literals
|
||||||
|
|
||||||
from . import config, db, logger, ub
|
|
||||||
from .services.background_scheduler import BackgroundScheduler
|
from .services.background_scheduler import BackgroundScheduler
|
||||||
|
from .tasks.database import TaskReconnectDatabase
|
||||||
from .tasks.thumbnail import TaskCleanupCoverThumbnailCache, TaskGenerateCoverThumbnails
|
from .tasks.thumbnail import TaskCleanupCoverThumbnailCache, TaskGenerateCoverThumbnails
|
||||||
|
|
||||||
log = logger.create()
|
|
||||||
|
|
||||||
|
|
||||||
def register_jobs():
|
def register_jobs():
|
||||||
scheduler = BackgroundScheduler()
|
scheduler = BackgroundScheduler()
|
||||||
|
@ -35,10 +33,4 @@ def register_jobs():
|
||||||
scheduler.add_task(user=None, task=lambda: TaskCleanupCoverThumbnailCache(), trigger='cron', hour=4)
|
scheduler.add_task(user=None, task=lambda: TaskCleanupCoverThumbnailCache(), trigger='cron', hour=4)
|
||||||
|
|
||||||
# Reconnect metadata.db every 4 hours
|
# Reconnect metadata.db every 4 hours
|
||||||
scheduler.add(func=reconnect_db_job, trigger='interval', hours=4)
|
scheduler.add_task(user=None, task=lambda: TaskReconnectDatabase(), 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)
|
|
||||||
|
|
|
@ -5167,7 +5167,7 @@ body.login > div.navbar.navbar-default.navbar-static-top > div > div.navbar-head
|
||||||
pointer-events: none
|
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
|
cursor: pointer
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -5254,7 +5254,7 @@ body.admin > div.container-fluid > div > div.col-sm-10 > div.container-fluid > d
|
||||||
margin-bottom: 20px
|
margin-bottom: 20px
|
||||||
}
|
}
|
||||||
|
|
||||||
body.admin:not(.modal-open) .btn-default {
|
body.admin .btn-default {
|
||||||
margin-bottom: 10px
|
margin-bottom: 10px
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -5485,7 +5485,7 @@ body.admin.modal-open .navbar {
|
||||||
z-index: 0 !important
|
z-index: 0 !important
|
||||||
}
|
}
|
||||||
|
|
||||||
#RestartDialog, #ShutdownDialog, #StatusDialog, #deleteModal {
|
#RestartDialog, #ShutdownDialog, #StatusDialog, #ClearCacheDialog, #deleteModal {
|
||||||
top: 0;
|
top: 0;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
padding-top: 70px;
|
padding-top: 70px;
|
||||||
|
@ -5495,7 +5495,7 @@ body.admin.modal-open .navbar {
|
||||||
background: rgba(0, 0, 0, .5)
|
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";
|
content: "\E208";
|
||||||
padding-right: 10px;
|
padding-right: 10px;
|
||||||
display: block;
|
display: block;
|
||||||
|
@ -5517,18 +5517,18 @@ body.admin.modal-open .navbar {
|
||||||
z-index: 99
|
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);
|
-webkit-transform: translate(0, 0);
|
||||||
-ms-transform: translate(0, 0);
|
-ms-transform: translate(0, 0);
|
||||||
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;
|
width: 450px;
|
||||||
margin: auto
|
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);
|
max-height: calc(100% - 90px);
|
||||||
-webkit-box-shadow: 0 5px 15px rgba(0, 0, 0, .5);
|
-webkit-box-shadow: 0 5px 15px rgba(0, 0, 0, .5);
|
||||||
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
|
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;
|
padding: 15px 20px;
|
||||||
border-radius: 3px 3px 0 0;
|
border-radius: 3px 3px 0 0;
|
||||||
line-height: 1.71428571;
|
line-height: 1.71428571;
|
||||||
|
@ -5552,7 +5552,7 @@ body.admin.modal-open .navbar {
|
||||||
text-align: left
|
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;
|
padding-right: 10px;
|
||||||
font-size: 18px;
|
font-size: 18px;
|
||||||
color: #999;
|
color: #999;
|
||||||
|
@ -5576,6 +5576,11 @@ body.admin.modal-open .navbar {
|
||||||
font-family: plex-icons-new, serif
|
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 {
|
#deleteModal > .modal-dialog > .modal-content > .modal-header:before {
|
||||||
content: "\EA6D";
|
content: "\EA6D";
|
||||||
font-family: plex-icons-new, serif
|
font-family: plex-icons-new, serif
|
||||||
|
@ -5599,6 +5604,12 @@ body.admin.modal-open .navbar {
|
||||||
font-size: 20px
|
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 {
|
#deleteModal > .modal-dialog > .modal-content > .modal-header:after {
|
||||||
content: "Delete Book";
|
content: "Delete Book";
|
||||||
display: inline-block;
|
display: inline-block;
|
||||||
|
@ -5629,7 +5640,17 @@ body.admin.modal-open .navbar {
|
||||||
text-align: left
|
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;
|
padding: 20px 20px 0 0;
|
||||||
font-size: 16px;
|
font-size: 16px;
|
||||||
line-height: 1.6em;
|
line-height: 1.6em;
|
||||||
|
@ -5638,7 +5659,7 @@ body.admin.modal-open .navbar {
|
||||||
background: #282828
|
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;
|
float: right;
|
||||||
z-index: 9;
|
z-index: 9;
|
||||||
position: relative;
|
position: relative;
|
||||||
|
@ -5674,6 +5695,18 @@ body.admin.modal-open .navbar {
|
||||||
border-radius: 3px
|
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 {
|
#deleteModal > .modal-dialog > .modal-content > .modal-footer > .btn-danger {
|
||||||
float: right;
|
float: right;
|
||||||
z-index: 9;
|
z-index: 9;
|
||||||
|
@ -5694,11 +5727,15 @@ body.admin.modal-open .navbar {
|
||||||
margin: 55px 0 0 10px
|
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 {
|
#deleteModal > .modal-dialog > .modal-content > .modal-footer > .btn-default {
|
||||||
margin: 0 0 0 10px
|
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)
|
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)
|
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 {
|
#deleteButton {
|
||||||
position: fixed;
|
position: fixed;
|
||||||
top: 60px;
|
top: 60px;
|
||||||
|
@ -7322,11 +7374,11 @@ body.edituser.admin > div.container-fluid > div.row-fluid > div.col-sm-10 > div.
|
||||||
background-color: transparent !important
|
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)
|
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);
|
max-width: calc(100vw - 40px);
|
||||||
left: 0
|
left: 0
|
||||||
}
|
}
|
||||||
|
@ -7476,7 +7528,7 @@ body.edituser.admin > div.container-fluid > div.row-fluid > div.col-sm-10 > div.
|
||||||
padding: 30px 15px
|
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;
|
left: auto;
|
||||||
right: 34px
|
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
|
// Init all data control handlers to default
|
||||||
$("input[data-control]").trigger("change");
|
$("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):
|
class TaskGenerateCoverThumbnails(CalibreTask):
|
||||||
def __init__(self, limit=100, task_message=u'Generating cover thumbnails'):
|
def __init__(self, limit=100, task_message=u'Generating cover thumbnails'):
|
||||||
super(TaskGenerateCoverThumbnails, self).__init__(task_message)
|
super(TaskGenerateCoverThumbnails, self).__init__(task_message)
|
||||||
self.self_cleanup = True
|
|
||||||
self.limit = limit
|
self.limit = limit
|
||||||
self.log = logger.create()
|
self.log = logger.create()
|
||||||
self.app_db_session = ub.get_new_session_instance()
|
self.app_db_session = ub.get_new_session_instance()
|
||||||
|
@ -186,7 +185,7 @@ class TaskGenerateCoverThumbnails(CalibreTask):
|
||||||
|
|
||||||
|
|
||||||
class TaskCleanupCoverThumbnailCache(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)
|
super(TaskCleanupCoverThumbnailCache, self).__init__(task_message)
|
||||||
self.log = logger.create()
|
self.log = logger.create()
|
||||||
self.app_db_session = ub.get_new_session_instance()
|
self.app_db_session = ub.get_new_session_instance()
|
||||||
|
@ -265,3 +264,58 @@ class TaskCleanupCoverThumbnailCache(CalibreTask):
|
||||||
@property
|
@property
|
||||||
def name(self):
|
def name(self):
|
||||||
return "CleanupCoverThumbnailCache"
|
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"
|
||||||
|
|
|
@ -139,15 +139,18 @@
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="row form-group">
|
<div class="row form-group">
|
||||||
<h2>{{_('Administration')}}</h2>
|
<h2>{{_('Administration')}}</h2>
|
||||||
<div class="btn btn-default"><a id="debug" href="{{url_for('admin.download_debug')}}">{{_('Download Debug Package')}}</a></div>
|
<div class="btn btn-default"><a id="debug" href="{{url_for('admin.download_debug')}}">{{_('Download Debug Package')}}</a></div>
|
||||||
<div class="btn btn-default"><a id="logfile" href="{{url_for('admin.view_logfile')}}">{{_('View Logs')}}</a></div>
|
<div class="btn btn-default"><a id="logfile" href="{{url_for('admin.view_logfile')}}">{{_('View Logs')}}</a></div>
|
||||||
</div>
|
</div>
|
||||||
<div class="row form-group">
|
<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="restart_database" data-toggle="modal" data-target="#StatusDialog">{{_('Reconnect Calibre Database')}}</div>
|
||||||
<div class="btn btn-default" id="admin_restart" data-toggle="modal" data-target="#RestartDialog">{{_('Restart')}}</div>
|
<div class="btn btn-default" id="clear_cover_thumbnail_cache" data-toggle="modal" data-target="#ClearCacheDialog">{{_('Clear Cover Thumbnail Cache')}}</div>
|
||||||
<div class="btn btn-default" id="admin_stop" data-toggle="modal" data-target="#ShutdownDialog">{{_('Shutdown')}}</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>
|
</div>
|
||||||
|
|
||||||
<div class="row">
|
<div class="row">
|
||||||
|
@ -226,4 +229,21 @@
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</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 %}
|
{% endblock %}
|
||||||
|
|
|
@ -36,7 +36,7 @@
|
||||||
<div id="books" class="col-sm-3 col-lg-2 col-xs-6 book">
|
<div id="books" class="col-sm-3 col-lg-2 col-xs-6 book">
|
||||||
<div class="cover">
|
<div class="cover">
|
||||||
<a href="{{ url_for('web.show_book', book_id=entry.id) }}">
|
<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>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
<div class="meta">
|
<div class="meta">
|
||||||
|
|
|
@ -1,8 +1,8 @@
|
||||||
{% macro book_cover_image(book_id, book_title) -%}
|
{% macro book_cover_image(book, book_title) -%}
|
||||||
<img
|
<img
|
||||||
srcset="{{ url_for('web.get_cover', book_id=book_id, resolution=1) }} 1x,
|
srcset="{{ url_for('web.get_cached_cover', cache_id=book|book_cover_cache_id(1)) }} 1x,
|
||||||
{{ url_for('web.get_cover', book_id=book_id, resolution=2) }} 2x"
|
{{ url_for('web.get_cached_cover', cache_id=book|book_cover_cache_id(2)) }} 2x"
|
||||||
src="{{ url_for('web.get_cover', book_id=book_id) }}"
|
src="{{ url_for('web.get_cached_cover', cache_id=book|book_cover_cache_id) }}"
|
||||||
alt="{{ book_title }}"
|
alt="{{ book_title }}"
|
||||||
/>
|
/>
|
||||||
{%- endmacro %}
|
{%- endmacro %}
|
||||||
|
|
|
@ -4,8 +4,7 @@
|
||||||
{% if book %}
|
{% if book %}
|
||||||
<div class="col-sm-3 col-lg-3 col-xs-12">
|
<div class="col-sm-3 col-lg-3 col-xs-12">
|
||||||
<div class="cover">
|
<div class="cover">
|
||||||
{{ book_cover_image(book.id, book.title) }}
|
{{ book_cover_image(book, book.title) }}
|
||||||
<!-- <img src="{{ url_for('web.get_cover', book_id=book.id, edit=1|uuidfilter) }}" alt="{{ book.title }}"/>-->
|
|
||||||
</div>
|
</div>
|
||||||
{% if g.user.role_delete_books() %}
|
{% if g.user.role_delete_books() %}
|
||||||
<div class="text-center">
|
<div class="text-center">
|
||||||
|
|
|
@ -4,8 +4,7 @@
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<div class="col-sm-3 col-lg-3 col-xs-5">
|
<div class="col-sm-3 col-lg-3 col-xs-5">
|
||||||
<div class="cover">
|
<div class="cover">
|
||||||
{{ book_cover_image(entry.id, entry.title) }}
|
{{ book_cover_image(entry, entry.title) }}
|
||||||
<!-- <img src="{{ url_for('web.get_cover', book_id=entry.id, edit=1|uuidfilter) }}" alt="{{ entry.title }}" />-->
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="col-sm-9 col-lg-9 book-meta">
|
<div class="col-sm-9 col-lg-9 book-meta">
|
||||||
|
|
|
@ -9,7 +9,7 @@
|
||||||
<div class="cover">
|
<div class="cover">
|
||||||
{% if entry.has_cover is defined %}
|
{% 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">
|
<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>
|
</a>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</div>
|
</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="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">
|
<div class="cover">
|
||||||
<a href="{{url_for('web.books_list', data=data, sort_param='new', book_id=entry[0].series[0].id )}}">
|
<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>
|
<span class="badge">{{entry.count}}</span>
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -9,7 +9,7 @@
|
||||||
<div class="col-sm-3 col-lg-2 col-xs-6 book" id="books_rand">
|
<div class="col-sm-3 col-lg-2 col-xs-6 book" id="books_rand">
|
||||||
<div class="cover">
|
<div class="cover">
|
||||||
<a href="{{ url_for('web.show_book', book_id=entry.id) }}" data-toggle="modal" data-target="#bookDetailsModal" data-remote="false">
|
<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>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
<div class="meta">
|
<div class="meta">
|
||||||
|
@ -83,7 +83,7 @@
|
||||||
<div class="col-sm-3 col-lg-2 col-xs-6 book" id="books">
|
<div class="col-sm-3 col-lg-2 col-xs-6 book" id="books">
|
||||||
<div class="cover">
|
<div class="cover">
|
||||||
<a href="{{ url_for('web.show_book', book_id=entry.id) }}" data-toggle="modal" data-target="#bookDetailsModal" data-remote="false">
|
<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>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
<div class="meta">
|
<div class="meta">
|
||||||
|
|
|
@ -44,7 +44,7 @@
|
||||||
<div class="cover">
|
<div class="cover">
|
||||||
{% if entry.has_cover is defined %}
|
{% 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">
|
<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>
|
</a>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -31,7 +31,7 @@
|
||||||
<div class="col-sm-3 col-lg-2 col-xs-6 book">
|
<div class="col-sm-3 col-lg-2 col-xs-6 book">
|
||||||
<div class="cover">
|
<div class="cover">
|
||||||
<a href="{{ url_for('web.show_book', book_id=entry.id) }}" data-toggle="modal" data-target="#bookDetailsModal" data-remote="false">
|
<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>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
<div class="meta">
|
<div class="meta">
|
||||||
|
|
|
@ -50,7 +50,7 @@ from . import babel, db, ub, config, get_locale, app
|
||||||
from . import calibre_db, shelf
|
from . import calibre_db, shelf
|
||||||
from .gdriveutils import getFileFromEbooksFolder, do_gdrive_download
|
from .gdriveutils import getFileFromEbooksFolder, do_gdrive_download
|
||||||
from .helper import check_valid_domain, render_task_status, \
|
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
|
send_registration_mail, check_send_to_kindle, check_read_formats, tags_filters, reset_password
|
||||||
from .pagination import Pagination
|
from .pagination import Pagination
|
||||||
from .redirect import redirect_back
|
from .redirect import redirect_back
|
||||||
|
@ -1177,6 +1177,12 @@ def get_cover(book_id, resolution=1):
|
||||||
return get_book_cover(book_id, resolution)
|
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")
|
@web.route("/robots.txt")
|
||||||
def get_robots():
|
def get_robots():
|
||||||
return send_from_directory(constants.STATIC_DIR, "robots.txt")
|
return send_from_directory(constants.STATIC_DIR, "robots.txt")
|
||||||
|
|
Loading…
Reference in New Issue
Block a user