mirror of
https://github.com/janeczku/calibre-web
synced 2024-12-18 14:10:30 +00:00
Simplified all of the thumbnail generation and loading.
This commit is contained in:
parent
524ed07a6c
commit
be28a91315
14
cps/fs.py
14
cps/fs.py
@ -57,17 +57,9 @@ class FileSystem:
|
||||
def get_cache_file_path(self, filename, cache_type=None):
|
||||
return join(self.get_cache_dir(cache_type), filename) if filename else None
|
||||
|
||||
def list_cache_files(self, cache_type=None):
|
||||
path = self.get_cache_dir(cache_type)
|
||||
return [file for file in listdir(path) if isfile(join(path, file))]
|
||||
|
||||
def list_existing_cache_files(self, filenames, cache_type=None):
|
||||
path = self.get_cache_dir(cache_type)
|
||||
return [file for file in listdir(path) if isfile(join(path, file)) and file in filenames]
|
||||
|
||||
def list_missing_cache_files(self, filenames, cache_type=None):
|
||||
path = self.get_cache_dir(cache_type)
|
||||
return [file for file in listdir(path) if isfile(join(path, file)) and file not in filenames]
|
||||
def get_cache_file_exists(self, filename, cache_type=None):
|
||||
path = self.get_cache_file_path(filename, cache_type)
|
||||
return isfile(path)
|
||||
|
||||
def delete_cache_dir(self, cache_type=None):
|
||||
if not cache_type and isdir(self._cache_dir):
|
||||
|
@ -35,7 +35,7 @@ from babel.units import format_unit
|
||||
from flask import send_from_directory, make_response, redirect, abort, url_for
|
||||
from flask_babel import gettext as _
|
||||
from flask_login import current_user
|
||||
from sqlalchemy.sql.expression import true, false, and_, text, func
|
||||
from sqlalchemy.sql.expression import true, false, and_, or_, text, func
|
||||
from werkzeug.datastructures import Headers
|
||||
from werkzeug.security import generate_password_hash
|
||||
from markupsafe import escape
|
||||
@ -550,26 +550,6 @@ def delete_book(book, calibrepath, book_format):
|
||||
return delete_book_file(book, calibrepath, book_format)
|
||||
|
||||
|
||||
def get_thumbnails_for_books(books):
|
||||
books_with_covers = list(filter(lambda b: b.has_cover, books))
|
||||
book_ids = list(map(lambda b: b.id, books_with_covers))
|
||||
cache = fs.FileSystem()
|
||||
thumbnail_files = cache.list_cache_files(fs.CACHE_TYPE_THUMBNAILS)
|
||||
|
||||
thumbnails = ub.session\
|
||||
.query(ub.Thumbnail)\
|
||||
.filter(ub.Thumbnail.book_id.in_(book_ids))\
|
||||
.filter(ub.Thumbnail.expiration > datetime.utcnow())\
|
||||
.all()
|
||||
|
||||
return list(filter(lambda t: t.filename in thumbnail_files, thumbnails))
|
||||
|
||||
|
||||
def get_thumbnails_for_book_series(series):
|
||||
books = list(map(lambda s: s[0], series))
|
||||
return get_thumbnails_for_books(books)
|
||||
|
||||
|
||||
def get_cover_on_failure(use_generic_cover):
|
||||
if use_generic_cover:
|
||||
return send_from_directory(_STATIC_DIR, "generic_cover.jpg")
|
||||
@ -577,9 +557,9 @@ def get_cover_on_failure(use_generic_cover):
|
||||
return None
|
||||
|
||||
|
||||
def get_book_cover(book_id):
|
||||
def get_book_cover(book_id, resolution=None):
|
||||
book = calibre_db.get_filtered_book(book_id, allow_show_archived=True)
|
||||
return get_book_cover_internal(book, use_generic_cover_on_failure=True)
|
||||
return get_book_cover_internal(book, use_generic_cover_on_failure=True, resolution=resolution)
|
||||
|
||||
|
||||
def get_book_cover_with_uuid(book_uuid, use_generic_cover_on_failure=True):
|
||||
@ -587,37 +567,6 @@ def get_book_cover_with_uuid(book_uuid, use_generic_cover_on_failure=True):
|
||||
return get_book_cover_internal(book, use_generic_cover_on_failure)
|
||||
|
||||
|
||||
def get_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_cached_book_cover_thumbnail(cache_id):
|
||||
parts = cache_id.split('_')
|
||||
thumbnail_uuid = parts[0] if len(parts) else None
|
||||
thumbnail = None
|
||||
if thumbnail_uuid:
|
||||
thumbnail = ub.session\
|
||||
.query(ub.Thumbnail)\
|
||||
.filter(ub.Thumbnail.uuid == thumbnail_uuid)\
|
||||
.first()
|
||||
|
||||
if thumbnail and thumbnail.expiration > datetime.utcnow():
|
||||
cache = fs.FileSystem()
|
||||
if cache.get_cache_file_path(thumbnail.filename, fs.CACHE_TYPE_THUMBNAILS):
|
||||
return send_from_directory(cache.get_cache_dir(fs.CACHE_TYPE_THUMBNAILS), thumbnail.filename)
|
||||
|
||||
elif thumbnail:
|
||||
book = calibre_db.get_book(thumbnail.book_id)
|
||||
return get_book_cover_internal(book, use_generic_cover_on_failure=True)
|
||||
|
||||
else:
|
||||
return get_cover_on_failure(True)
|
||||
|
||||
|
||||
def get_book_cover_internal(book, use_generic_cover_on_failure, resolution=None):
|
||||
if book and book.has_cover:
|
||||
|
||||
@ -626,7 +575,7 @@ def get_book_cover_internal(book, use_generic_cover_on_failure, resolution=None)
|
||||
thumbnail = get_book_cover_thumbnail(book, resolution)
|
||||
if thumbnail:
|
||||
cache = fs.FileSystem()
|
||||
if cache.get_cache_file_path(thumbnail.filename, fs.CACHE_TYPE_THUMBNAILS):
|
||||
if cache.get_cache_file_exists(thumbnail.filename, fs.CACHE_TYPE_THUMBNAILS):
|
||||
return send_from_directory(cache.get_cache_dir(fs.CACHE_TYPE_THUMBNAILS), thumbnail.filename)
|
||||
|
||||
# Send the book cover from Google Drive if configured
|
||||
@ -661,7 +610,7 @@ def get_book_cover_thumbnail(book, resolution):
|
||||
.query(ub.Thumbnail)\
|
||||
.filter(ub.Thumbnail.book_id == book.id)\
|
||||
.filter(ub.Thumbnail.resolution == resolution)\
|
||||
.filter(ub.Thumbnail.expiration > datetime.utcnow())\
|
||||
.filter(or_(ub.Thumbnail.expiration.is_(None), ub.Thumbnail.expiration > datetime.utcnow()))\
|
||||
.first()
|
||||
|
||||
|
||||
|
@ -33,6 +33,7 @@ from flask_babel import get_locale
|
||||
from flask_login import current_user
|
||||
from markupsafe import escape
|
||||
from . import logger
|
||||
from .tasks.thumbnail import THUMBNAIL_RESOLUTION_1X, THUMBNAIL_RESOLUTION_2X, THUMBNAIL_RESOLUTION_3X
|
||||
|
||||
|
||||
jinjia = Blueprint('jinjia', __name__)
|
||||
@ -140,24 +141,17 @@ def uuidfilter(var):
|
||||
return uuid4()
|
||||
|
||||
|
||||
@jinjia.app_template_filter('book_cover_cache_id')
|
||||
def book_cover_cache_id(book, resolution=None):
|
||||
@jinjia.app_template_filter('last_modified')
|
||||
def book_cover_cache_id(book):
|
||||
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)
|
||||
return str(timestamp)
|
||||
|
||||
|
||||
@jinjia.app_template_filter('get_book_thumbnails')
|
||||
def get_book_thumbnails(book_id, thumbnails=None):
|
||||
return list(filter(lambda t: t.book_id == book_id, thumbnails)) if book_id > -1 and thumbnails else list()
|
||||
|
||||
|
||||
@jinjia.app_template_filter('get_book_thumbnail_srcset')
|
||||
def get_book_thumbnail_srcset(thumbnails):
|
||||
@jinjia.app_template_filter('get_cover_srcset')
|
||||
def get_cover_srcset(book):
|
||||
srcset = list()
|
||||
for thumbnail in thumbnails:
|
||||
timestamp = int(thumbnail.generated_at.timestamp() * 1000)
|
||||
cache_id = str(thumbnail.uuid) + '_' + str(timestamp)
|
||||
url = url_for('web.get_cached_cover_thumbnail', cache_id=cache_id)
|
||||
srcset.append(url + ' ' + str(thumbnail.resolution) + 'x')
|
||||
for resolution in [THUMBNAIL_RESOLUTION_1X, THUMBNAIL_RESOLUTION_2X, THUMBNAIL_RESOLUTION_3X]:
|
||||
timestamp = int(book.last_modified.timestamp() * 1000)
|
||||
url = url_for('web.get_cover', book_id=book.id, resolution=resolution, cache_bust=str(timestamp))
|
||||
srcset.append(f'{url} {resolution}x')
|
||||
return ', '.join(srcset)
|
||||
|
@ -21,7 +21,7 @@ from __future__ import division, print_function, unicode_literals
|
||||
from .services.background_scheduler import BackgroundScheduler
|
||||
from .services.worker import WorkerThread
|
||||
from .tasks.database import TaskReconnectDatabase
|
||||
from .tasks.thumbnail import TaskSyncCoverThumbnailCache, TaskGenerateCoverThumbnails
|
||||
from .tasks.thumbnail import TaskGenerateCoverThumbnails
|
||||
|
||||
|
||||
def register_jobs():
|
||||
@ -31,9 +31,6 @@ def register_jobs():
|
||||
# Reconnect metadata.db once every 12 hours
|
||||
scheduler.add_task(user=None, task=lambda: TaskReconnectDatabase(), trigger='cron', hour='4,16')
|
||||
|
||||
# Cleanup book cover cache once every 24 hours
|
||||
scheduler.add_task(user=None, task=lambda: TaskSyncCoverThumbnailCache(), trigger='cron', hour=4)
|
||||
|
||||
# Generate all missing book cover thumbnails once every 24 hours
|
||||
scheduler.add_task(user=None, task=lambda: TaskGenerateCoverThumbnails(), trigger='cron', hour=4)
|
||||
|
||||
|
@ -22,7 +22,7 @@ import os
|
||||
from cps import config, db, fs, gdriveutils, logger, ub
|
||||
from cps.services.worker import CalibreTask
|
||||
from datetime import datetime, timedelta
|
||||
from sqlalchemy import func
|
||||
from sqlalchemy import or_
|
||||
|
||||
try:
|
||||
from urllib.request import urlopen
|
||||
@ -41,9 +41,8 @@ THUMBNAIL_RESOLUTION_3X = 3
|
||||
|
||||
|
||||
class TaskGenerateCoverThumbnails(CalibreTask):
|
||||
def __init__(self, limit=100, task_message=u'Generating cover thumbnails'):
|
||||
def __init__(self, task_message=u'Generating cover thumbnails'):
|
||||
super(TaskGenerateCoverThumbnails, self).__init__(task_message)
|
||||
self.limit = limit
|
||||
self.log = logger.create()
|
||||
self.app_db_session = ub.get_new_session_instance()
|
||||
self.calibre_db = db.CalibreDB(expire_on_commit=False)
|
||||
@ -55,74 +54,51 @@ class TaskGenerateCoverThumbnails(CalibreTask):
|
||||
|
||||
def run(self, worker_thread):
|
||||
if self.calibre_db.session and use_IM:
|
||||
expired_thumbnails = self.get_expired_thumbnails()
|
||||
thumbnail_book_ids = self.get_thumbnail_book_ids()
|
||||
books_without_thumbnails = self.get_books_without_thumbnails(thumbnail_book_ids)
|
||||
books_with_covers = self.get_books_with_covers()
|
||||
count = len(books_with_covers)
|
||||
|
||||
count = len(books_without_thumbnails)
|
||||
if count == 0:
|
||||
# Do not display this task on the frontend if there are no covers to update
|
||||
self.self_cleanup = True
|
||||
updated = 0
|
||||
generated = 0
|
||||
for i, book in enumerate(books_with_covers):
|
||||
book_cover_thumbnails = self.get_book_cover_thumbnails(book.id)
|
||||
|
||||
for i, book in enumerate(books_without_thumbnails):
|
||||
for resolution in self.resolutions:
|
||||
expired_thumbnail = self.get_expired_thumbnail_for_book_and_resolution(
|
||||
book,
|
||||
resolution,
|
||||
expired_thumbnails
|
||||
)
|
||||
if expired_thumbnail:
|
||||
self.update_book_thumbnail(book, expired_thumbnail)
|
||||
else:
|
||||
self.create_book_thumbnail(book, resolution)
|
||||
# Generate new thumbnails for missing covers
|
||||
resolutions = list(map(lambda t: t.resolution, book_cover_thumbnails))
|
||||
missing_resolutions = list(set(self.resolutions).difference(resolutions))
|
||||
for resolution in missing_resolutions:
|
||||
generated += 1
|
||||
self.create_book_cover_thumbnail(book, resolution)
|
||||
|
||||
self.message = u'Generating cover thumbnail {0} of {1}'.format(i + 1, count)
|
||||
# Replace outdated or missing thumbnails
|
||||
for thumbnail in book_cover_thumbnails:
|
||||
if book.last_modified > thumbnail.generated_at:
|
||||
updated += 1
|
||||
self.update_book_cover_thumbnail(book, thumbnail)
|
||||
|
||||
elif not self.cache.get_cache_file_exists(thumbnail.filename, fs.CACHE_TYPE_THUMBNAILS):
|
||||
updated += 1
|
||||
self.update_book_cover_thumbnail(book, thumbnail)
|
||||
|
||||
self.message = u'Processing book {0} of {1}'.format(i + 1, count)
|
||||
self.progress = (1.0 / count) * i
|
||||
|
||||
self._handleSuccess()
|
||||
self.app_db_session.remove()
|
||||
|
||||
def get_expired_thumbnails(self):
|
||||
return self.app_db_session\
|
||||
.query(ub.Thumbnail)\
|
||||
.filter(ub.Thumbnail.expiration < datetime.utcnow())\
|
||||
.all()
|
||||
|
||||
def get_thumbnail_book_ids(self):
|
||||
return self.app_db_session\
|
||||
.query(ub.Thumbnail.book_id)\
|
||||
.group_by(ub.Thumbnail.book_id)\
|
||||
.having(func.min(ub.Thumbnail.expiration) > datetime.utcnow())\
|
||||
.distinct()
|
||||
|
||||
def get_books_without_thumbnails(self, thumbnail_book_ids):
|
||||
def get_books_with_covers(self):
|
||||
return self.calibre_db.session\
|
||||
.query(db.Books)\
|
||||
.filter(db.Books.has_cover == 1)\
|
||||
.filter(db.Books.id.notin_(thumbnail_book_ids))\
|
||||
.limit(self.limit)\
|
||||
.all()
|
||||
|
||||
def get_expired_thumbnail_for_book_and_resolution(self, book, resolution, expired_thumbnails):
|
||||
for thumbnail in expired_thumbnails:
|
||||
if thumbnail.book_id == book.id and thumbnail.resolution == resolution:
|
||||
return thumbnail
|
||||
def get_book_cover_thumbnails(self, book_id):
|
||||
return self.app_db_session\
|
||||
.query(ub.Thumbnail)\
|
||||
.filter(ub.Thumbnail.book_id == book_id)\
|
||||
.filter(or_(ub.Thumbnail.expiration.is_(None), ub.Thumbnail.expiration > datetime.utcnow()))\
|
||||
.all()
|
||||
|
||||
return None
|
||||
|
||||
def update_book_thumbnail(self, book, thumbnail):
|
||||
thumbnail.generated_at = datetime.utcnow()
|
||||
thumbnail.expiration = datetime.utcnow() + timedelta(days=30)
|
||||
|
||||
try:
|
||||
self.app_db_session.commit()
|
||||
self.generate_book_thumbnail(book, thumbnail)
|
||||
except Exception as ex:
|
||||
self.log.info(u'Error updating book thumbnail: ' + str(ex))
|
||||
self._handleError(u'Error updating book thumbnail: ' + str(ex))
|
||||
self.app_db_session.rollback()
|
||||
|
||||
def create_book_thumbnail(self, book, resolution):
|
||||
def create_book_cover_thumbnail(self, book, resolution):
|
||||
thumbnail = ub.Thumbnail()
|
||||
thumbnail.book_id = book.id
|
||||
thumbnail.format = 'jpeg'
|
||||
@ -137,6 +113,18 @@ class TaskGenerateCoverThumbnails(CalibreTask):
|
||||
self._handleError(u'Error creating book thumbnail: ' + str(ex))
|
||||
self.app_db_session.rollback()
|
||||
|
||||
def update_book_cover_thumbnail(self, book, thumbnail):
|
||||
thumbnail.generated_at = datetime.utcnow()
|
||||
|
||||
try:
|
||||
self.app_db_session.commit()
|
||||
self.cache.delete_cache_file(thumbnail.filename, fs.CACHE_TYPE_THUMBNAILS)
|
||||
self.generate_book_thumbnail(book, thumbnail)
|
||||
except Exception as ex:
|
||||
self.log.info(u'Error updating book thumbnail: ' + str(ex))
|
||||
self._handleError(u'Error updating book thumbnail: ' + str(ex))
|
||||
self.app_db_session.rollback()
|
||||
|
||||
def generate_book_thumbnail(self, book, thumbnail):
|
||||
if book and thumbnail:
|
||||
if config.config_use_google_drive:
|
||||
@ -190,128 +178,6 @@ class TaskGenerateCoverThumbnails(CalibreTask):
|
||||
return "ThumbnailsGenerate"
|
||||
|
||||
|
||||
class TaskSyncCoverThumbnailCache(CalibreTask):
|
||||
def __init__(self, task_message=u'Syncing cover thumbnail cache'):
|
||||
super(TaskSyncCoverThumbnailCache, self).__init__(task_message)
|
||||
self.log = logger.create()
|
||||
self.app_db_session = ub.get_new_session_instance()
|
||||
self.calibre_db = db.CalibreDB(expire_on_commit=False)
|
||||
self.cache = fs.FileSystem()
|
||||
|
||||
def run(self, worker_thread):
|
||||
cached_thumbnail_files = self.cache.list_cache_files(fs.CACHE_TYPE_THUMBNAILS)
|
||||
|
||||
# Expire thumbnails in the database if the cached file is missing
|
||||
# This case will happen if a user deletes the cache dir or cached files
|
||||
if self.app_db_session:
|
||||
self.expire_missing_thumbnails(cached_thumbnail_files)
|
||||
self.progress = 0.25
|
||||
|
||||
# Delete thumbnails in the database if the book has been removed
|
||||
# This case will happen if a book is removed in Calibre and the metadata.db file is updated in the filesystem
|
||||
if self.app_db_session and self.calibre_db:
|
||||
book_ids = self.get_book_ids()
|
||||
self.delete_thumbnails_for_missing_books(book_ids)
|
||||
self.progress = 0.50
|
||||
|
||||
# Expire thumbnails in the database if their corresponding book has been updated since they were generated
|
||||
# This case will happen if the book was updated externally
|
||||
if self.app_db_session and self.cache:
|
||||
books = self.get_books_updated_in_the_last_day()
|
||||
book_ids = list(map(lambda b: b.id, books))
|
||||
thumbnails = self.get_thumbnails_for_updated_books(book_ids)
|
||||
self.expire_thumbnails_for_updated_book(books, thumbnails)
|
||||
self.progress = 0.75
|
||||
|
||||
# Delete extraneous cached thumbnail files
|
||||
# This case will happen if a book was deleted and the thumbnail OR the metadata.db file was changed externally
|
||||
if self.app_db_session:
|
||||
db_thumbnail_files = self.get_thumbnail_filenames()
|
||||
self.delete_extraneous_thumbnail_files(cached_thumbnail_files, db_thumbnail_files)
|
||||
|
||||
self._handleSuccess()
|
||||
self.app_db_session.remove()
|
||||
|
||||
def expire_missing_thumbnails(self, filenames):
|
||||
try:
|
||||
self.app_db_session\
|
||||
.query(ub.Thumbnail)\
|
||||
.filter(ub.Thumbnail.filename.notin_(filenames))\
|
||||
.update({"expiration": datetime.utcnow()}, synchronize_session=False)
|
||||
self.app_db_session.commit()
|
||||
except Exception as ex:
|
||||
self.log.info(u'Error expiring thumbnails for missing cache files: ' + str(ex))
|
||||
self._handleError(u'Error expiring thumbnails for missing cache files: ' + str(ex))
|
||||
self.app_db_session.rollback()
|
||||
|
||||
def get_book_ids(self):
|
||||
results = self.calibre_db.session\
|
||||
.query(db.Books.id)\
|
||||
.filter(db.Books.has_cover == 1)\
|
||||
.distinct()
|
||||
|
||||
return [value for value, in results]
|
||||
|
||||
def delete_thumbnails_for_missing_books(self, book_ids):
|
||||
try:
|
||||
self.app_db_session\
|
||||
.query(ub.Thumbnail)\
|
||||
.filter(ub.Thumbnail.book_id.notin_(book_ids))\
|
||||
.delete(synchronize_session=False)
|
||||
self.app_db_session.commit()
|
||||
except Exception as ex:
|
||||
self.log.info(str(ex))
|
||||
self._handleError(u'Error deleting thumbnails for missing books: ' + str(ex))
|
||||
self.app_db_session.rollback()
|
||||
|
||||
def get_thumbnail_filenames(self):
|
||||
results = self.app_db_session\
|
||||
.query(ub.Thumbnail.filename)\
|
||||
.all()
|
||||
|
||||
return [thumbnail for thumbnail, in results]
|
||||
|
||||
def delete_extraneous_thumbnail_files(self, cached_thumbnail_files, db_thumbnail_files):
|
||||
extraneous_files = list(set(cached_thumbnail_files).difference(db_thumbnail_files))
|
||||
for file in extraneous_files:
|
||||
self.cache.delete_cache_file(file, fs.CACHE_TYPE_THUMBNAILS)
|
||||
|
||||
def get_books_updated_in_the_last_day(self):
|
||||
return self.calibre_db.session\
|
||||
.query(db.Books)\
|
||||
.filter(db.Books.has_cover == 1)\
|
||||
.filter(db.Books.last_modified > datetime.utcnow() - timedelta(days=1, hours=1))\
|
||||
.all()
|
||||
|
||||
def get_thumbnails_for_updated_books(self, book_ids):
|
||||
return self.app_db_session\
|
||||
.query(ub.Thumbnail)\
|
||||
.filter(ub.Thumbnail.book_id.in_(book_ids))\
|
||||
.all()
|
||||
|
||||
def expire_thumbnails_for_updated_book(self, books, thumbnails):
|
||||
thumbnail_ids = list()
|
||||
for book in books:
|
||||
for thumbnail in thumbnails:
|
||||
if thumbnail.book_id == book.id and thumbnail.generated_at < book.last_modified:
|
||||
thumbnail_ids.append(thumbnail.id)
|
||||
|
||||
try:
|
||||
self.app_db_session\
|
||||
.query(ub.Thumbnail)\
|
||||
.filter(ub.Thumbnail.id.in_(thumbnail_ids)) \
|
||||
.update({"expiration": datetime.utcnow()}, synchronize_session=False)
|
||||
self.app_db_session.commit()
|
||||
except Exception as ex:
|
||||
self.log.info(u'Error expiring thumbnails for updated books: ' + str(ex))
|
||||
self._handleError(u'Error expiring thumbnails for updated books: ' + str(ex))
|
||||
self.app_db_session.rollback()
|
||||
|
||||
@property
|
||||
def name(self):
|
||||
return "ThumbnailsSync"
|
||||
|
||||
|
||||
class TaskClearCoverThumbnailCache(CalibreTask):
|
||||
def __init__(self, book_id=None, task_message=u'Clearing cover thumbnail cache'):
|
||||
super(TaskClearCoverThumbnailCache, self).__init__(task_message)
|
||||
@ -325,9 +191,9 @@ class TaskClearCoverThumbnailCache(CalibreTask):
|
||||
if self.book_id:
|
||||
thumbnails = self.get_thumbnails_for_book(self.book_id)
|
||||
for thumbnail in thumbnails:
|
||||
self.expire_and_delete_thumbnail(thumbnail)
|
||||
self.delete_thumbnail(thumbnail)
|
||||
else:
|
||||
self.expire_and_delete_all_thumbnails()
|
||||
self.delete_all_thumbnails()
|
||||
|
||||
self._handleSuccess()
|
||||
self.app_db_session.remove()
|
||||
@ -338,29 +204,19 @@ class TaskClearCoverThumbnailCache(CalibreTask):
|
||||
.filter(ub.Thumbnail.book_id == book_id)\
|
||||
.all()
|
||||
|
||||
def expire_and_delete_thumbnail(self, thumbnail):
|
||||
thumbnail.expiration = datetime.utcnow()
|
||||
|
||||
def delete_thumbnail(self, thumbnail):
|
||||
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()})
|
||||
self.log.info(u'Error deleting book thumbnail: ' + str(ex))
|
||||
self._handleError(u'Error deleting book thumbnail: ' + str(ex))
|
||||
|
||||
def delete_all_thumbnails(self):
|
||||
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()
|
||||
self.log.info(u'Error deleting book thumbnails: ' + str(ex))
|
||||
self._handleError(u'Error deleting book thumbnails: ' + str(ex))
|
||||
|
||||
@property
|
||||
def name(self):
|
||||
|
@ -37,8 +37,7 @@
|
||||
<div class="cover">
|
||||
<a href="{{ url_for('web.show_book', book_id=entry.id) }}">
|
||||
<span class="img">
|
||||
{{ book_cover_image(entry, entry.id|get_book_thumbnails(thumbnails)) }}
|
||||
<!-- <img title="{{author.name|safe}}" src="{{ url_for('web.get_cover', book_id=entry.id) }}" /> -->
|
||||
{{ book_cover_image(entry, title=author.name|safe) }}
|
||||
{% if entry.id in read_book_ids %}<span class="badge read glyphicon glyphicon-ok"></span>{% endif %}
|
||||
</span>
|
||||
</a>
|
||||
|
@ -1,14 +1,11 @@
|
||||
{% macro book_cover_image(book, thumbnails, title=None) -%}
|
||||
{% macro book_cover_image(book, title=None) -%}
|
||||
{%- set book_title = book.title if book.title else book.name -%}
|
||||
{%- set book_title = title if title else book_title -%}
|
||||
{% set srcset = thumbnails|get_book_thumbnail_srcset if thumbnails|length else '' %}
|
||||
{%- if srcset|length -%}
|
||||
<img
|
||||
srcset="{{ srcset }}"
|
||||
src="{{ url_for('web.get_cached_cover', cache_id=book|book_cover_cache_id) }}"
|
||||
alt="{{ book_title }}"
|
||||
/>
|
||||
{%- else -%}
|
||||
<img src="{{ url_for('web.get_cached_cover', cache_id=book|book_cover_cache_id) }}" alt="{{ book_title }}" />
|
||||
{%- endif -%}
|
||||
{% set srcset = book|get_cover_srcset %}
|
||||
<img
|
||||
srcset="{{ srcset }}"
|
||||
src="{{ url_for('web.get_cover', book_id=book.id, resolution=0, cache_bust=book|last_modified) }}"
|
||||
title="{{ book_title }}"
|
||||
alt="{{ book_title }}"
|
||||
/>
|
||||
{%- endmacro %}
|
||||
|
@ -4,8 +4,7 @@
|
||||
{% if book %}
|
||||
<div class="col-sm-3 col-lg-3 col-xs-12">
|
||||
<div class="cover">
|
||||
{{ book_cover_image(book, book.id|get_book_thumbnails(thumbnails)) }}
|
||||
<!-- <img id="detailcover" title="{{book.title}}" src="{{ url_for('web.get_cover', book_id=book.id, edit=1|uuidfilter) }}" alt="{{ book.title }}"/> -->
|
||||
{{ book_cover_image(book) }}
|
||||
</div>
|
||||
{% if g.user.role_delete_books() %}
|
||||
<div class="text-center">
|
||||
|
@ -4,8 +4,7 @@
|
||||
<div class="row">
|
||||
<div class="col-sm-3 col-lg-3 col-xs-5">
|
||||
<div class="cover">
|
||||
{{ book_cover_image(entry, entry.id|get_book_thumbnails(thumbnails)) }}
|
||||
<!-- <img id="detailcover" title="{{entry.title}}" src="{{ url_for('web.get_cover', book_id=entry.id, edit=1|uuidfilter) }}" alt="{{ entry.title }}" /> -->
|
||||
{{ book_cover_image(entry) }}
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-sm-9 col-lg-9 book-meta">
|
||||
|
@ -10,8 +10,7 @@
|
||||
{% 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">
|
||||
<span class="img">
|
||||
{{ book_cover_image(entry, entry.id|get_book_thumbnails(thumbnails)) }}
|
||||
<!-- <img title="{{entry.title}}" src="{{ url_for('web.get_cover', book_id=entry.id) }}" alt="{{ entry.title }}" /> -->
|
||||
{{ book_cover_image(entry) }}
|
||||
{% if entry.id in read_book_ids %}<span class="badge read glyphicon glyphicon-ok"></span>{% endif %}
|
||||
</span>
|
||||
</a>
|
||||
|
@ -29,8 +29,7 @@
|
||||
<div class="cover">
|
||||
<a href="{{url_for('web.books_list', data=data, sort_param='stored', book_id=entry[0].series[0].id )}}">
|
||||
<span class="img">
|
||||
{{ book_cover_image(entry[0], entry[0].id|get_book_thumbnails(thumbnails)) }}
|
||||
<!-- <img title="{{entry.title}}" src="{{ url_for('web.get_cover', book_id=entry[0].id) }}" alt="{{ entry[0].name }}"/> -->
|
||||
{{ book_cover_image(entry[0], title=entry[0].series[0].name|shortentitle) }}
|
||||
<span class="badge">{{entry.count}}</span>
|
||||
</span>
|
||||
</a>
|
||||
|
@ -10,8 +10,7 @@
|
||||
<div class="cover">
|
||||
<a href="{{ url_for('web.show_book', book_id=entry.id) }}" data-toggle="modal" data-target="#bookDetailsModal" data-remote="false">
|
||||
<span class="img">
|
||||
{{ book_cover_image(entry, entry.id|get_book_thumbnails(thumbnails)) }}
|
||||
<!-- <img title="{{ entry.title }}" src="{{ url_for('web.get_cover', book_id=entry.id) }}" alt="{{ entry.title }}" /> -->
|
||||
{{ book_cover_image(entry) }}
|
||||
{% if entry.id in read_book_ids %}<span class="badge read glyphicon glyphicon-ok"></span>{% endif %}
|
||||
</span>
|
||||
</a>
|
||||
@ -88,8 +87,7 @@
|
||||
<div class="cover">
|
||||
<a href="{{ url_for('web.show_book', book_id=entry.id) }}" data-toggle="modal" data-target="#bookDetailsModal" data-remote="false">
|
||||
<span class="img">
|
||||
{{ book_cover_image(entry, entry.id|get_book_thumbnails(thumbnails)) }}
|
||||
<!-- <img title="{{ entry.title }}" src="{{ url_for('web.get_cover', book_id=entry.id) }}" alt="{{ entry.title }}"/> -->
|
||||
{{ book_cover_image(entry) }}
|
||||
{% if entry.id in read_book_ids %}<span class="badge read glyphicon glyphicon-ok"></span>{% endif %}
|
||||
</span>
|
||||
</a>
|
||||
|
@ -45,8 +45,7 @@
|
||||
{% 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">
|
||||
<span class="img">
|
||||
{{ book_cover_image(entry, entry.id|get_book_thumbnails(thumbnails)) }}
|
||||
<!-- <img title="{{entry.title}}" src="{{ url_for('web.get_cover', book_id=entry.id) }}" alt="{{ entry.title }}" /> -->
|
||||
{{ book_cover_image(entry) }}
|
||||
{% if entry.id in read_book_ids %}<span class="badge read glyphicon glyphicon-ok"></span>{% endif %}
|
||||
</span>
|
||||
</a>
|
||||
|
@ -32,8 +32,7 @@
|
||||
<div class="cover">
|
||||
<a href="{{ url_for('web.show_book', book_id=entry.id) }}" data-toggle="modal" data-target="#bookDetailsModal" data-remote="false">
|
||||
<span class="img">
|
||||
{{ book_cover_image(entry, entry.id|get_book_thumbnails(thumbnails)) }}
|
||||
<!-- <img title="{{entry.title}}" src="{{ url_for('web.get_cover', book_id=entry.id) }}" alt="{{ entry.title }}" /> -->
|
||||
{{ book_cover_image(entry) }}
|
||||
{% if entry.id in read_book_ids %}<span class="badge read glyphicon glyphicon-ok"></span>{% endif %}
|
||||
</span>
|
||||
</a>
|
||||
|
@ -532,7 +532,7 @@ class Thumbnail(Base):
|
||||
resolution = Column(SmallInteger, default=1)
|
||||
filename = Column(String, default=filename)
|
||||
generated_at = Column(DateTime, default=lambda: datetime.datetime.utcnow())
|
||||
expiration = Column(DateTime, default=lambda: datetime.datetime.utcnow() + datetime.timedelta(days=90))
|
||||
expiration = Column(DateTime, nullable=True)
|
||||
|
||||
|
||||
# Add missing tables during migration of database
|
||||
|
83
cps/web.py
83
cps/web.py
@ -51,7 +51,6 @@ from . import babel, db, ub, config, get_locale, app
|
||||
from . import calibre_db
|
||||
from .gdriveutils import getFileFromEbooksFolder, do_gdrive_download
|
||||
from .helper import check_valid_domain, render_task_status, check_email, check_username, \
|
||||
get_cached_book_cover, get_cached_book_cover_thumbnail, get_thumbnails_for_books, get_thumbnails_for_book_series, \
|
||||
get_cc_columns, get_book_cover, get_download_link, send_mail, generate_random_password, \
|
||||
send_registration_mail, check_send_to_kindle, check_read_formats, tags_filters, reset_password, valid_email
|
||||
from .pagination import Pagination
|
||||
@ -415,10 +414,8 @@ def render_books_list(data, sort, book_id, page):
|
||||
db.books_series_link,
|
||||
db.Books.id == db.books_series_link.c.book,
|
||||
db.Series)
|
||||
|
||||
thumbnails = get_thumbnails_for_books(entries + random if type(random) is list else entries)
|
||||
return render_title_template('index.html', random=random, entries=entries, pagination=pagination,
|
||||
title=_(u"Books"), page=website, thumbnails=thumbnails)
|
||||
title=_(u"Books"), page=website)
|
||||
|
||||
|
||||
def render_rated_books(page, book_id, order):
|
||||
@ -467,9 +464,8 @@ def render_hot_books(page):
|
||||
ub.delete_download(book.Downloads.book_id)
|
||||
numBooks = entries.__len__()
|
||||
pagination = Pagination(page, config.config_books_per_page, numBooks)
|
||||
thumbnails = get_thumbnails_for_books(entries + random if type(random) is list else entries)
|
||||
return render_title_template('index.html', random=random, entries=entries, pagination=pagination,
|
||||
title=_(u"Hot Books (Most Downloaded)"), page="hot", thumbnails=thumbnails)
|
||||
title=_(u"Hot Books (Most Downloaded)"), page="hot")
|
||||
else:
|
||||
abort(404)
|
||||
|
||||
@ -497,16 +493,13 @@ def render_downloaded_books(page, order, user_id):
|
||||
.filter(db.Books.id == book.id).first():
|
||||
ub.delete_download(book.id)
|
||||
user = ub.session.query(ub.User).filter(ub.User.id == user_id).first()
|
||||
|
||||
thumbnails = get_thumbnails_for_books(entries + random if type(random) is list else entries)
|
||||
return render_title_template('index.html',
|
||||
random=random,
|
||||
entries=entries,
|
||||
pagination=pagination,
|
||||
id=user_id,
|
||||
title=_(u"Downloaded books by %(user)s",user=user.name),
|
||||
page="download",
|
||||
thumbnails=thumbnails)
|
||||
page="download")
|
||||
else:
|
||||
abort(404)
|
||||
|
||||
@ -535,10 +528,9 @@ def render_author_books(page, author_id, order):
|
||||
author_info = services.goodreads_support.get_author_info(author_name)
|
||||
other_books = services.goodreads_support.get_other_books(author_info, entries)
|
||||
|
||||
thumbnails = get_thumbnails_for_books(entries)
|
||||
return render_title_template('author.html', entries=entries, pagination=pagination, id=author_id,
|
||||
title=_(u"Author: %(name)s", name=author_name), author=author_info,
|
||||
other_books=other_books, page="author", thumbnails=thumbnails)
|
||||
other_books=other_books, page="author")
|
||||
|
||||
|
||||
def render_publisher_books(page, book_id, order):
|
||||
@ -551,10 +543,8 @@ def render_publisher_books(page, book_id, order):
|
||||
db.books_series_link,
|
||||
db.Books.id == db.books_series_link.c.book,
|
||||
db.Series)
|
||||
thumbnails = get_thumbnails_for_books(entries + random if type(random) is list else entries)
|
||||
return render_title_template('index.html', random=random, entries=entries, pagination=pagination, id=book_id,
|
||||
title=_(u"Publisher: %(name)s", name=publisher.name), page="publisher",
|
||||
thumbnails=thumbnails)
|
||||
title=_(u"Publisher: %(name)s", name=publisher.name), page="publisher")
|
||||
else:
|
||||
abort(404)
|
||||
|
||||
@ -566,10 +556,8 @@ def render_series_books(page, book_id, order):
|
||||
db.Books,
|
||||
db.Books.series.any(db.Series.id == book_id),
|
||||
[order[0]])
|
||||
thumbnails = get_thumbnails_for_books(entries + random if type(random) is list else entries)
|
||||
return render_title_template('index.html', random=random, pagination=pagination, entries=entries, id=book_id,
|
||||
title=_(u"Series: %(serie)s", serie=name.name), page="series",
|
||||
thumbnails=thumbnails)
|
||||
title=_(u"Series: %(serie)s", serie=name.name), page="series")
|
||||
else:
|
||||
abort(404)
|
||||
|
||||
@ -581,10 +569,8 @@ def render_ratings_books(page, book_id, order):
|
||||
db.Books.ratings.any(db.Ratings.id == book_id),
|
||||
[order[0]])
|
||||
if name and name.rating <= 10:
|
||||
thumbnails = get_thumbnails_for_books(entries + random if type(random) is list else entries)
|
||||
return render_title_template('index.html', random=random, pagination=pagination, entries=entries, id=book_id,
|
||||
title=_(u"Rating: %(rating)s stars", rating=int(name.rating / 2)), page="ratings",
|
||||
thumbnails=thumbnails)
|
||||
title=_(u"Rating: %(rating)s stars", rating=int(name.rating / 2)), page="ratings")
|
||||
else:
|
||||
abort(404)
|
||||
|
||||
@ -596,10 +582,8 @@ def render_formats_books(page, book_id, order):
|
||||
db.Books,
|
||||
db.Books.data.any(db.Data.format == book_id.upper()),
|
||||
[order[0]])
|
||||
thumbnails = get_thumbnails_for_books(entries + random if type(random) is list else entries)
|
||||
return render_title_template('index.html', random=random, pagination=pagination, entries=entries, id=book_id,
|
||||
title=_(u"File format: %(format)s", format=name.format), page="formats",
|
||||
thumbnails=thumbnails)
|
||||
title=_(u"File format: %(format)s", format=name.format), page="formats")
|
||||
else:
|
||||
abort(404)
|
||||
|
||||
@ -614,10 +598,8 @@ def render_category_books(page, book_id, order):
|
||||
db.books_series_link,
|
||||
db.Books.id == db.books_series_link.c.book,
|
||||
db.Series)
|
||||
thumbnails = get_thumbnails_for_books(entries + random if type(random) is list else entries)
|
||||
return render_title_template('index.html', random=random, entries=entries, pagination=pagination, id=book_id,
|
||||
title=_(u"Category: %(name)s", name=name.name), page="category",
|
||||
thumbnails=thumbnails)
|
||||
title=_(u"Category: %(name)s", name=name.name), page="category")
|
||||
else:
|
||||
abort(404)
|
||||
|
||||
@ -635,9 +617,8 @@ def render_language_books(page, name, order):
|
||||
db.Books,
|
||||
db.Books.languages.any(db.Languages.lang_code == name),
|
||||
[order[0]])
|
||||
thumbnails = get_thumbnails_for_books(entries + random if type(random) is list else entries)
|
||||
return render_title_template('index.html', random=random, entries=entries, pagination=pagination, id=name,
|
||||
title=_(u"Language: %(name)s", name=lang_name), page="language", thumbnails=thumbnails)
|
||||
title=_(u"Language: %(name)s", name=lang_name), page="language")
|
||||
|
||||
|
||||
def render_read_books(page, are_read, as_xml=False, order=None):
|
||||
@ -687,10 +668,8 @@ def render_read_books(page, are_read, as_xml=False, order=None):
|
||||
else:
|
||||
name = _(u'Unread Books') + ' (' + str(pagination.total_count) + ')'
|
||||
pagename = "unread"
|
||||
|
||||
thumbnails = get_thumbnails_for_books(entries + random if type(random) is list else entries)
|
||||
return render_title_template('index.html', random=random, entries=entries, pagination=pagination,
|
||||
title=name, page=pagename, thumbnails=thumbnails)
|
||||
title=name, page=pagename)
|
||||
|
||||
|
||||
def render_archived_books(page, order):
|
||||
@ -713,9 +692,8 @@ def render_archived_books(page, order):
|
||||
|
||||
name = _(u'Archived Books') + ' (' + str(len(archived_book_ids)) + ')'
|
||||
pagename = "archived"
|
||||
thumbnails = get_thumbnails_for_books(entries + random if type(random) is list else entries)
|
||||
return render_title_template('index.html', random=random, entries=entries, pagination=pagination,
|
||||
title=name, page=pagename, thumbnails=thumbnails)
|
||||
title=name, page=pagename)
|
||||
|
||||
|
||||
def render_prepare_search_form(cc):
|
||||
@ -752,7 +730,6 @@ def render_prepare_search_form(cc):
|
||||
def render_search_results(term, offset=None, order=None, limit=None):
|
||||
join = db.books_series_link, db.Books.id == db.books_series_link.c.book, db.Series
|
||||
entries, result_count, pagination = calibre_db.get_search_results(term, offset, order, limit, *join)
|
||||
thumbnails = get_thumbnails_for_books(entries)
|
||||
return render_title_template('search.html',
|
||||
searchterm=term,
|
||||
pagination=pagination,
|
||||
@ -761,8 +738,7 @@ def render_search_results(term, offset=None, order=None, limit=None):
|
||||
entries=entries,
|
||||
result_count=result_count,
|
||||
title=_(u"Search"),
|
||||
page="search",
|
||||
thumbnails=thumbnails)
|
||||
page="search")
|
||||
|
||||
|
||||
# ################################### View Books list ##################################################################
|
||||
@ -973,10 +949,9 @@ def series_list():
|
||||
.join(db.books_series_link).join(db.Books).filter(calibre_db.common_filters()) \
|
||||
.group_by(func.upper(func.substr(db.Series.sort, 1, 1))).all()
|
||||
|
||||
thumbnails = get_thumbnails_for_book_series(entries)
|
||||
return render_title_template('grid.html', entries=entries, folder='web.books_list', charlist=charlist,
|
||||
title=_(u"Series"), page="serieslist", data="series", bodyClass="grid-view",
|
||||
order=order_no, thumbnails=thumbnails)
|
||||
order=order_no)
|
||||
else:
|
||||
abort(404)
|
||||
|
||||
@ -1392,17 +1367,13 @@ def render_adv_search_results(term, offset=None, order=None, limit=None):
|
||||
else:
|
||||
offset = 0
|
||||
limit_all = result_count
|
||||
|
||||
entries = q[offset:limit_all]
|
||||
thumbnails = get_thumbnails_for_books(entries)
|
||||
return render_title_template('search.html',
|
||||
adv_searchterm=searchterm,
|
||||
pagination=pagination,
|
||||
entries=entries,
|
||||
entries=q[offset:limit_all],
|
||||
result_count=result_count,
|
||||
title=_(u"Advanced Search"),
|
||||
page="advsearch",
|
||||
thumbnails=thumbnails)
|
||||
page="advsearch")
|
||||
|
||||
|
||||
@web.route("/advsearch", methods=['GET'])
|
||||
@ -1417,21 +1388,11 @@ def advanced_search_form():
|
||||
|
||||
|
||||
@web.route("/cover/<int:book_id>")
|
||||
@web.route("/cover/<int:book_id>/<int:resolution>")
|
||||
@web.route("/cover/<int:book_id>/<int:resolution>/<string:cache_bust>")
|
||||
@login_required_if_no_ano
|
||||
def get_cover(book_id):
|
||||
return get_book_cover(book_id)
|
||||
|
||||
|
||||
@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("/cached-cover-thumbnail/<string:cache_id>")
|
||||
@login_required_if_no_ano
|
||||
def get_cached_cover_thumbnail(cache_id):
|
||||
return get_cached_book_cover_thumbnail(cache_id)
|
||||
def get_cover(book_id, resolution=None, cache_bust=None):
|
||||
return get_book_cover(book_id, resolution)
|
||||
|
||||
|
||||
@web.route("/robots.txt")
|
||||
@ -1841,7 +1802,6 @@ def show_book(book_id):
|
||||
if media_format.format.lower() in constants.EXTENSIONS_AUDIO:
|
||||
audioentries.append(media_format.format.lower())
|
||||
|
||||
thumbnails = get_thumbnails_for_books([entries])
|
||||
return render_title_template('detail.html',
|
||||
entry=entries,
|
||||
audioentries=audioentries,
|
||||
@ -1853,8 +1813,7 @@ def show_book(book_id):
|
||||
is_archived=is_archived,
|
||||
kindle_list=kindle_list,
|
||||
reader_list=reader_list,
|
||||
page="book",
|
||||
thumbnails=thumbnails)
|
||||
page="book")
|
||||
else:
|
||||
log.debug(u"Oops! Selected book title is unavailable. File does not exist or is not accessible")
|
||||
flash(_(u"Oops! Selected book title is unavailable. File does not exist or is not accessible"),
|
||||
|
Loading…
Reference in New Issue
Block a user