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