Display thumbnails on the frontend, generate thumbnails from google drive
This commit is contained in:
		
							parent
							
								
									21fce9a5b5
								
							
						
					
					
						commit
						e48bdf9d5a
					
				
							
								
								
									
										5
									
								
								cps.py
									
									
									
									
									
								
							
							
						
						
									
										5
									
								
								cps.py
									
									
									
									
									
								
							| 
						 | 
				
			
			@ -43,6 +43,7 @@ from cps.gdrive import gdrive
 | 
			
		|||
from cps.editbooks import editbook
 | 
			
		||||
from cps.remotelogin import remotelogin
 | 
			
		||||
from cps.error_handler import init_errorhandler
 | 
			
		||||
from cps.schedule import register_jobs
 | 
			
		||||
 | 
			
		||||
try:
 | 
			
		||||
    from cps.kobo import kobo, get_kobo_activated
 | 
			
		||||
| 
						 | 
				
			
			@ -78,6 +79,10 @@ def main():
 | 
			
		|||
        app.register_blueprint(kobo_auth)
 | 
			
		||||
    if oauth_available:
 | 
			
		||||
        app.register_blueprint(oauth)
 | 
			
		||||
 | 
			
		||||
    # Register scheduled jobs
 | 
			
		||||
    register_jobs()
 | 
			
		||||
 | 
			
		||||
    success = web_server.start()
 | 
			
		||||
    sys.exit(0 if success else 1)
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -36,8 +36,6 @@ from flask_principal import Principal
 | 
			
		|||
from . import config_sql, logger, cache_buster, cli, ub, db
 | 
			
		||||
from .reverseproxy import ReverseProxied
 | 
			
		||||
from .server import WebServer
 | 
			
		||||
from .services.background_scheduler import BackgroundScheduler
 | 
			
		||||
from .tasks.thumbnail import TaskThumbnail
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
mimetypes.init()
 | 
			
		||||
| 
						 | 
				
			
			@ -117,10 +115,6 @@ def create_app():
 | 
			
		|||
                                           config.config_goodreads_api_secret,
 | 
			
		||||
                                           config.config_use_goodreads)
 | 
			
		||||
 | 
			
		||||
    scheduler = BackgroundScheduler()
 | 
			
		||||
    # Generate 100 book cover thumbnails every 5 minutes
 | 
			
		||||
    scheduler.add_task(user=None, task=lambda: TaskThumbnail(config=config, limit=100), trigger='interval', minutes=5)
 | 
			
		||||
 | 
			
		||||
    return app
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -52,7 +52,7 @@ except ImportError:
 | 
			
		|||
 | 
			
		||||
from . import calibre_db
 | 
			
		||||
from .tasks.convert import TaskConvert
 | 
			
		||||
from . import logger, config, get_locale, db, ub
 | 
			
		||||
from . import logger, config, get_locale, db, thumbnails, ub
 | 
			
		||||
from . import gdriveutils as gd
 | 
			
		||||
from .constants import STATIC_DIR as _STATIC_DIR
 | 
			
		||||
from .subproc_wrapper import process_wait
 | 
			
		||||
| 
						 | 
				
			
			@ -538,24 +538,27 @@ def get_cover_on_failure(use_generic_cover):
 | 
			
		|||
        return None
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def get_book_cover(book_id):
 | 
			
		||||
def get_book_cover(book_id, resolution=1):
 | 
			
		||||
    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):
 | 
			
		||||
def get_book_cover_with_uuid(book_uuid, use_generic_cover_on_failure=True):
 | 
			
		||||
    book = calibre_db.get_book_by_uuid(book_uuid)
 | 
			
		||||
    return get_book_cover_internal(book, use_generic_cover_on_failure)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def get_book_cover_internal(book, use_generic_cover_on_failure):
 | 
			
		||||
def get_book_cover_internal(book, use_generic_cover_on_failure, resolution=1, disable_thumbnail=False):
 | 
			
		||||
    if book and book.has_cover:
 | 
			
		||||
        # if thumbnails.cover_thumbnail_exists_for_book(book):
 | 
			
		||||
        #     thumbnail = ub.session.query(ub.Thumbnail).filter(ub.Thumbnail.book_id == book.id).first()
 | 
			
		||||
        #     return send_from_directory(thumbnails.get_thumbnail_cache_dir(), thumbnail.filename)
 | 
			
		||||
        # else:
 | 
			
		||||
            # WorkerThread.add(None, TaskThumbnail(book, _(u'Generating cover thumbnail for: ' + book.title)))
 | 
			
		||||
 | 
			
		||||
        # Send the book cover thumbnail if it exists in cache
 | 
			
		||||
        if not disable_thumbnail:
 | 
			
		||||
            thumbnail = get_book_cover_thumbnail(book, resolution)
 | 
			
		||||
            if thumbnail:
 | 
			
		||||
                if os.path.isfile(thumbnails.get_thumbnail_cache_path(thumbnail)):
 | 
			
		||||
                    return send_from_directory(thumbnails.get_thumbnail_cache_dir(), thumbnail.filename)
 | 
			
		||||
 | 
			
		||||
        # Send the book cover from Google Drive if configured
 | 
			
		||||
        if config.config_use_google_drive:
 | 
			
		||||
            try:
 | 
			
		||||
                if not gd.is_gdrive_ready():
 | 
			
		||||
| 
						 | 
				
			
			@ -569,6 +572,8 @@ def get_book_cover_internal(book, use_generic_cover_on_failure):
 | 
			
		|||
            except Exception as ex:
 | 
			
		||||
                log.debug_or_exception(ex)
 | 
			
		||||
                return get_cover_on_failure(use_generic_cover_on_failure)
 | 
			
		||||
 | 
			
		||||
        # Send the book cover from the Calibre directory
 | 
			
		||||
        else:
 | 
			
		||||
            cover_file_path = os.path.join(config.config_calibre_dir, book.path)
 | 
			
		||||
            if os.path.isfile(os.path.join(cover_file_path, "cover.jpg")):
 | 
			
		||||
| 
						 | 
				
			
			@ -579,6 +584,16 @@ def get_book_cover_internal(book, use_generic_cover_on_failure):
 | 
			
		|||
        return get_cover_on_failure(use_generic_cover_on_failure)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def get_book_cover_thumbnail(book, resolution=1):
 | 
			
		||||
    if book and book.has_cover:
 | 
			
		||||
        return ub.session\
 | 
			
		||||
            .query(ub.Thumbnail)\
 | 
			
		||||
            .filter(ub.Thumbnail.book_id == book.id)\
 | 
			
		||||
            .filter(ub.Thumbnail.resolution == resolution)\
 | 
			
		||||
            .filter(ub.Thumbnail.expiration > datetime.utcnow())\
 | 
			
		||||
            .first()
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
# saves book cover from url
 | 
			
		||||
def save_cover_from_url(url, book_path):
 | 
			
		||||
    try:
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
							
								
								
									
										34
									
								
								cps/schedule.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										34
									
								
								cps/schedule.py
									
									
									
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -0,0 +1,34 @@
 | 
			
		|||
# -*- coding: utf-8 -*-
 | 
			
		||||
 | 
			
		||||
#   This file is part of the Calibre-Web (https://github.com/janeczku/calibre-web)
 | 
			
		||||
#     Copyright (C) 2020 mmonkey
 | 
			
		||||
#
 | 
			
		||||
#   This program is free software: you can redistribute it and/or modify
 | 
			
		||||
#   it under the terms of the GNU General Public License as published by
 | 
			
		||||
#   the Free Software Foundation, either version 3 of the License, or
 | 
			
		||||
#   (at your option) any later version.
 | 
			
		||||
#
 | 
			
		||||
#   This program is distributed in the hope that it will be useful,
 | 
			
		||||
#   but WITHOUT ANY WARRANTY; without even the implied warranty of
 | 
			
		||||
#   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 | 
			
		||||
#   GNU General Public License for more details.
 | 
			
		||||
#
 | 
			
		||||
#   You should have received a copy of the GNU General Public License
 | 
			
		||||
#   along with this program. If not, see <http://www.gnu.org/licenses/>.
 | 
			
		||||
 | 
			
		||||
from __future__ import division, print_function, unicode_literals
 | 
			
		||||
 | 
			
		||||
from . import logger
 | 
			
		||||
from .services.background_scheduler import BackgroundScheduler
 | 
			
		||||
from .tasks.thumbnail import TaskThumbnail
 | 
			
		||||
 | 
			
		||||
log = logger.create()
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def register_jobs():
 | 
			
		||||
    scheduler = BackgroundScheduler()
 | 
			
		||||
 | 
			
		||||
    # Generate 100 book cover thumbnails every 5 minutes
 | 
			
		||||
    scheduler.add_task(user=None, task=lambda: TaskThumbnail(limit=100), trigger='interval', minutes=5)
 | 
			
		||||
 | 
			
		||||
    # TODO: validate thumbnail scheduled task
 | 
			
		||||
| 
						 | 
				
			
			@ -19,11 +19,13 @@
 | 
			
		|||
from __future__ import division, print_function, unicode_literals
 | 
			
		||||
import os
 | 
			
		||||
 | 
			
		||||
from cps import db, logger, ub
 | 
			
		||||
from cps import config, db, gdriveutils, logger, ub
 | 
			
		||||
from cps.constants import CACHE_DIR as _CACHE_DIR
 | 
			
		||||
from cps.services.worker import CalibreTask
 | 
			
		||||
from cps.thumbnails import THUMBNAIL_RESOLUTION_1X, THUMBNAIL_RESOLUTION_2X
 | 
			
		||||
from datetime import datetime, timedelta
 | 
			
		||||
from sqlalchemy import func
 | 
			
		||||
from urllib.request import urlopen
 | 
			
		||||
 | 
			
		||||
try:
 | 
			
		||||
    from wand.image import Image
 | 
			
		||||
| 
						 | 
				
			
			@ -31,14 +33,10 @@ try:
 | 
			
		|||
except (ImportError, RuntimeError) as e:
 | 
			
		||||
    use_IM = False
 | 
			
		||||
 | 
			
		||||
THUMBNAIL_RESOLUTION_1X = 1.0
 | 
			
		||||
THUMBNAIL_RESOLUTION_2X = 2.0
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class TaskThumbnail(CalibreTask):
 | 
			
		||||
    def __init__(self, config, limit=100, task_message=u'Generating cover thumbnails'):
 | 
			
		||||
    def __init__(self, limit=100, task_message=u'Generating cover thumbnails'):
 | 
			
		||||
        super(TaskThumbnail, self).__init__(task_message)
 | 
			
		||||
        self.config = config
 | 
			
		||||
        self.limit = limit
 | 
			
		||||
        self.log = logger.create()
 | 
			
		||||
        self.app_db_session = ub.get_new_session_instance()
 | 
			
		||||
| 
						 | 
				
			
			@ -114,17 +112,39 @@ class TaskThumbnail(CalibreTask):
 | 
			
		|||
 | 
			
		||||
    def generate_book_thumbnail(self, book, thumbnail):
 | 
			
		||||
        if book and thumbnail:
 | 
			
		||||
            if self.config.config_use_google_drive:
 | 
			
		||||
                self.log.info('google drive thumbnail')
 | 
			
		||||
            else:
 | 
			
		||||
                book_cover_filepath = os.path.join(self.config.config_calibre_dir, book.path, 'cover.jpg')
 | 
			
		||||
                if os.path.isfile(book_cover_filepath):
 | 
			
		||||
                    with Image(filename=book_cover_filepath) as img:
 | 
			
		||||
            if config.config_use_google_drive:
 | 
			
		||||
                if not gdriveutils.is_gdrive_ready():
 | 
			
		||||
                    raise Exception('Google Drive is configured but not ready')
 | 
			
		||||
 | 
			
		||||
                web_content_link = gdriveutils.get_cover_via_gdrive(book.path)
 | 
			
		||||
                if not web_content_link:
 | 
			
		||||
                    raise Exception('Google Drive cover url not found')
 | 
			
		||||
 | 
			
		||||
                stream = None
 | 
			
		||||
                try:
 | 
			
		||||
                    stream = urlopen(web_content_link)
 | 
			
		||||
                    with Image(file=stream) as img:
 | 
			
		||||
                        height = self.get_thumbnail_height(thumbnail)
 | 
			
		||||
                        if img.height > height:
 | 
			
		||||
                            width = self.get_thumbnail_width(height, img)
 | 
			
		||||
                            img.resize(width=width, height=height, filter='lanczos')
 | 
			
		||||
                            img.save(filename=self.get_thumbnail_cache_path(thumbnail))
 | 
			
		||||
                except Exception as ex:
 | 
			
		||||
                    # Bubble exception to calling function
 | 
			
		||||
                    raise ex
 | 
			
		||||
                finally:
 | 
			
		||||
                    stream.close()
 | 
			
		||||
            else:
 | 
			
		||||
                book_cover_filepath = os.path.join(config.config_calibre_dir, book.path, 'cover.jpg')
 | 
			
		||||
                if not os.path.isfile(book_cover_filepath):
 | 
			
		||||
                    raise Exception('Book cover file not found')
 | 
			
		||||
 | 
			
		||||
                with Image(filename=book_cover_filepath) as img:
 | 
			
		||||
                    height = self.get_thumbnail_height(thumbnail)
 | 
			
		||||
                    if img.height > height:
 | 
			
		||||
                        width = self.get_thumbnail_width(height, img)
 | 
			
		||||
                        img.resize(width=width, height=height, filter='lanczos')
 | 
			
		||||
                        img.save(filename=self.get_thumbnail_cache_path(thumbnail))
 | 
			
		||||
 | 
			
		||||
    def get_thumbnail_height(self, thumbnail):
 | 
			
		||||
        return int(225 * thumbnail.resolution)
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -36,7 +36,7 @@
 | 
			
		|||
    <div id="books" class="col-sm-3 col-lg-2 col-xs-6 book">
 | 
			
		||||
      <div class="cover">
 | 
			
		||||
        <a href="{{ url_for('web.show_book', book_id=entry.id) }}">
 | 
			
		||||
          <img src="{{ url_for('web.get_cover', book_id=entry.id) }}" />
 | 
			
		||||
            {{ book_cover_image(entry.id, entry.title) }}
 | 
			
		||||
        </a>
 | 
			
		||||
      </div>
 | 
			
		||||
      <div class="meta">
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
							
								
								
									
										8
									
								
								cps/templates/book_cover.html
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										8
									
								
								cps/templates/book_cover.html
									
									
									
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -0,0 +1,8 @@
 | 
			
		|||
{% macro book_cover_image(book_id, book_title) -%}
 | 
			
		||||
    <img
 | 
			
		||||
        srcset="{{ url_for('web.get_cover', book_id=book_id, resolution=1) }} 1x,
 | 
			
		||||
                {{ url_for('web.get_cover', book_id=book_id, resolution=2) }} 2x"
 | 
			
		||||
        src="{{ url_for('web.get_cover', book_id=book_id) }}"
 | 
			
		||||
        alt="{{ book_title }}"
 | 
			
		||||
    />
 | 
			
		||||
{%- endmacro %}
 | 
			
		||||
| 
						 | 
				
			
			@ -1,9 +1,11 @@
 | 
			
		|||
{% from 'book_cover.html' import book_cover_image %}
 | 
			
		||||
{% extends "layout.html" %}
 | 
			
		||||
{% block body %}
 | 
			
		||||
{% if book %}
 | 
			
		||||
  <div class="col-sm-3 col-lg-3 col-xs-12">
 | 
			
		||||
    <div class="cover">
 | 
			
		||||
        <img src="{{ url_for('web.get_cover', book_id=book.id, edit=1|uuidfilter)  }}" alt="{{ book.title }}"/>
 | 
			
		||||
        {{ book_cover_image(book.id, book.title) }}
 | 
			
		||||
<!--        <img src="{{ url_for('web.get_cover', book_id=book.id, edit=1|uuidfilter)  }}" alt="{{ book.title }}"/>-->
 | 
			
		||||
    </div>
 | 
			
		||||
{% if g.user.role_delete_books() %}
 | 
			
		||||
    <div class="text-center">
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -4,7 +4,8 @@
 | 
			
		|||
  <div class="row">
 | 
			
		||||
    <div class="col-sm-3 col-lg-3 col-xs-5">
 | 
			
		||||
      <div class="cover">
 | 
			
		||||
          <img src="{{ url_for('web.get_cover', book_id=entry.id, edit=1|uuidfilter) }}" alt="{{ entry.title }}" />
 | 
			
		||||
          {{ book_cover_image(entry.id, entry.title) }}
 | 
			
		||||
<!--          <img src="{{ url_for('web.get_cover', book_id=entry.id, edit=1|uuidfilter) }}" alt="{{ entry.title }}" />-->
 | 
			
		||||
      </div>
 | 
			
		||||
    </div>
 | 
			
		||||
    <div class="col-sm-9 col-lg-9 book-meta">
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -1,3 +1,4 @@
 | 
			
		|||
{% from 'book_cover.html' import book_cover_image %}
 | 
			
		||||
{% extends "layout.html" %}
 | 
			
		||||
{% block body %}
 | 
			
		||||
<div class="discover load-more">
 | 
			
		||||
| 
						 | 
				
			
			@ -8,7 +9,7 @@
 | 
			
		|||
      <div class="cover">
 | 
			
		||||
        {% 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">
 | 
			
		||||
            <img src="{{ url_for('web.get_cover', book_id=entry.id) }}" alt="{{ entry.title }}" />
 | 
			
		||||
              {{ book_cover_image(entry.id, entry.title) }}
 | 
			
		||||
          </a>
 | 
			
		||||
        {% endif %}
 | 
			
		||||
      </div>
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -1,3 +1,4 @@
 | 
			
		|||
{% from 'book_cover.html' import book_cover_image %}
 | 
			
		||||
<div class="container-fluid">
 | 
			
		||||
  {% block body %}{% endblock %}
 | 
			
		||||
</div>
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -1,3 +1,4 @@
 | 
			
		|||
{% from 'book_cover.html' import book_cover_image %}
 | 
			
		||||
{% extends "layout.html" %}
 | 
			
		||||
{% block body %}
 | 
			
		||||
<h1 class="{{page}}">{{_(title)}}</h1>
 | 
			
		||||
| 
						 | 
				
			
			@ -28,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="cover">
 | 
			
		||||
                      <a href="{{url_for('web.books_list', data=data, sort_param='new', book_id=entry[0].series[0].id )}}">
 | 
			
		||||
                          <img src="{{ url_for('web.get_cover', book_id=entry[0].id) }}" alt="{{ entry[0].name }}"/>
 | 
			
		||||
                          {{ book_cover_image(entry[0].id, entry[0].name) }}
 | 
			
		||||
                          <span class="badge">{{entry.count}}</span>
 | 
			
		||||
                      </a>
 | 
			
		||||
                  </div>
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -1,3 +1,4 @@
 | 
			
		|||
{% from 'book_cover.html' import book_cover_image %}
 | 
			
		||||
{% extends "layout.html" %}
 | 
			
		||||
{% block body %}
 | 
			
		||||
{% if g.user.show_detail_random() %}
 | 
			
		||||
| 
						 | 
				
			
			@ -8,7 +9,7 @@
 | 
			
		|||
    <div class="col-sm-3 col-lg-2 col-xs-6 book" id="books_rand">
 | 
			
		||||
      <div class="cover">
 | 
			
		||||
          <a href="{{ url_for('web.show_book', book_id=entry.id) }}" data-toggle="modal" data-target="#bookDetailsModal" data-remote="false">
 | 
			
		||||
              <img src="{{ url_for('web.get_cover', book_id=entry.id) }}" alt="{{ entry.title }}" />
 | 
			
		||||
              {{ book_cover_image(entry.id, entry.title) }}
 | 
			
		||||
          </a>
 | 
			
		||||
      </div>
 | 
			
		||||
      <div class="meta">
 | 
			
		||||
| 
						 | 
				
			
			@ -82,7 +83,7 @@
 | 
			
		|||
    <div class="col-sm-3 col-lg-2 col-xs-6 book" id="books">
 | 
			
		||||
      <div class="cover">
 | 
			
		||||
          <a href="{{ url_for('web.show_book', book_id=entry.id) }}" data-toggle="modal" data-target="#bookDetailsModal" data-remote="false">
 | 
			
		||||
              <img src="{{ url_for('web.get_cover', book_id=entry.id) }}" alt="{{ entry.title }}"/>
 | 
			
		||||
              {{ book_cover_image(entry.id, entry.title) }}
 | 
			
		||||
          </a>
 | 
			
		||||
      </div>
 | 
			
		||||
      <div class="meta">
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -1,4 +1,5 @@
 | 
			
		|||
{% from 'modal_dialogs.html' import restrict_modal, delete_book, filechooser_modal %}
 | 
			
		||||
{% from 'book_cover.html' import book_cover_image %}
 | 
			
		||||
<!DOCTYPE html>
 | 
			
		||||
<html lang="{{ g.user.locale }}">
 | 
			
		||||
  <head>
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -1,3 +1,4 @@
 | 
			
		|||
{% from 'book_cover.html' import book_cover_image %}
 | 
			
		||||
{% extends "layout.html" %}
 | 
			
		||||
{% block body %}
 | 
			
		||||
<div class="discover">
 | 
			
		||||
| 
						 | 
				
			
			@ -43,7 +44,7 @@
 | 
			
		|||
      <div class="cover">
 | 
			
		||||
        {% 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">
 | 
			
		||||
            <img src="{{ url_for('web.get_cover', book_id=entry.id) }}" alt="{{ entry.title }}" />
 | 
			
		||||
               {{ book_cover_image(entry.id, entry.title) }}
 | 
			
		||||
          </a>
 | 
			
		||||
        {% endif %}
 | 
			
		||||
      </div>
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -1,3 +1,4 @@
 | 
			
		|||
{% from 'book_cover.html' import book_cover_image %}
 | 
			
		||||
{% extends "layout.html" %}
 | 
			
		||||
{% block body %}
 | 
			
		||||
<div class="discover">
 | 
			
		||||
| 
						 | 
				
			
			@ -30,7 +31,7 @@
 | 
			
		|||
    <div class="col-sm-3 col-lg-2 col-xs-6 book">
 | 
			
		||||
      <div class="cover">
 | 
			
		||||
            <a href="{{ url_for('web.show_book', book_id=entry.id) }}" data-toggle="modal" data-target="#bookDetailsModal" data-remote="false">
 | 
			
		||||
              <img src="{{ url_for('web.get_cover', book_id=entry.id) }}" alt="{{ entry.title }}" />
 | 
			
		||||
                {{ book_cover_image(entry.id, entry.title) }}
 | 
			
		||||
            </a>
 | 
			
		||||
      </div>
 | 
			
		||||
      <div class="meta">
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -21,13 +21,11 @@ import os
 | 
			
		|||
 | 
			
		||||
from . import logger, ub
 | 
			
		||||
from .constants import CACHE_DIR as _CACHE_DIR
 | 
			
		||||
from .services.worker import WorkerThread
 | 
			
		||||
from .tasks.thumbnail import TaskThumbnail
 | 
			
		||||
 | 
			
		||||
from datetime import datetime
 | 
			
		||||
 | 
			
		||||
THUMBNAIL_RESOLUTION_1X = 1.0
 | 
			
		||||
THUMBNAIL_RESOLUTION_2X = 2.0
 | 
			
		||||
THUMBNAIL_RESOLUTION_1X = 1
 | 
			
		||||
THUMBNAIL_RESOLUTION_2X = 2
 | 
			
		||||
 | 
			
		||||
log = logger.create()
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -35,17 +33,14 @@ log = logger.create()
 | 
			
		|||
def get_thumbnail_cache_dir():
 | 
			
		||||
    if not os.path.isdir(_CACHE_DIR):
 | 
			
		||||
        os.makedirs(_CACHE_DIR)
 | 
			
		||||
 | 
			
		||||
    if not os.path.isdir(os.path.join(_CACHE_DIR, 'thumbnails')):
 | 
			
		||||
        os.makedirs(os.path.join(_CACHE_DIR, 'thumbnails'))
 | 
			
		||||
 | 
			
		||||
    return os.path.join(_CACHE_DIR, 'thumbnails')
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def get_thumbnail_cache_path(thumbnail):
 | 
			
		||||
    if thumbnail:
 | 
			
		||||
        return os.path.join(get_thumbnail_cache_dir(), thumbnail.filename)
 | 
			
		||||
 | 
			
		||||
    return None
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -40,7 +40,7 @@ except ImportError:
 | 
			
		|||
        oauth_support = False
 | 
			
		||||
from sqlalchemy import create_engine, exc, exists, event
 | 
			
		||||
from sqlalchemy import Column, ForeignKey
 | 
			
		||||
from sqlalchemy import String, Integer, SmallInteger, Boolean, DateTime, Float, JSON, Numeric
 | 
			
		||||
from sqlalchemy import String, Integer, SmallInteger, Boolean, DateTime, Float, JSON
 | 
			
		||||
from sqlalchemy.ext.declarative import declarative_base
 | 
			
		||||
from sqlalchemy.ext.hybrid import hybrid_property
 | 
			
		||||
from sqlalchemy.orm.attributes import flag_modified
 | 
			
		||||
| 
						 | 
				
			
			@ -442,7 +442,7 @@ class Thumbnail(Base):
 | 
			
		|||
    book_id = Column(Integer)
 | 
			
		||||
    uuid = Column(String, default=lambda: str(uuid.uuid4()), unique=True)
 | 
			
		||||
    format = Column(String, default='jpeg')
 | 
			
		||||
    resolution = Column(Numeric(precision=2, scale=1, asdecimal=False), default=1.0)
 | 
			
		||||
    resolution = Column(SmallInteger, default=1)
 | 
			
		||||
    expiration = Column(DateTime, default=lambda: datetime.datetime.utcnow() + datetime.timedelta(days=30))
 | 
			
		||||
 | 
			
		||||
    @hybrid_property
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
							
								
								
									
										11
									
								
								cps/web.py
									
									
									
									
									
								
							
							
						
						
									
										11
									
								
								cps/web.py
									
									
									
									
									
								
							| 
						 | 
				
			
			@ -1171,14 +1171,17 @@ def advanced_search_form():
 | 
			
		|||
 | 
			
		||||
 | 
			
		||||
@web.route("/cover/<int:book_id>")
 | 
			
		||||
@web.route("/cover/<int:book_id>/<int:resolution>")
 | 
			
		||||
@login_required_if_no_ano
 | 
			
		||||
def get_cover(book_id):
 | 
			
		||||
    return get_book_cover(book_id)
 | 
			
		||||
def get_cover(book_id, resolution=1):
 | 
			
		||||
    return get_book_cover(book_id, resolution)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@web.route("/robots.txt")
 | 
			
		||||
def get_robots():
 | 
			
		||||
    return send_from_directory(constants.STATIC_DIR, "robots.txt")
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@web.route("/show/<int:book_id>/<book_format>", defaults={'anyname': 'None'})
 | 
			
		||||
@web.route("/show/<int:book_id>/<book_format>/<anyname>")
 | 
			
		||||
@login_required_if_no_ano
 | 
			
		||||
| 
						 | 
				
			
			@ -1205,7 +1208,6 @@ def serve_book(book_id, book_format, anyname):
 | 
			
		|||
        return send_from_directory(os.path.join(config.config_calibre_dir, book.path), data.name + "." + book_format)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@web.route("/download/<int:book_id>/<book_format>", defaults={'anyname': 'None'})
 | 
			
		||||
@web.route("/download/<int:book_id>/<book_format>/<anyname>")
 | 
			
		||||
@login_required_if_no_ano
 | 
			
		||||
| 
						 | 
				
			
			@ -1387,9 +1389,6 @@ def logout():
 | 
			
		|||
    return redirect(url_for('web.login'))
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
# ################################### Users own configuration #########################################################
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
		Loading…
	
		Reference in New Issue
	
	Block a user