This commit is contained in:
cbartondock 2021-07-12 12:08:55 -04:00
commit 6ef792d65d
26 changed files with 131 additions and 77 deletions

View File

@ -40,7 +40,6 @@ from sqlalchemy.exc import IntegrityError, OperationalError, InvalidRequestError
from sqlalchemy.sql.expression import func, or_, text from sqlalchemy.sql.expression import func, or_, text
from . import constants, logger, helper, services from . import constants, logger, helper, services
# from .cli import filepicker
from . import db, calibre_db, ub, web_server, get_locale, config, updater_thread, babel, gdriveutils from . import db, calibre_db, ub, web_server, get_locale, config, updater_thread, babel, gdriveutils
from .helper import check_valid_domain, send_test_mail, reset_password, generate_password_hash, check_email, \ from .helper import check_valid_domain, send_test_mail, reset_password, generate_password_hash, check_email, \
valid_email, check_username valid_email, check_username

View File

@ -39,9 +39,7 @@ def _get_command_version(path, pattern, argument=None):
if argument: if argument:
command.append(argument) command.append(argument)
try: try:
for line in process_wait(command): return process_wait(command, pattern=pattern).string
if re.search(pattern, line):
return line
except Exception as ex: except Exception as ex:
log.warning("%s: %s", path, ex) log.warning("%s: %s", path, ex)
return _EXECUTION_ERROR return _EXECUTION_ERROR

View File

@ -22,10 +22,6 @@ import glob
import zipfile import zipfile
import json import json
from io import BytesIO from io import BytesIO
try:
from StringIO import StringIO
except ImportError:
from io import StringIO
import os import os
@ -38,9 +34,9 @@ log = logger.create()
def assemble_logfiles(file_name): def assemble_logfiles(file_name):
log_list = sorted(glob.glob(file_name + '*'), reverse=True) log_list = sorted(glob.glob(file_name + '*'), reverse=True)
wfd = StringIO() wfd = BytesIO()
for f in log_list: for f in log_list:
with open(f, 'r') as fd: with open(f, 'rb') as fd:
shutil.copyfileobj(fd, wfd) shutil.copyfileobj(fd, wfd)
wfd.seek(0) wfd.seek(0)
if int(__version__.split('.')[0]) < 2: if int(__version__.split('.')[0]) < 2:

View File

@ -711,12 +711,11 @@ def check_unrar(unrarLocation):
if sys.version_info < (3, 0): if sys.version_info < (3, 0):
unrarLocation = unrarLocation.encode(sys.getfilesystemencoding()) unrarLocation = unrarLocation.encode(sys.getfilesystemencoding())
unrarLocation = [unrarLocation] unrarLocation = [unrarLocation]
for lines in process_wait(unrarLocation): value = process_wait(unrarLocation, pattern='UNRAR (.*) freeware')
value = re.search('UNRAR (.*) freeware', lines, re.IGNORECASE) if value:
if value: version = value.group(1)
version = value.group(1) log.debug("unrar version %s", version)
log.debug("unrar version %s", version)
break
except (OSError, UnicodeDecodeError) as err: except (OSError, UnicodeDecodeError) as err:
log.debug_or_exception(err) log.debug_or_exception(err)
return _('Error excecuting UnRar') return _('Error excecuting UnRar')

View File

@ -113,10 +113,8 @@ def yesno(value, yes, no):
@jinjia.app_template_filter('formatfloat') @jinjia.app_template_filter('formatfloat')
def formatfloat(value, decimals=1): def formatfloat(value, decimals=1):
formatedstring = '%d' % value value = 0 if not value else value
if (value % 1) != 0: return ('{0:.' + str(decimals) + 'f}').format(value).rstrip('0').rstrip('.')
formatedstring = ('%s.%d' % (formatedstring, (value % 1) * 10**decimals)).rstrip('0')
return formatedstring
@jinjia.app_template_filter('formatseriesindex') @jinjia.app_template_filter('formatseriesindex')

View File

@ -44,6 +44,8 @@ from werkzeug.datastructures import Headers
from sqlalchemy import func from sqlalchemy import func
from sqlalchemy.sql.expression import and_, or_ from sqlalchemy.sql.expression import and_, or_
from sqlalchemy.exc import StatementError from sqlalchemy.exc import StatementError
from sqlalchemy import __version__ as sql_version
from sqlalchemy.sql import select
import requests import requests
from . import config, logger, kobo_auth, db, calibre_db, helper, shelf as shelf_lib, ub from . import config, logger, kobo_auth, db, calibre_db, helper, shelf as shelf_lib, ub
@ -64,6 +66,7 @@ kobo_auth.register_url_value_preprocessor(kobo)
log = logger.create() log = logger.create()
sql2 = ([int(x) for x in sql_version.split('.')] >= [2,0,0])
def get_store_url_for_current_request(): def get_store_url_for_current_request():
# Programmatically modify the current url to point to the official Kobo store # Programmatically modify the current url to point to the official Kobo store
@ -136,6 +139,7 @@ def convert_to_kobo_timestamp_string(timestamp):
def HandleSyncRequest(): def HandleSyncRequest():
sync_token = SyncToken.SyncToken.from_headers(request.headers) sync_token = SyncToken.SyncToken.from_headers(request.headers)
log.info("Kobo library sync request received.") log.info("Kobo library sync request received.")
log.debug("SyncToken: {}".format(sync_token))
if not current_app.wsgi_app.is_proxied: if not current_app.wsgi_app.is_proxied:
log.debug('Kobo: Received unproxied request, changed request port to external server port') log.debug('Kobo: Received unproxied request, changed request port to external server port')
@ -153,14 +157,19 @@ def HandleSyncRequest():
calibre_db.reconnect_db(config, ub.app_DB_path) calibre_db.reconnect_db(config, ub.app_DB_path)
only_kobo_shelves = current_user.kobo_only_shelves_sync only_kobo_shelves = current_user.kobo_only_shelves_sync
# calibre_db.session.query(ub.Shelf).filter(ub.Shelf.user_id == current_user.id).filter(ub.Shelf.kobo_sync).count() > 0
if only_kobo_shelves: if only_kobo_shelves:
changed_entries = ( if sql2:
calibre_db.session.query(db.Books, changed_entries = select(db.Books,
ub.ArchivedBook.last_modified, ub.ArchivedBook.last_modified,
ub.BookShelf.date_added, ub.BookShelf.date_added,
ub.ArchivedBook.is_archived) ub.ArchivedBook.is_archived)
else:
changed_entries = calibre_db.session.query(db.Books,
ub.ArchivedBook.last_modified,
ub.BookShelf.date_added,
ub.ArchivedBook.is_archived)
changed_entries = (changed_entries
.join(db.Data).outerjoin(ub.ArchivedBook, db.Books.id == ub.ArchivedBook.book_id) .join(db.Data).outerjoin(ub.ArchivedBook, db.Books.id == ub.ArchivedBook.book_id)
.filter(or_(db.Books.last_modified > sync_token.books_last_modified, .filter(or_(db.Books.last_modified > sync_token.books_last_modified,
ub.BookShelf.date_added > sync_token.books_last_modified)) ub.BookShelf.date_added > sync_token.books_last_modified))
@ -174,8 +183,13 @@ def HandleSyncRequest():
.distinct() .distinct()
) )
else: else:
changed_entries = ( if sql2:
calibre_db.session.query(db.Books, ub.ArchivedBook.last_modified, ub.ArchivedBook.is_archived) changed_entries = select(db.Books, ub.ArchivedBook.last_modified, ub.ArchivedBook.is_archived)
else:
changed_entries = calibre_db.session.query(db.Books,
ub.ArchivedBook.last_modified,
ub.ArchivedBook.is_archived)
changed_entries = (changed_entries
.join(db.Data).outerjoin(ub.ArchivedBook, db.Books.id == ub.ArchivedBook.book_id) .join(db.Data).outerjoin(ub.ArchivedBook, db.Books.id == ub.ArchivedBook.book_id)
.filter(db.Books.last_modified > sync_token.books_last_modified) .filter(db.Books.last_modified > sync_token.books_last_modified)
.filter(calibre_db.common_filters()) .filter(calibre_db.common_filters())
@ -188,7 +202,11 @@ def HandleSyncRequest():
changed_entries = changed_entries.filter(db.Books.id > sync_token.books_last_id) changed_entries = changed_entries.filter(db.Books.id > sync_token.books_last_id)
reading_states_in_new_entitlements = [] reading_states_in_new_entitlements = []
for book in changed_entries.limit(SYNC_ITEM_LIMIT): if sql2:
books = calibre_db.session.execute(changed_entries.limit(SYNC_ITEM_LIMIT))
else:
books = changed_entries.limit(SYNC_ITEM_LIMIT)
for book in books:
formats = [data.format for data in book.Books.data] formats = [data.format for data in book.Books.data]
if not 'KEPUB' in formats and config.config_kepubifypath and 'EPUB' in formats: if not 'KEPUB' in formats and config.config_kepubifypath and 'EPUB' in formats:
helper.convert_book_format(book.Books.id, config.config_calibre_dir, 'EPUB', 'KEPUB', current_user.name) helper.convert_book_format(book.Books.id, config.config_calibre_dir, 'EPUB', 'KEPUB', current_user.name)
@ -228,18 +246,28 @@ def HandleSyncRequest():
new_books_last_created = max(ts_created, new_books_last_created) new_books_last_created = max(ts_created, new_books_last_created)
max_change = changed_entries.from_self().filter(ub.ArchivedBook.is_archived)\ if sql2:
.order_by(func.datetime(ub.ArchivedBook.last_modified).desc()).first() max_change = calibre_db.session.execute(changed_entries
.filter(ub.ArchivedBook.is_archived)
.order_by(func.datetime(ub.ArchivedBook.last_modified).desc()))\
.columns(db.Books).first()
else:
max_change = changed_entries.from_self().filter(ub.ArchivedBook.is_archived) \
.order_by(func.datetime(ub.ArchivedBook.last_modified).desc()).first()
max_change = max_change.last_modified if max_change else new_archived_last_modified max_change = max_change.last_modified if max_change else new_archived_last_modified
new_archived_last_modified = max(new_archived_last_modified, max_change) new_archived_last_modified = max(new_archived_last_modified, max_change)
# no. of books returned # no. of books returned
book_count = changed_entries.count() if sql2:
entries = calibre_db.session.execute(changed_entries).all()
book_count = len(entries)
else:
entries = changed_entries.all()
book_count = changed_entries.count()
# last entry: # last entry:
books_last_id = changed_entries.all()[-1].Books.id or -1 if book_count else -1 books_last_id = entries[-1].Books.id or -1 if book_count else -1
# generate reading state data # generate reading state data
changed_reading_states = ub.session.query(ub.KoboReadingState) changed_reading_states = ub.session.query(ub.KoboReadingState)
@ -303,6 +331,7 @@ def generate_sync_response(sync_token, sync_results, set_cont=False):
extra_headers["x-kobo-sync"] = "continue" extra_headers["x-kobo-sync"] = "continue"
sync_token.to_headers(extra_headers) sync_token.to_headers(extra_headers)
log.debug("Kobo Sync Content: {}".format(sync_results))
response = make_response(jsonify(sync_results), extra_headers) response = make_response(jsonify(sync_results), extra_headers)
return response return response
@ -668,12 +697,23 @@ def sync_shelves(sync_token, sync_results, only_kobo_shelves=False):
}) })
extra_filters.append(ub.Shelf.kobo_sync) extra_filters.append(ub.Shelf.kobo_sync)
for shelf in ub.session.query(ub.Shelf).outerjoin(ub.BookShelf).filter( if sql2:
or_(func.datetime(ub.Shelf.last_modified) > sync_token.tags_last_modified, shelflist = ub.session.execute(select(ub.Shelf).outerjoin(ub.BookShelf).filter(
func.datetime(ub.BookShelf.date_added) > sync_token.tags_last_modified), or_(func.datetime(ub.Shelf.last_modified) > sync_token.tags_last_modified,
ub.Shelf.user_id == current_user.id, func.datetime(ub.BookShelf.date_added) > sync_token.tags_last_modified),
*extra_filters ub.Shelf.user_id == current_user.id,
).distinct().order_by(func.datetime(ub.Shelf.last_modified).asc()): # .columns(ub.Shelf): *extra_filters
).distinct().order_by(func.datetime(ub.Shelf.last_modified).asc())).columns(ub.Shelf)
else:
shelflist = ub.session.query(ub.Shelf).outerjoin(ub.BookShelf).filter(
or_(func.datetime(ub.Shelf.last_modified) > sync_token.tags_last_modified,
func.datetime(ub.BookShelf.date_added) > sync_token.tags_last_modified),
ub.Shelf.user_id == current_user.id,
*extra_filters
).distinct().order_by(func.datetime(ub.Shelf.last_modified).asc())
for shelf in shelflist:
if not shelf_lib.check_shelf_view_permissions(shelf): if not shelf_lib.check_shelf_view_permissions(shelf):
continue continue

View File

@ -183,3 +183,12 @@ class SyncToken:
}, },
} }
return b64encode_json(token) return b64encode_json(token)
def __str__(self):
return "{},{},{},{},{},{},{}".format(self.raw_kobo_store_token,
self.books_last_created,
self.books_last_modified,
self.archive_last_modified,
self.reading_state_last_modified,
self.tags_last_modified,
self.books_last_id)

View File

@ -3291,7 +3291,6 @@ div.btn-group[role=group][aria-label="Download, send to Kindle, reading"] .dropd
transform-origin: center top; transform-origin: center top;
border: 0; border: 0;
left: 0 !important; left: 0 !important;
max-height: 80%;
overflow-y: auto; overflow-y: auto;
} }

View File

@ -413,7 +413,11 @@ if($("body.advsearch").length > 0) {
}); });
$('#add-to-shelf').height("40px"); $('#add-to-shelf').height("40px");
function search_dropdownToggle() { function search_dropdownToggle() {
topPos = $("#add-to-shelf").offset().top-20; if( $("#add-to-shelf").length) {
topPos = $("#add-to-shelf").offset().top - 20;
} else {
topPos = 0
}
if ($('div[aria-label="Add to shelves"]').length > 0) { if ($('div[aria-label="Add to shelves"]').length > 0) {
position = $('div[aria-label="Add to shelves"]').offset().left position = $('div[aria-label="Add to shelves"]').offset().left

View File

@ -20,7 +20,7 @@ from __future__ import division, print_function, unicode_literals
import sys import sys
import os import os
import subprocess import subprocess
import re
def process_open(command, quotes=(), env=None, sout=subprocess.PIPE, serr=subprocess.PIPE, newlines=True): def process_open(command, quotes=(), env=None, sout=subprocess.PIPE, serr=subprocess.PIPE, newlines=True):
# Linux py2.7 encode as list without quotes no empty element for parameters # Linux py2.7 encode as list without quotes no empty element for parameters
@ -44,12 +44,19 @@ def process_open(command, quotes=(), env=None, sout=subprocess.PIPE, serr=subpro
return subprocess.Popen(exc_command, shell=False, stdout=sout, stderr=serr, universal_newlines=newlines, env=env) # nosec return subprocess.Popen(exc_command, shell=False, stdout=sout, stderr=serr, universal_newlines=newlines, env=env) # nosec
def process_wait(command, serr=subprocess.PIPE): def process_wait(command, serr=subprocess.PIPE, pattern=""):
# Run command, wait for process to terminate, and return an iterator over lines of its output. # Run command, wait for process to terminate, and return an iterator over lines of its output.
newlines = os.name != 'nt' newlines = os.name != 'nt'
ret_val = ""
p = process_open(command, serr=serr, newlines=newlines) p = process_open(command, serr=serr, newlines=newlines)
p.wait() p.wait()
for line in p.stdout.readlines(): for line in p.stdout.readlines():
if isinstance(line, bytes): if isinstance(line, bytes):
line = line.decode('utf-8') line = line.decode('utf-8', errors="ignore")
yield line match = re.search(pattern, line, re.IGNORECASE)
if match and ret_val == "":
ret_val = match
break
p.stdout.close()
p.stderr.close()
return ret_val

View File

@ -5,7 +5,7 @@
{% if author is not none %} {% if author is not none %}
<section class="author-bio"> <section class="author-bio">
{%if author.image_url is not none %} {%if author.image_url is not none %}
<img src="{{author.image_url}}" alt="{{author.name|safe}}" class="author-photo pull-left"> <img title="{{author.name|safe}}" src="{{author.image_url}}" alt="{{author.name|safe}}" class="author-photo pull-left">
{% endif %} {% endif %}
{%if author.about is not none %} {%if author.about is not none %}
@ -37,14 +37,14 @@
<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) }}">
<span class="img"> <span class="img">
<img src="{{ url_for('web.get_cover', book_id=entry.id) }}" /> <img title="{{author.name|safe}}" src="{{ url_for('web.get_cover', book_id=entry.id) }}" />
{% if entry.id in read_book_ids %}<span class="badge read glyphicon glyphicon-ok"></span>{% endif %} {% if entry.id in read_book_ids %}<span class="badge read glyphicon glyphicon-ok"></span>{% endif %}
</span> </span>
</a> </a>
</div> </div>
<div class="meta"> <div class="meta">
<a href="{{ url_for('web.show_book', book_id=entry.id) }}"> <a href="{{ url_for('web.show_book', book_id=entry.id) }}">
<p class="title">{{entry.title|shortentitle}}</p> <p title="{{ entry.title }}" class="title">{{entry.title|shortentitle}}</p>
</a> </a>
<p class="author"> <p class="author">
{% for author in entry.authors %} {% for author in entry.authors %}
@ -104,11 +104,11 @@
<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="https://www.goodreads.com/book/show/{{ entry.gid['#text'] }}" target="_blank" rel="noopener"> <a href="https://www.goodreads.com/book/show/{{ entry.gid['#text'] }}" target="_blank" rel="noopener">
<img src="{{ entry.image_url }}" /> <img title="{{entry.title}}" src="{{ entry.image_url }}" />
</a> </a>
</div> </div>
<div class="meta"> <div class="meta">
<p class="title">{{entry.title|shortentitle}}</p> <p title="{{ entry.title }}" class="title">{{entry.title|shortentitle}}</p>
<p class="author"> <p class="author">
{% for author in entry.authors %} {% for author in entry.authors %}
{% if loop.index > g.config_authors_max and g.config_authors_max != 0 %} {% if loop.index > g.config_authors_max and g.config_authors_max != 0 %}

View File

@ -3,7 +3,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">
<img id="detailcover" src="{{ url_for('web.get_cover', book_id=book.id, edit=1|uuidfilter) }}" alt="{{ book.title }}"/> <img id="detailcover" title="{{book.title}}" src="{{ url_for('web.get_cover', book_id=book.id, edit=1|uuidfilter) }}" alt="{{ book.title }}"/>
</div> </div>
{% if g.user.role_delete_books() %} {% if g.user.role_delete_books() %}
<div class="text-center"> <div class="text-center">

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">
<img id="detailcover" src="{{ url_for('web.get_cover', book_id=entry.id, edit=1|uuidfilter) }}" alt="{{ entry.title }}" /> <img id="detailcover" title="{{entry.title}}" src="{{ url_for('web.get_cover', book_id=entry.id, edit=1|uuidfilter) }}" alt="{{ entry.title }}" />
</div> </div>
</div> </div>
<div class="col-sm-9 col-lg-9 book-meta"> <div class="col-sm-9 col-lg-9 book-meta">
@ -122,7 +122,7 @@
{% endif %} {% endif %}
{% if entry.series|length > 0 %} {% if entry.series|length > 0 %}
<p>{{_('Book')}} {{entry.series_index}} {{_('of')}} <a href="{{url_for('web.books_list', data='series', sort_param='stored', book_id=entry.series[0].id)}}">{{entry.series[0].name}}</a></p> <p>{{_('Book')}} {{entry.series_index|formatfloat(2)}} {{_('of')}} <a href="{{url_for('web.books_list', data='series', sort_param='stored', book_id=entry.series[0].id)}}">{{entry.series[0].name}}</a></p>
{% endif %} {% endif %}
{% if entry.languages.__len__() > 0 %} {% if entry.languages.__len__() > 0 %}

View File

@ -9,7 +9,7 @@
{% 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">
<span class="img"> <span class="img">
<img src="{{ url_for('web.get_cover', book_id=entry.id) }}" alt="{{ entry.title }}" /> <img title="{{entry.title}}" src="{{ url_for('web.get_cover', book_id=entry.id) }}" alt="{{ entry.title }}" />
{% if entry.id in read_book_ids %}<span class="badge read glyphicon glyphicon-ok"></span>{% endif %} {% if entry.id in read_book_ids %}<span class="badge read glyphicon glyphicon-ok"></span>{% endif %}
</span> </span>
</a> </a>
@ -17,7 +17,7 @@
</div> </div>
<div class="meta"> <div class="meta">
<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">
<p class="title">{{entry.title|shortentitle}}</p> <p title="{{ entry.title }}" class="title">{{entry.title|shortentitle}}</p>
</a> </a>
<p class="author"> <p class="author">
{% for author in entry.authors %} {% for author in entry.authors %}

View File

@ -29,14 +29,14 @@
<div class="cover"> <div class="cover">
<a href="{{url_for('web.books_list', data=data, sort_param='stored', book_id=entry[0].series[0].id )}}"> <a href="{{url_for('web.books_list', data=data, sort_param='stored', book_id=entry[0].series[0].id )}}">
<span class="img"> <span class="img">
<img src="{{ url_for('web.get_cover', book_id=entry[0].id) }}" alt="{{ entry[0].name }}"/> <img title="{{entry.title}}" src="{{ url_for('web.get_cover', book_id=entry[0].id) }}" alt="{{ entry[0].name }}"/>
<span class="badge">{{entry.count}}</span> <span class="badge">{{entry.count}}</span>
</span> </span>
</a> </a>
</div> </div>
<div class="meta"> <div class="meta">
<a href="{{url_for('web.books_list', data=data, sort_param='stored', book_id=entry[0].series[0].id )}}"> <a href="{{url_for('web.books_list', data=data, sort_param='stored', book_id=entry[0].series[0].id )}}">
<p class="title">{{entry[0].series[0].name|shortentitle}}</p> <p title="{{entry[0].series[0].name|shortentitle}}" class="title">{{entry[0].series[0].name|shortentitle}}</p>
</a> </a>
</div> </div>
</div> </div>

View File

@ -9,14 +9,14 @@
<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">
<span class="img"> <span class="img">
<img src="{{ url_for('web.get_cover', book_id=entry.id) }}" alt="{{ entry.title }}" /> <img title="{{ entry.title }}" src="{{ url_for('web.get_cover', book_id=entry.id) }}" alt="{{ entry.title }}" />
{% if entry.id in read_book_ids %}<span class="badge read glyphicon glyphicon-ok"></span>{% endif %} {% if entry.id in read_book_ids %}<span class="badge read glyphicon glyphicon-ok"></span>{% endif %}
</span> </span>
</a> </a>
</div> </div>
<div class="meta"> <div class="meta">
<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">
<p class="title">{{entry.title|shortentitle}}</p> <p title="{{entry.title}}" class="title">{{entry.title|shortentitle}}</p>
</a> </a>
<p class="author"> <p class="author">
{% for author in entry.authors %} {% for author in entry.authors %}
@ -86,14 +86,14 @@
<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">
<span class="img"> <span class="img">
<img src="{{ url_for('web.get_cover', book_id=entry.id) }}" alt="{{ entry.title }}"/> <img title="{{ entry.title }}" src="{{ url_for('web.get_cover', book_id=entry.id) }}" alt="{{ entry.title }}"/>
{% if entry.id in read_book_ids %}<span class="badge read glyphicon glyphicon-ok"></span>{% endif %} {% if entry.id in read_book_ids %}<span class="badge read glyphicon glyphicon-ok"></span>{% endif %}
</span> </span>
</a> </a>
</div> </div>
<div class="meta"> <div class="meta">
<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">
<p class="title">{{entry.title|shortentitle}}</p> <p title="{{ entry.title }}" class="title">{{entry.title|shortentitle}}</p>
</a> </a>
<p class="author"> <p class="author">
{% for author in entry.authors %} {% for author in entry.authors %}

View File

@ -3,7 +3,7 @@
<head> <head>
<meta charset="utf-8"> <meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1"> <meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1">
<title>ePub Reader</title> <title>{{_('epub Reader')}} | {{title}}</title>
<meta name="description" content=""> <meta name="description" content="">
<meta name="viewport" content="width=device-width, user-scalable=no"> <meta name="viewport" content="width=device-width, user-scalable=no">
<meta name="apple-mobile-web-app-capable" content="yes"> <meta name="apple-mobile-web-app-capable" content="yes">

View File

@ -1,10 +1,10 @@
<!DOCTYPE html> <!DOCTYPE html>
<html> <html>
<head> <head>
<title>Comic Reader</title>
<meta charset="utf-8"> <meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1"> <meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1">
<meta name="description" content=""> <meta name="description" content="">
<title>{{_('Comic Reader')}} | {{title}}</title>
<meta name="viewport" content="width=device-width, user-scalable=no"> <meta name="viewport" content="width=device-width, user-scalable=no">
<meta name="apple-mobile-web-app-capable" content="yes"> <meta name="apple-mobile-web-app-capable" content="yes">

View File

@ -7,7 +7,7 @@
<link type="text/css" rel="stylesheet" href="{{ url_for('static', filename='js/libs/djvu_html5/Djvu_html5.css') }}"> <link type="text/css" rel="stylesheet" href="{{ url_for('static', filename='js/libs/djvu_html5/Djvu_html5.css') }}">
<title>Djvu HTML5 browser demo</title> <title>{{_('DJVU Reader')}} | {{title}}</title>
<script type="text/javascript" language="javascript" <script type="text/javascript" language="javascript"
src="{{ url_for('static', filename='js/libs/djvu_html5/djvu_html5/djvu_html5.nocache.js') }}"></script> src="{{ url_for('static', filename='js/libs/djvu_html5/djvu_html5/djvu_html5.nocache.js') }}"></script>

View File

@ -26,7 +26,7 @@ See https://github.com/adobe-type-tools/cmap-resources
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1"> <meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1">
<meta name="google" content="notranslate"> <meta name="google" content="notranslate">
<meta http-equiv="X-UA-Compatible" content="IE=edge"> <meta http-equiv="X-UA-Compatible" content="IE=edge">
<title>{{_('PDF reader')}}</title> <title>{{_('PDF Reader')}} | {{title}}</title>
<link rel="stylesheet" href="{{ url_for('static', filename='css/libs/viewer.css') }}"> <link rel="stylesheet" href="{{ url_for('static', filename='css/libs/viewer.css') }}">

View File

@ -3,7 +3,7 @@
<head> <head>
<meta charset="utf-8"> <meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1"> <meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1">
<title>{{_('Basic txt Reader')}}</title> <title>{{_('txt Reader')}} | {{title}}</title>
<meta name="description" content=""> <meta name="description" content="">
<meta name="viewport" content="width=device-width"> <meta name="viewport" content="width=device-width">
<meta name="apple-mobile-web-app-capable" content="yes"> <meta name="apple-mobile-web-app-capable" content="yes">

View File

@ -44,7 +44,7 @@
{% 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">
<span class="img"> <span class="img">
<img src="{{ url_for('web.get_cover', book_id=entry.id) }}" alt="{{ entry.title }}" /> <img title="{{entry.title}}" src="{{ url_for('web.get_cover', book_id=entry.id) }}" alt="{{ entry.title }}" />
{% if entry.id in read_book_ids %}<span class="badge read glyphicon glyphicon-ok"></span>{% endif %} {% if entry.id in read_book_ids %}<span class="badge read glyphicon glyphicon-ok"></span>{% endif %}
</span> </span>
</a> </a>
@ -52,7 +52,7 @@
</div> </div>
<div class="meta"> <div class="meta">
<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">
<p class="title">{{entry.title|shortentitle}}</p> <p title="{{entry.title}}" class="title">{{entry.title|shortentitle}}</p>
</a> </a>
<p class="author"> <p class="author">
{% for author in entry.authors %} {% for author in entry.authors %}

View File

@ -31,14 +31,14 @@
<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">
<span class="img"> <span class="img">
<img src="{{ url_for('web.get_cover', book_id=entry.id) }}" alt="{{ entry.title }}" /> <img title="{{entry.title}}" src="{{ url_for('web.get_cover', book_id=entry.id) }}" alt="{{ entry.title }}" />
{% if entry.id in read_book_ids %}<span class="badge read glyphicon glyphicon-ok"></span>{% endif %} {% if entry.id in read_book_ids %}<span class="badge read glyphicon glyphicon-ok"></span>{% endif %}
</span> </span>
</a> </a>
</div> </div>
<div class="meta"> <div class="meta">
<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">
<p class="title">{{entry.title|shortentitle}}</p> <p title="{{entry.title}}" class="title">{{entry.title|shortentitle}}</p>
</a> </a>
<p class="author"> <p class="author">
{% for author in entry.authors %} {% for author in entry.authors %}

View File

@ -9,9 +9,9 @@
<div class="row"> <div class="row">
<div class="col-lg-2 col-sm-4 hidden-xs"> <div class="col-lg-2 col-sm-4 hidden-xs">
{% if entry['visible'] %} {% if entry['visible'] %}
<img class="cover-height" src="{{ url_for('web.get_cover', book_id=entry['Books']['id']) }}"> <img title="{{entry.title}}" class="cover-height" src="{{ url_for('web.get_cover', book_id=entry['Books']['id']) }}">
{% else %} {% else %}
<img class="cover-height" src="{{ url_for('static', filename='generic_cover.jpg') }}"> <img title="{{entry.title}}" class="cover-height" src="{{ url_for('static', filename='generic_cover.jpg') }}">
{% endif %} {% endif %}
</div> </div>
<div class="col-lg-10 col-sm-8 col-xs-12"> <div class="col-lg-10 col-sm-8 col-xs-12">

View File

@ -35,7 +35,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="meta"> <div class="meta">
<p class="title">{{entry.title|shortentitle}}</p> <p title="{{entry.title}}" class="title">{{entry.title|shortentitle}}</p>
<p class="author"> <p class="author">
{% for author in entry.authors %} {% for author in entry.authors %}
<a href="{{url_for('web.books_list', data='author', sort_param='new', book_id=author.id) }}">{{author.name.replace('|',',')}}</a> <a href="{{url_for('web.books_list', data='author', sort_param='new', book_id=author.id) }}">{{author.name.replace('|',',')}}</a>

View File

@ -1680,28 +1680,33 @@ def read_book(book_id, book_format):
ub.Bookmark.format == book_format.upper())).first() ub.Bookmark.format == book_format.upper())).first()
if book_format.lower() == "epub": if book_format.lower() == "epub":
log.debug(u"Start epub reader for %d", book_id) log.debug(u"Start epub reader for %d", book_id)
return render_title_template('read.html', bookid=book_id, title=_(u"Read a Book"), bookmark=bookmark) return render_title_template('read.html', bookid=book_id, title=book.title, bookmark=bookmark)
elif book_format.lower() == "pdf": elif book_format.lower() == "pdf":
log.debug(u"Start pdf reader for %d", book_id) log.debug(u"Start pdf reader for %d", book_id)
return render_title_template('readpdf.html', pdffile=book_id, title=_(u"Read a Book")) return render_title_template('readpdf.html', pdffile=book_id, title=book.title)
elif book_format.lower() == "txt": elif book_format.lower() == "txt":
log.debug(u"Start txt reader for %d", book_id) log.debug(u"Start txt reader for %d", book_id)
return render_title_template('readtxt.html', txtfile=book_id, title=_(u"Read a Book")) return render_title_template('readtxt.html', txtfile=book_id, title=book.title)
elif book_format.lower() == "djvu": elif book_format.lower() == "djvu":
log.debug(u"Start djvu reader for %d", book_id) log.debug(u"Start djvu reader for %d", book_id)
return render_title_template('readdjvu.html', djvufile=book_id, title=_(u"Read a Book")) return render_title_template('readdjvu.html', djvufile=book_id, title=book.title)
else: else:
for fileExt in constants.EXTENSIONS_AUDIO: for fileExt in constants.EXTENSIONS_AUDIO:
if book_format.lower() == fileExt: if book_format.lower() == fileExt:
entries = calibre_db.get_filtered_book(book_id) entries = calibre_db.get_filtered_book(book_id)
log.debug(u"Start mp3 listening for %d", book_id) log.debug(u"Start mp3 listening for %d", book_id)
return render_title_template('listenmp3.html', mp3file=book_id, audioformat=book_format.lower(), return render_title_template('listenmp3.html', mp3file=book_id, audioformat=book_format.lower(),
title=_(u"Read a Book"), entry=entries, bookmark=bookmark) entry=entries, bookmark=bookmark)
for fileExt in ["cbr", "cbt", "cbz"]: for fileExt in ["cbr", "cbt", "cbz"]:
if book_format.lower() == fileExt: if book_format.lower() == fileExt:
all_name = str(book_id) all_name = str(book_id)
title = book.title
if len(book.series):
title = title + " - " + book.series[0].name
if book.series_index:
title = title + " #" + '{0:.2f}'.format(book.series_index).rstrip('0').rstrip('.')
log.debug(u"Start comic reader for %d", book_id) log.debug(u"Start comic reader for %d", book_id)
return render_title_template('readcbr.html', comicfile=all_name, title=_(u"Read a Book"), return render_title_template('readcbr.html', comicfile=all_name, title=title,
extension=fileExt) extension=fileExt)
log.debug(u"Oops! Selected book title is unavailable. File does not exist or is not accessible") 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"), category="error") flash(_(u"Oops! Selected book title is unavailable. File does not exist or is not accessible"), category="error")