Simplified all of the thumbnail generation and loading.

This commit is contained in:
mmonkey 2021-09-24 03:11:14 -05:00
parent 524ed07a6c
commit be28a91315
16 changed files with 111 additions and 376 deletions

View File

@ -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):

View File

@ -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()

View File

@ -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)

View File

@ -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)

View File

@ -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):

View File

@ -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>

View File

@ -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 -%}
{% set srcset = book|get_cover_srcset %}
<img
srcset="{{ srcset }}"
src="{{ url_for('web.get_cached_cover', cache_id=book|book_cover_cache_id) }}"
src="{{ url_for('web.get_cover', book_id=book.id, resolution=0, cache_bust=book|last_modified) }}"
title="{{ book_title }}"
alt="{{ book_title }}"
/>
{%- else -%}
<img src="{{ url_for('web.get_cached_cover', cache_id=book|book_cover_cache_id) }}" alt="{{ book_title }}" />
{%- endif -%}
{%- endmacro %}

View File

@ -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">

View File

@ -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">

View File

@ -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>

View File

@ -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>

View File

@ -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>

View File

@ -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>

View File

@ -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>

View File

@ -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

View File

@ -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"),