Added thumbnail urls to book cover srcsets with cache busting ids

This commit is contained in:
mmonkey 2020-12-24 02:35:32 -06:00
parent 626051e489
commit 242a2767a1
15 changed files with 199 additions and 55 deletions

View File

@ -609,7 +609,8 @@ class CalibreDB():
randm = self.session.query(Books) \ randm = self.session.query(Books) \
.filter(self.common_filters(allow_show_archived)) \ .filter(self.common_filters(allow_show_archived)) \
.order_by(func.random()) \ .order_by(func.random()) \
.limit(self.config.config_random_books) .limit(self.config.config_random_books) \
.all()
else: else:
randm = false() randm = false()
off = int(int(pagesize) * (page - 1)) off = int(int(pagesize) * (page - 1))

View File

@ -533,6 +533,21 @@ def delete_book(book, calibrepath, book_format):
return delete_book_file(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))
return ub.session\
.query(ub.Thumbnail)\
.filter(ub.Thumbnail.book_id.in_(book_ids))\
.filter(ub.Thumbnail.expiration > datetime.utcnow())\
.all()
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): def get_cover_on_failure(use_generic_cover):
if use_generic_cover: if use_generic_cover:
return send_from_directory(_STATIC_DIR, "generic_cover.jpg") return send_from_directory(_STATIC_DIR, "generic_cover.jpg")
@ -558,6 +573,29 @@ def get_cached_book_cover(cache_id):
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, 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): 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:

View File

@ -139,3 +139,19 @@ def book_cover_cache_id(book, resolution=None):
timestamp = int(book.last_modified.timestamp() * 1000) timestamp = int(book.last_modified.timestamp() * 1000)
cache_bust = str(book.uuid) + '_' + str(timestamp) cache_bust = str(book.uuid) + '_' + str(timestamp)
return cache_bust if not resolution else cache_bust + '_' + str(resolution) return cache_bust if not resolution else cache_bust + '_' + str(resolution)
@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):
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')
return ', '.join(srcset)

View File

@ -20,17 +20,17 @@ from __future__ import division, print_function, unicode_literals
from .services.background_scheduler import BackgroundScheduler from .services.background_scheduler import BackgroundScheduler
from .tasks.database import TaskReconnectDatabase from .tasks.database import TaskReconnectDatabase
from .tasks.thumbnail import TaskCleanupCoverThumbnailCache, TaskGenerateCoverThumbnails from .tasks.thumbnail import TaskSyncCoverThumbnailCache, TaskGenerateCoverThumbnails
def register_jobs(): def register_jobs():
scheduler = BackgroundScheduler() scheduler = BackgroundScheduler()
# Generate 100 book cover thumbnails every 5 minutes # Generate 100 book cover thumbnails every 5 minutes
scheduler.add_task(user=None, task=lambda: TaskGenerateCoverThumbnails(limit=100), trigger='interval', minutes=5) scheduler.add_task(user=None, task=lambda: TaskGenerateCoverThumbnails(limit=100), trigger='cron', minute='*/5')
# Cleanup book cover cache every day at 4am # Cleanup book cover cache every 6 hours
scheduler.add_task(user=None, task=lambda: TaskCleanupCoverThumbnailCache(), trigger='cron', hour=4) scheduler.add_task(user=None, task=lambda: TaskSyncCoverThumbnailCache(), trigger='cron', minute='15', hour='*/6')
# Reconnect metadata.db every 4 hours # Reconnect metadata.db every 4 hours
scheduler.add_task(user=None, task=lambda: TaskReconnectDatabase(), trigger='interval', hours=4) scheduler.add_task(user=None, task=lambda: TaskReconnectDatabase(), trigger='cron', minute='5', hour='*/4')

View File

@ -59,6 +59,10 @@ class TaskGenerateCoverThumbnails(CalibreTask):
books_without_thumbnails = self.get_books_without_thumbnails(thumbnail_book_ids) books_without_thumbnails = self.get_books_without_thumbnails(thumbnail_book_ids)
count = len(books_without_thumbnails) 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
for i, book in enumerate(books_without_thumbnails): for i, book in enumerate(books_without_thumbnails):
for resolution in self.resolutions: for resolution in self.resolutions:
expired_thumbnail = self.get_expired_thumbnail_for_book_and_resolution( expired_thumbnail = self.get_expired_thumbnail_for_book_and_resolution(
@ -71,6 +75,7 @@ class TaskGenerateCoverThumbnails(CalibreTask):
else: else:
self.create_book_thumbnail(book, resolution) self.create_book_thumbnail(book, resolution)
self.message(u'Generating cover thumbnail {0} of {1}'.format(i, count))
self.progress = (1.0 / count) * i self.progress = (1.0 / count) * i
self._handleSuccess() self._handleSuccess()
@ -181,12 +186,12 @@ class TaskGenerateCoverThumbnails(CalibreTask):
@property @property
def name(self): def name(self):
return "GenerateCoverThumbnails" return "ThumbnailsGenerate"
class TaskCleanupCoverThumbnailCache(CalibreTask): class TaskSyncCoverThumbnailCache(CalibreTask):
def __init__(self, task_message=u'Cleaning up cover thumbnail cache'): def __init__(self, task_message=u'Syncing cover thumbnail cache'):
super(TaskCleanupCoverThumbnailCache, self).__init__(task_message) super(TaskSyncCoverThumbnailCache, 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()
self.calibre_db = db.CalibreDB(expire_on_commit=False) self.calibre_db = db.CalibreDB(expire_on_commit=False)
@ -199,14 +204,23 @@ class TaskCleanupCoverThumbnailCache(CalibreTask):
# This case will happen if a user deletes the cache dir or cached files # This case will happen if a user deletes the cache dir or cached files
if self.app_db_session: if self.app_db_session:
self.expire_missing_thumbnails(cached_thumbnail_files) self.expire_missing_thumbnails(cached_thumbnail_files)
self.progress = 0.33 self.progress = 0.25
# Delete thumbnails in the database if the book has been removed # 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 # 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: if self.app_db_session and self.calibre_db:
book_ids = self.get_book_ids() book_ids = self.get_book_ids()
self.delete_thumbnails_for_missing_books(book_ids) self.delete_thumbnails_for_missing_books(book_ids)
self.progress = 0.66 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 # 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 # This case will happen if a book was deleted and the thumbnail OR the metadata.db file was changed externally
@ -261,9 +275,40 @@ class TaskCleanupCoverThumbnailCache(CalibreTask):
for file in extraneous_files: for file in extraneous_files:
self.cache.delete_cache_file(file, fs.CACHE_TYPE_THUMBNAILS) 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 @property
def name(self): def name(self):
return "CleanupCoverThumbnailCache" return "ThumbnailsSync"
class TaskClearCoverThumbnailCache(CalibreTask): class TaskClearCoverThumbnailCache(CalibreTask):
@ -318,4 +363,4 @@ class TaskClearCoverThumbnailCache(CalibreTask):
@property @property
def name(self): def name(self):
return "ClearCoverThumbnailCache" return "ThumbnailsClear"

View File

@ -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, entry.title) }} {{ book_cover_image(entry, entry.id|get_book_thumbnails(thumbnails)) }}
</a> </a>
</div> </div>
<div class="meta"> <div class="meta">

View File

@ -1,8 +1,13 @@
{% macro book_cover_image(book, book_title) -%} {% macro book_cover_image(book, thumbnails) -%}
<img {%- set book_title = book.title if book.title else book.name -%}
srcset="{{ url_for('web.get_cached_cover', cache_id=book|book_cover_cache_id(1)) }} 1x, {% set srcset = thumbnails|get_book_thumbnail_srcset if thumbnails|length else '' %}
{{ url_for('web.get_cached_cover', cache_id=book|book_cover_cache_id(2)) }} 2x" {%- if srcset|length -%}
src="{{ url_for('web.get_cached_cover', cache_id=book|book_cover_cache_id) }}" <img
alt="{{ book_title }}" 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 -%}
{%- endmacro %} {%- endmacro %}

View File

@ -4,7 +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, book.title) }} {{ book_cover_image(book, book.id|get_book_thumbnails(thumbnails)) }}
</div> </div>
{% if g.user.role_delete_books() %} {% if g.user.role_delete_books() %}
<div class="text-center"> <div class="text-center">

View File

@ -4,7 +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, entry.title) }} {{ book_cover_image(entry, entry.id|get_book_thumbnails(thumbnails)) }}
</div> </div>
</div> </div>
<div class="col-sm-9 col-lg-9 book-meta"> <div class="col-sm-9 col-lg-9 book-meta">

View File

@ -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, entry.title) }} {{ book_cover_image(entry, entry.id|get_book_thumbnails(thumbnails)) }}
</a> </a>
{% endif %} {% endif %}
</div> </div>

View File

@ -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], entry[0].name) }} {{ book_cover_image(entry[0], entry[0].id|get_book_thumbnails(thumbnails)) }}
<span class="badge">{{entry.count}}</span> <span class="badge">{{entry.count}}</span>
</a> </a>
</div> </div>

View File

@ -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, entry.title) }} {{ book_cover_image(entry, entry.id|get_book_thumbnails(thumbnails)) }}
</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, entry.title) }} {{ book_cover_image(entry, entry.id|get_book_thumbnails(thumbnails)) }}
</a> </a>
</div> </div>
<div class="meta"> <div class="meta">

View File

@ -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, entry.title) }} {{ book_cover_image(entry, entry.id|get_book_thumbnails(thumbnails)) }}
</a> </a>
{% endif %} {% endif %}
</div> </div>

View File

@ -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, entry.title) }} {{ book_cover_image(entry, entry.id|get_book_thumbnails(thumbnails)) }}
</a> </a>
</div> </div>
<div class="meta"> <div class="meta">

View File

@ -49,9 +49,10 @@ from . import constants, logger, isoLanguages, services
from . import babel, db, ub, config, get_locale, app 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_cached_book_cover, \
get_cc_columns, get_book_cover, get_cached_book_cover, get_download_link, send_mail, generate_random_password, \ get_cached_book_cover_thumbnail, get_thumbnails_for_books, get_thumbnails_for_book_series, get_download_link, \
send_registration_mail, check_send_to_kindle, check_read_formats, tags_filters, reset_password send_mail, generate_random_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
from .usermanagement import login_required_if_no_ano from .usermanagement import login_required_if_no_ano
@ -386,16 +387,18 @@ def render_books_list(data, sort, book_id, page):
db.Books, db.Books,
db.Books.ratings.any(db.Ratings.rating > 9), db.Books.ratings.any(db.Ratings.rating > 9),
order) order)
thumbnails = get_thumbnails_for_books(entries + random)
return render_title_template('index.html', random=random, entries=entries, pagination=pagination, return render_title_template('index.html', random=random, entries=entries, pagination=pagination,
id=book_id, title=_(u"Top Rated Books"), page="rated") id=book_id, title=_(u"Top Rated Books"), page="rated", thumbnails=thumbnails)
else: else:
abort(404) abort(404)
elif data == "discover": elif data == "discover":
if current_user.check_visibility(constants.SIDEBAR_RANDOM): if current_user.check_visibility(constants.SIDEBAR_RANDOM):
entries, __, pagination = calibre_db.fill_indexpage(page, 0, db.Books, True, [func.randomblob(2)]) entries, __, pagination = calibre_db.fill_indexpage(page, 0, db.Books, True, [func.randomblob(2)])
pagination = Pagination(1, config.config_books_per_page, config.config_books_per_page) pagination = Pagination(1, config.config_books_per_page, config.config_books_per_page)
thumbnails = get_thumbnails_for_books(entries)
return render_title_template('discover.html', entries=entries, pagination=pagination, id=book_id, return render_title_template('discover.html', entries=entries, pagination=pagination, id=book_id,
title=_(u"Discover (Random Books)"), page="discover") title=_(u"Discover (Random Books)"), page="discover", thumbnails=thumbnails)
else: else:
abort(404) abort(404)
elif data == "unread": elif data == "unread":
@ -433,8 +436,9 @@ def render_books_list(data, sort, book_id, page):
else: else:
website = data or "newest" website = data or "newest"
entries, random, pagination = calibre_db.fill_indexpage(page, 0, db.Books, True, order) entries, random, pagination = calibre_db.fill_indexpage(page, 0, db.Books, True, order)
thumbnails = get_thumbnails_for_books(entries + random)
return render_title_template('index.html', random=random, entries=entries, pagination=pagination, return render_title_template('index.html', random=random, entries=entries, pagination=pagination,
title=_(u"Books"), page=website) title=_(u"Books"), page=website, thumbnails=thumbnails)
def render_hot_books(page): def render_hot_books(page):
@ -458,8 +462,9 @@ def render_hot_books(page):
ub.delete_download(book.Downloads.book_id) ub.delete_download(book.Downloads.book_id)
numBooks = entries.__len__() numBooks = entries.__len__()
pagination = Pagination(page, config.config_books_per_page, numBooks) pagination = Pagination(page, config.config_books_per_page, numBooks)
thumbnails = get_thumbnails_for_books(entries + random)
return render_title_template('index.html', random=random, entries=entries, pagination=pagination, return render_title_template('index.html', random=random, entries=entries, pagination=pagination,
title=_(u"Hot Books (Most Downloaded)"), page="hot") title=_(u"Hot Books (Most Downloaded)"), page="hot", thumbnails=thumbnails)
else: else:
abort(404) abort(404)
@ -490,12 +495,14 @@ def render_downloaded_books(page, order):
.filter(db.Books.id == book.id).first(): .filter(db.Books.id == book.id).first():
ub.delete_download(book.id) ub.delete_download(book.id)
thumbnails = get_thumbnails_for_books(entries + random)
return render_title_template('index.html', return render_title_template('index.html',
random=random, random=random,
entries=entries, entries=entries,
pagination=pagination, pagination=pagination,
title=_(u"Downloaded books by %(user)s",user=current_user.nickname), title=_(u"Downloaded books by %(user)s",user=current_user.nickname),
page="download") page="download",
thumbnails=thumbnails)
else: else:
abort(404) abort(404)
@ -521,9 +528,10 @@ def render_author_books(page, author_id, order):
author_info = services.goodreads_support.get_author_info(author_name) author_info = services.goodreads_support.get_author_info(author_name)
other_books = services.goodreads_support.get_other_books(author_info, entries) 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, return render_title_template('author.html', entries=entries, pagination=pagination, id=author_id,
title=_(u"Author: %(name)s", name=author_name), author=author_info, title=_(u"Author: %(name)s", name=author_name), author=author_info,
other_books=other_books, page="author") other_books=other_books, page="author", thumbnails=thumbnails)
def render_publisher_books(page, book_id, order): def render_publisher_books(page, book_id, order):
@ -535,8 +543,10 @@ def render_publisher_books(page, book_id, order):
[db.Series.name, order[0], db.Books.series_index], [db.Series.name, order[0], db.Books.series_index],
db.books_series_link, db.books_series_link,
db.Series) db.Series)
thumbnails = get_thumbnails_for_books(entries + random)
return render_title_template('index.html', random=random, entries=entries, pagination=pagination, id=book_id, 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") title=_(u"Publisher: %(name)s", name=publisher.name), page="publisher",
thumbnails=thumbnails)
else: else:
abort(404) abort(404)
@ -548,8 +558,10 @@ def render_series_books(page, book_id, order):
db.Books, db.Books,
db.Books.series.any(db.Series.id == book_id), db.Books.series.any(db.Series.id == book_id),
[order[0]]) [order[0]])
thumbnails = get_thumbnails_for_books(entries + random)
return render_title_template('index.html', random=random, pagination=pagination, entries=entries, id=book_id, 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") title=_(u"Series: %(serie)s", serie=name.name), page="series",
thumbnails=thumbnails)
else: else:
abort(404) abort(404)
@ -561,8 +573,10 @@ def render_ratings_books(page, book_id, order):
db.Books.ratings.any(db.Ratings.id == book_id), db.Books.ratings.any(db.Ratings.id == book_id),
[order[0]]) [order[0]])
if name and name.rating <= 10: if name and name.rating <= 10:
thumbnails = get_thumbnails_for_books(entries + random)
return render_title_template('index.html', random=random, pagination=pagination, entries=entries, id=book_id, 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") title=_(u"Rating: %(rating)s stars", rating=int(name.rating / 2)), page="ratings",
thumbnails=thumbnails)
else: else:
abort(404) abort(404)
@ -574,8 +588,10 @@ def render_formats_books(page, book_id, order):
db.Books, db.Books,
db.Books.data.any(db.Data.format == book_id.upper()), db.Books.data.any(db.Data.format == book_id.upper()),
[order[0]]) [order[0]])
thumbnails = get_thumbnails_for_books(entries + random)
return render_title_template('index.html', random=random, pagination=pagination, entries=entries, id=book_id, 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") title=_(u"File format: %(format)s", format=name.format), page="formats",
thumbnails=thumbnails)
else: else:
abort(404) abort(404)
@ -588,8 +604,10 @@ def render_category_books(page, book_id, order):
db.Books.tags.any(db.Tags.id == book_id), db.Books.tags.any(db.Tags.id == book_id),
[order[0], db.Series.name, db.Books.series_index], [order[0], db.Series.name, db.Books.series_index],
db.books_series_link, db.Series) db.books_series_link, db.Series)
thumbnails = get_thumbnails_for_books(entries + random)
return render_title_template('index.html', random=random, entries=entries, pagination=pagination, id=book_id, 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") title=_(u"Category: %(name)s", name=name.name), page="category",
thumbnails=thumbnails)
else: else:
abort(404) abort(404)
@ -607,8 +625,9 @@ def render_language_books(page, name, order):
db.Books, db.Books,
db.Books.languages.any(db.Languages.lang_code == name), db.Books.languages.any(db.Languages.lang_code == name),
[order[0]]) [order[0]])
thumbnails = get_thumbnails_for_books(entries + random)
return render_title_template('index.html', random=random, entries=entries, pagination=pagination, id=name, return render_title_template('index.html', random=random, entries=entries, pagination=pagination, id=name,
title=_(u"Language: %(name)s", name=lang_name), page="language") title=_(u"Language: %(name)s", name=lang_name), page="language", thumbnails=thumbnails)
def render_read_books(page, are_read, as_xml=False, order=None): def render_read_books(page, are_read, as_xml=False, order=None):
@ -652,8 +671,10 @@ def render_read_books(page, are_read, as_xml=False, order=None):
else: else:
name = _(u'Unread Books') + ' (' + str(pagination.total_count) + ')' name = _(u'Unread Books') + ' (' + str(pagination.total_count) + ')'
pagename = "unread" pagename = "unread"
thumbnails = get_thumbnails_for_books(entries + random)
return render_title_template('index.html', random=random, entries=entries, pagination=pagination, return render_title_template('index.html', random=random, entries=entries, pagination=pagination,
title=name, page=pagename) title=name, page=pagename, thumbnails=thumbnails)
def render_archived_books(page, order): def render_archived_books(page, order):
@ -676,8 +697,9 @@ def render_archived_books(page, order):
name = _(u'Archived Books') + ' (' + str(len(archived_book_ids)) + ')' name = _(u'Archived Books') + ' (' + str(len(archived_book_ids)) + ')'
pagename = "archived" pagename = "archived"
thumbnails = get_thumbnails_for_books(entries + random)
return render_title_template('index.html', random=random, entries=entries, pagination=pagination, return render_title_template('index.html', random=random, entries=entries, pagination=pagination,
title=name, page=pagename) title=name, page=pagename, thumbnails=thumbnails)
def render_prepare_search_form(cc): def render_prepare_search_form(cc):
@ -710,6 +732,7 @@ def render_prepare_search_form(cc):
def render_search_results(term, offset=None, order=None, limit=None): def render_search_results(term, offset=None, order=None, limit=None):
entries, result_count, pagination = calibre_db.get_search_results(term, offset, order, limit) entries, result_count, pagination = calibre_db.get_search_results(term, offset, order, limit)
thumbnails = get_thumbnails_for_books(entries)
return render_title_template('search.html', return render_title_template('search.html',
searchterm=term, searchterm=term,
pagination=pagination, pagination=pagination,
@ -718,7 +741,8 @@ def render_search_results(term, offset=None, order=None, limit=None):
entries=entries, entries=entries,
result_count=result_count, result_count=result_count,
title=_(u"Search"), title=_(u"Search"),
page="search") page="search",
thumbnails=thumbnails)
# ################################### View Books list ################################################################## # ################################### View Books list ##################################################################
@ -748,6 +772,7 @@ def books_table():
return render_title_template('book_table.html', title=_(u"Books List"), page="book_table", return render_title_template('book_table.html', title=_(u"Books List"), page="book_table",
visiblility=visibility) visiblility=visibility)
@web.route("/ajax/listbooks") @web.route("/ajax/listbooks")
@login_required @login_required
def list_books(): def list_books():
@ -780,6 +805,7 @@ def list_books():
response.headers["Content-Type"] = "application/json; charset=utf-8" response.headers["Content-Type"] = "application/json; charset=utf-8"
return response return response
@web.route("/ajax/table_settings", methods=['POST']) @web.route("/ajax/table_settings", methods=['POST'])
@login_required @login_required
def update_table_settings(): def update_table_settings():
@ -834,6 +860,7 @@ def publisher_list():
charlist = calibre_db.session.query(func.upper(func.substr(db.Publishers.name, 1, 1)).label('char')) \ charlist = calibre_db.session.query(func.upper(func.substr(db.Publishers.name, 1, 1)).label('char')) \
.join(db.books_publishers_link).join(db.Books).filter(calibre_db.common_filters()) \ .join(db.books_publishers_link).join(db.Books).filter(calibre_db.common_filters()) \
.group_by(func.upper(func.substr(db.Publishers.name, 1, 1))).all() .group_by(func.upper(func.substr(db.Publishers.name, 1, 1))).all()
return render_title_template('list.html', entries=entries, folder='web.books_list', charlist=charlist, return render_title_template('list.html', entries=entries, folder='web.books_list', charlist=charlist,
title=_(u"Publishers"), page="publisherlist", data="publisher") title=_(u"Publishers"), page="publisherlist", data="publisher")
else: else:
@ -865,8 +892,10 @@ def series_list():
.join(db.books_series_link).join(db.Books).filter(calibre_db.common_filters()) \ .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() .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, return render_title_template('grid.html', entries=entries, folder='web.books_list', charlist=charlist,
title=_(u"Series"), page="serieslist", data="series", bodyClass="grid-view") title=_(u"Series"), page="serieslist", data="series", bodyClass="grid-view",
thumbnails=thumbnails)
else: else:
abort(404) abort(404)
@ -1150,13 +1179,16 @@ def render_adv_search_results(term, offset=None, order=None, limit=None):
else: else:
offset = 0 offset = 0
limit_all = result_count limit_all = result_count
thumbnails = get_thumbnails_for_books(entries)
return render_title_template('search.html', return render_title_template('search.html',
adv_searchterm=searchterm, adv_searchterm=searchterm,
pagination=pagination, pagination=pagination,
entries=q[offset:limit_all], entries=q[offset:limit_all],
result_count=result_count, result_count=result_count,
title=_(u"Advanced Search"), page="advsearch") title=_(u"Advanced Search"),
page="advsearch",
thumbnails=thumbnails)
@web.route("/advsearch", methods=['GET']) @web.route("/advsearch", methods=['GET'])
@ -1171,10 +1203,9 @@ def advanced_search_form():
@web.route("/cover/<int:book_id>") @web.route("/cover/<int:book_id>")
@web.route("/cover/<int:book_id>/<int:resolution>")
@login_required_if_no_ano @login_required_if_no_ano
def get_cover(book_id, resolution=1): def get_cover(book_id):
return get_book_cover(book_id, resolution) return get_book_cover(book_id)
@web.route("/cached-cover/<string:cache_id>") @web.route("/cached-cover/<string:cache_id>")
@ -1183,6 +1214,12 @@ def get_cached_cover(cache_id):
return get_cached_book_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)
@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")
@ -1591,6 +1628,7 @@ def show_book(book_id):
if media_format.format.lower() in constants.EXTENSIONS_AUDIO: if media_format.format.lower() in constants.EXTENSIONS_AUDIO:
audioentries.append(media_format.format.lower()) audioentries.append(media_format.format.lower())
thumbnails = get_thumbnails_for_books([entries])
return render_title_template('detail.html', return render_title_template('detail.html',
entry=entries, entry=entries,
audioentries=audioentries, audioentries=audioentries,
@ -1602,7 +1640,8 @@ def show_book(book_id):
is_archived=is_archived, is_archived=is_archived,
kindle_list=kindle_list, kindle_list=kindle_list,
reader_list=reader_list, reader_list=reader_list,
page="book") page="book",
thumbnails=thumbnails)
else: else:
log.debug(u"Error opening eBook. File does not exist or file is not accessible") log.debug(u"Error opening eBook. File does not exist or file is not accessible")
flash(_(u"Error opening eBook. File does not exist or file is not accessible"), category="error") flash(_(u"Error opening eBook. File does not exist or file is not accessible"), category="error")