Merge branch 'master' of https://github.com/janeczku/calibre-web
This commit is contained in:
commit
6ef792d65d
|
@ -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
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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:
|
||||||
|
|
|
@ -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')
|
||||||
|
|
|
@ -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')
|
||||||
|
|
74
cps/kobo.py
74
cps/kobo.py
|
@ -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
|
||||||
|
|
||||||
|
|
|
@ -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)
|
||||||
|
|
|
@ -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;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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 %}
|
||||||
|
|
|
@ -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">
|
||||||
|
|
|
@ -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 %}
|
||||||
|
|
|
@ -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 %}
|
||||||
|
|
|
@ -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>
|
||||||
|
|
|
@ -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 %}
|
||||||
|
|
|
@ -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">
|
||||||
|
|
|
@ -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">
|
||||||
|
|
||||||
|
|
|
@ -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>
|
||||||
|
|
|
@ -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') }}">
|
||||||
|
|
|
@ -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">
|
||||||
|
|
|
@ -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 %}
|
||||||
|
|
|
@ -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 %}
|
||||||
|
|
|
@ -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">
|
||||||
|
|
|
@ -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>
|
||||||
|
|
17
cps/web.py
17
cps/web.py
|
@ -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")
|
||||||
|
|
Loading…
Reference in New Issue
Block a user