Merge branch 'master' into Develop
# Conflicts: # cps/admin.py # cps/converter.py # cps/subproc_wrapper.py # test/Calibre-Web TestSummary_Linux.html
This commit is contained in:
commit
302679719d
|
@ -41,6 +41,6 @@ Open a new GitHub pull request with the patch. Ensure the PR description clearly
|
|||
|
||||
In case your code enhances features of Calibre-Web: Create your pull request for the development branch if your enhancement consists of more than some lines of code in a local section of Calibre-Webs code. This makes it easier to test it and check all implication before it's made public.
|
||||
|
||||
Please check if your code runs on Python 2.7 (still necessary in 2020) and mainly on python 3. If possible and the feature is related to operating system functions, try to check it on Windows and Linux.
|
||||
Calibre-Web is automatically tested on Linux in combination with python 3.7. The code for testing is in a [separate repo](https://github.com/OzzieIsaacs/calibre-web-test) on Github. It uses unit tests and performs real system tests with selenium; it would be great if you could consider also writing some tests.
|
||||
Please check if your code runs with python 3, python 2 is no longer supported. If possible and the feature is related to operating system functions, try to check it on Windows and Linux.
|
||||
Calibre-Web is automatically tested on Linux in combination with python 3.8. The code for testing is in a [separate repo](https://github.com/OzzieIsaacs/calibre-web-test) on Github. It uses unit tests and performs real system tests with selenium; it would be great if you could consider also writing some tests.
|
||||
A static code analysis is done by Codacy, but it's partly broken and doesn't run automatically. You could check your code with ESLint before contributing, a configuration file can be found in the projects root folder.
|
||||
|
|
5
SECURITY.md
Normal file
5
SECURITY.md
Normal file
|
@ -0,0 +1,5 @@
|
|||
# Security Policy
|
||||
|
||||
## Reporting a Vulnerability
|
||||
|
||||
Please report security issues to ozzie.fernandez.isaacs@googlemail.com
|
|
@ -102,8 +102,9 @@ def create_app():
|
|||
|
||||
log.info('Starting Calibre Web...')
|
||||
if sys.version_info < (3, 0):
|
||||
log.info('Python2 is EOL since end of 2019, this version of Calibre-Web is no longer supporting Python2 please consider upgrading to Python3')
|
||||
print('Python2 is EOL since end of 2019, this version of Calibre-Web is no longer supporting Python2 please consider upgrading to Python3')
|
||||
log.info('*** Python2 is EOL since end of 2019, this version of Calibre-Web is no longer supporting Python2, please update your installation to Python3 ***')
|
||||
print('*** Python2 is EOL since end of 2019, this version of Calibre-Web is no longer supporting Python2, please update your installation to Python3 ***')
|
||||
sys.exit(5)
|
||||
Principal(app)
|
||||
lm.init_app(app)
|
||||
app.secret_key = os.getenv('SECRET_KEY', config_sql.get_flask_session_key(ub.session))
|
||||
|
|
15
cps/admin.py
15
cps/admin.py
|
@ -99,10 +99,11 @@ def admin_required(f):
|
|||
|
||||
@admi.before_app_request
|
||||
def before_request():
|
||||
if not ub.check_user_session(current_user.id, flask_session.get('_id')):
|
||||
# make remember me function work
|
||||
if current_user.is_authenticated:
|
||||
confirm_login()
|
||||
if not ub.check_user_session(current_user.id, flask_session.get('_id')) and 'opds' not in request.path:
|
||||
logout_user()
|
||||
# if current_user.is_authenticated:
|
||||
# confirm_login()
|
||||
g.constants = constants
|
||||
g.user = current_user
|
||||
g.allow_registration = config.config_public_reg
|
||||
|
@ -1375,11 +1376,11 @@ def _delete_user(content):
|
|||
if content.name != "Guest":
|
||||
# Delete all books in shelfs belonging to user, all shelfs of user, downloadstat of user, read status
|
||||
# and user itself
|
||||
ub.session.query(ub.ReadBook).filter(ub.User.id == ub.ReadBook.user_id).delete()
|
||||
ub.session.query(ub.Downloads).filter(ub.User.id == ub.Downloads.user_id).delete()
|
||||
for us in ub.session.query(ub.Shelf).filter(ub.User.id == ub.Shelf.user_id):
|
||||
ub.session.query(ub.ReadBook).filter(content.id == ub.ReadBook.user_id).delete()
|
||||
ub.session.query(ub.Downloads).filter(content.id == ub.Downloads.user_id).delete()
|
||||
for us in ub.session.query(ub.Shelf).filter(content.id == ub.Shelf.user_id):
|
||||
ub.session.query(ub.BookShelf).filter(us.id == ub.BookShelf.shelf).delete()
|
||||
ub.session.query(ub.Shelf).filter(ub.User.id == ub.Shelf.user_id).delete()
|
||||
ub.session.query(ub.Shelf).filter(content.id == ub.Shelf.user_id).delete()
|
||||
ub.session.query(ub.User).filter(ub.User.id == content.id).delete()
|
||||
ub.session_commit()
|
||||
log.info(u"User {} deleted".format(content.name))
|
||||
|
|
|
@ -20,6 +20,9 @@ from __future__ import division, print_function, unicode_literals
|
|||
import sys
|
||||
import os
|
||||
from collections import namedtuple
|
||||
from sqlalchemy import __version__ as sql_version
|
||||
|
||||
sqlalchemy_version2 = ([int(x) for x in sql_version.split('.')] >= [2,0,0])
|
||||
|
||||
# if installed via pip this variable is set to true (empty file with name .HOMEDIR present)
|
||||
HOME_CONFIG = os.path.isfile(os.path.join(os.path.dirname(os.path.abspath(__file__)), '.HOMEDIR'))
|
||||
|
|
|
@ -39,7 +39,9 @@ def _get_command_version(path, pattern, argument=None):
|
|||
if argument:
|
||||
command.append(argument)
|
||||
try:
|
||||
return process_wait(command, pattern=pattern).string
|
||||
match = process_wait(command, pattern=pattern)
|
||||
if isinstance(match, re.Match):
|
||||
return match.string
|
||||
except Exception as ex:
|
||||
log.warning("%s: %s", path, ex)
|
||||
return _EXECUTION_ERROR
|
||||
|
|
|
@ -690,6 +690,8 @@ class CalibreDB():
|
|||
randm = false()
|
||||
off = int(int(pagesize) * (page - 1))
|
||||
query = self.session.query(database)
|
||||
if len(join) == 6:
|
||||
query = query.outerjoin(join[0], join[1]).outerjoin(join[2]).outerjoin(join[3], join[4]).outerjoin(join[5])
|
||||
if len(join) == 3:
|
||||
query = query.outerjoin(join[0], join[1]).outerjoin(join[2])
|
||||
elif len(join) == 2:
|
||||
|
@ -755,6 +757,8 @@ class CalibreDB():
|
|||
for authorterm in authorterms:
|
||||
q.append(Books.authors.any(func.lower(Authors.name).ilike("%" + authorterm + "%")))
|
||||
query = self.session.query(Books)
|
||||
if len(join) == 6:
|
||||
query = query.outerjoin(join[0], join[1]).outerjoin(join[2]).outerjoin(join[3], join[4]).outerjoin(join[5])
|
||||
if len(join) == 3:
|
||||
query = query.outerjoin(join[0], join[1]).outerjoin(join[2])
|
||||
elif len(join) == 2:
|
||||
|
|
|
@ -22,10 +22,6 @@ import glob
|
|||
import zipfile
|
||||
import json
|
||||
from io import BytesIO
|
||||
try:
|
||||
from StringIO import StringIO
|
||||
except ImportError:
|
||||
from io import StringIO
|
||||
|
||||
import os
|
||||
|
||||
|
@ -38,9 +34,9 @@ log = logger.create()
|
|||
|
||||
def assemble_logfiles(file_name):
|
||||
log_list = sorted(glob.glob(file_name + '*'), reverse=True)
|
||||
wfd = StringIO()
|
||||
wfd = BytesIO()
|
||||
for f in log_list:
|
||||
with open(f, 'r') as fd:
|
||||
with open(f, 'rb') as fd:
|
||||
shutil.copyfileobj(fd, wfd)
|
||||
wfd.seek(0)
|
||||
if int(__version__.split('.')[0]) < 2:
|
||||
|
|
|
@ -113,10 +113,8 @@ def yesno(value, yes, no):
|
|||
|
||||
@jinjia.app_template_filter('formatfloat')
|
||||
def formatfloat(value, decimals=1):
|
||||
formatedstring = '%d' % value
|
||||
if (value % 1) != 0:
|
||||
formatedstring = ('%s.%d' % (formatedstring, (value % 1) * 10**decimals)).rstrip('0')
|
||||
return formatedstring
|
||||
value = 0 if not value else value
|
||||
return ('{0:.' + str(decimals) + 'f}').format(value).rstrip('0').rstrip('.')
|
||||
|
||||
|
||||
@jinjia.app_template_filter('formatseriesindex')
|
||||
|
|
17
cps/kobo.py
17
cps/kobo.py
|
@ -44,11 +44,11 @@ from werkzeug.datastructures import Headers
|
|||
from sqlalchemy import func
|
||||
from sqlalchemy.sql.expression import and_, or_
|
||||
from sqlalchemy.exc import StatementError
|
||||
from sqlalchemy import __version__ as sql_version
|
||||
from sqlalchemy.sql import select
|
||||
import requests
|
||||
|
||||
from . import config, logger, kobo_auth, db, calibre_db, helper, shelf as shelf_lib, ub
|
||||
from .constants import sqlalchemy_version2
|
||||
from .helper import get_download_link
|
||||
from .services import SyncToken as SyncToken
|
||||
from .web import download_required
|
||||
|
@ -66,7 +66,6 @@ kobo_auth.register_url_value_preprocessor(kobo)
|
|||
|
||||
log = logger.create()
|
||||
|
||||
sql2 = ([int(x) for x in sql_version.split('.')] >= [2,0,0])
|
||||
|
||||
def get_store_url_for_current_request():
|
||||
# Programmatically modify the current url to point to the official Kobo store
|
||||
|
@ -139,6 +138,7 @@ def convert_to_kobo_timestamp_string(timestamp):
|
|||
def HandleSyncRequest():
|
||||
sync_token = SyncToken.SyncToken.from_headers(request.headers)
|
||||
log.info("Kobo library sync request received.")
|
||||
log.debug("SyncToken: {}".format(sync_token))
|
||||
if not current_app.wsgi_app.is_proxied:
|
||||
log.debug('Kobo: Received unproxied request, changed request port to external server port')
|
||||
|
||||
|
@ -158,7 +158,7 @@ def HandleSyncRequest():
|
|||
only_kobo_shelves = current_user.kobo_only_shelves_sync
|
||||
|
||||
if only_kobo_shelves:
|
||||
if sql2:
|
||||
if sqlalchemy_version2:
|
||||
changed_entries = select(db.Books,
|
||||
ub.ArchivedBook.last_modified,
|
||||
ub.BookShelf.date_added,
|
||||
|
@ -182,7 +182,7 @@ def HandleSyncRequest():
|
|||
.distinct()
|
||||
)
|
||||
else:
|
||||
if sql2:
|
||||
if sqlalchemy_version2:
|
||||
changed_entries = select(db.Books, ub.ArchivedBook.last_modified, ub.ArchivedBook.is_archived)
|
||||
else:
|
||||
changed_entries = calibre_db.session.query(db.Books,
|
||||
|
@ -201,7 +201,7 @@ def HandleSyncRequest():
|
|||
changed_entries = changed_entries.filter(db.Books.id > sync_token.books_last_id)
|
||||
|
||||
reading_states_in_new_entitlements = []
|
||||
if sql2:
|
||||
if sqlalchemy_version2:
|
||||
books = calibre_db.session.execute(changed_entries.limit(SYNC_ITEM_LIMIT))
|
||||
else:
|
||||
books = changed_entries.limit(SYNC_ITEM_LIMIT)
|
||||
|
@ -245,7 +245,7 @@ def HandleSyncRequest():
|
|||
|
||||
new_books_last_created = max(ts_created, new_books_last_created)
|
||||
|
||||
if sql2:
|
||||
if sqlalchemy_version2:
|
||||
max_change = calibre_db.session.execute(changed_entries
|
||||
.filter(ub.ArchivedBook.is_archived)
|
||||
.order_by(func.datetime(ub.ArchivedBook.last_modified).desc()))\
|
||||
|
@ -259,7 +259,7 @@ def HandleSyncRequest():
|
|||
new_archived_last_modified = max(new_archived_last_modified, max_change)
|
||||
|
||||
# no. of books returned
|
||||
if sql2:
|
||||
if sqlalchemy_version2:
|
||||
entries = calibre_db.session.execute(changed_entries).all()
|
||||
book_count = len(entries)
|
||||
else:
|
||||
|
@ -330,6 +330,7 @@ def generate_sync_response(sync_token, sync_results, set_cont=False):
|
|||
extra_headers["x-kobo-sync"] = "continue"
|
||||
sync_token.to_headers(extra_headers)
|
||||
|
||||
log.debug("Kobo Sync Content: {}".format(sync_results))
|
||||
response = make_response(jsonify(sync_results), extra_headers)
|
||||
|
||||
return response
|
||||
|
@ -695,7 +696,7 @@ def sync_shelves(sync_token, sync_results, only_kobo_shelves=False):
|
|||
})
|
||||
extra_filters.append(ub.Shelf.kobo_sync)
|
||||
|
||||
if sql2:
|
||||
if sqlalchemy_version2:
|
||||
shelflist = ub.session.execute(select(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),
|
||||
|
|
|
@ -183,3 +183,12 @@ class SyncToken:
|
|||
},
|
||||
}
|
||||
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)
|
||||
|
|
52
cps/shelf.py
52
cps/shelf.py
|
@ -72,10 +72,9 @@ def add_to_shelf(shelf_id, book_id):
|
|||
|
||||
if not check_shelf_edit_permissions(shelf):
|
||||
if not xhr:
|
||||
flash(_(u"Sorry you are not allowed to add a book to the the shelf: %(shelfname)s", shelfname=shelf.name),
|
||||
category="error")
|
||||
flash(_(u"Sorry you are not allowed to add a book to that shelf"), category="error")
|
||||
return redirect(url_for('web.index'))
|
||||
return "Sorry you are not allowed to add a book to the the shelf: %s" % shelf.name, 403
|
||||
return "Sorry you are not allowed to add a book to the that shelf", 403
|
||||
|
||||
book_in_shelf = ub.session.query(ub.BookShelf).filter(ub.BookShelf.shelf == shelf_id,
|
||||
ub.BookShelf.book_id == book_id).first()
|
||||
|
@ -228,18 +227,21 @@ def remove_from_shelf(shelf_id, book_id):
|
|||
@login_required
|
||||
def create_shelf():
|
||||
shelf = ub.Shelf()
|
||||
return create_edit_shelf(shelf, title=_(u"Create a Shelf"), page="shelfcreate")
|
||||
return create_edit_shelf(shelf, page_title=_(u"Create a Shelf"), page="shelfcreate")
|
||||
|
||||
|
||||
@shelf.route("/shelf/edit/<int:shelf_id>", methods=["GET", "POST"])
|
||||
@login_required
|
||||
def edit_shelf(shelf_id):
|
||||
shelf = ub.session.query(ub.Shelf).filter(ub.Shelf.id == shelf_id).first()
|
||||
return create_edit_shelf(shelf, title=_(u"Edit a shelf"), page="shelfedit", shelf_id=shelf_id)
|
||||
if not check_shelf_edit_permissions(shelf):
|
||||
flash(_(u"Sorry you are not allowed to edit this shelf"), category="error")
|
||||
return redirect(url_for('web.index'))
|
||||
return create_edit_shelf(shelf, page_title=_(u"Edit a shelf"), page="shelfedit", shelf_id=shelf_id)
|
||||
|
||||
|
||||
# if shelf ID is set, we are editing a shelf
|
||||
def create_edit_shelf(shelf, title, page, shelf_id=False):
|
||||
def create_edit_shelf(shelf, page_title, page, shelf_id=False):
|
||||
sync_only_selected_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()
|
||||
if request.method == "POST":
|
||||
|
@ -247,20 +249,20 @@ def create_edit_shelf(shelf, title, page, shelf_id=False):
|
|||
shelf.is_public = 1 if to_save.get("is_public") else 0
|
||||
if config.config_kobo_sync:
|
||||
shelf.kobo_sync = True if to_save.get("kobo_sync") else False
|
||||
|
||||
if check_shelf_is_unique(shelf, to_save, shelf_id):
|
||||
shelf.name = to_save["title"]
|
||||
shelf_title = to_save.get("title", "")
|
||||
if check_shelf_is_unique(shelf, shelf_title, shelf_id):
|
||||
shelf.name = shelf_title
|
||||
if not shelf_id:
|
||||
shelf.user_id = int(current_user.id)
|
||||
ub.session.add(shelf)
|
||||
shelf_action = "created"
|
||||
flash_text = _(u"Shelf %(title)s created", title=to_save["title"])
|
||||
flash_text = _(u"Shelf %(title)s created", title=shelf_title)
|
||||
else:
|
||||
shelf_action = "changed"
|
||||
flash_text = _(u"Shelf %(title)s changed", title=to_save["title"])
|
||||
flash_text = _(u"Shelf %(title)s changed", title=shelf_title)
|
||||
try:
|
||||
ub.session.commit()
|
||||
log.info(u"Shelf {} {}".format(to_save["title"], shelf_action))
|
||||
log.info(u"Shelf {} {}".format(shelf_title, shelf_action))
|
||||
flash(flash_text, category="success")
|
||||
return redirect(url_for('shelf.show_shelf', shelf_id=shelf.id))
|
||||
except (OperationalError, InvalidRequestError) as ex:
|
||||
|
@ -274,37 +276,37 @@ def create_edit_shelf(shelf, title, page, shelf_id=False):
|
|||
flash(_(u"There was an error"), category="error")
|
||||
return render_title_template('shelf_edit.html',
|
||||
shelf=shelf,
|
||||
title=title,
|
||||
title=page_title,
|
||||
page=page,
|
||||
kobo_sync_enabled=config.config_kobo_sync,
|
||||
sync_only_selected_shelves=sync_only_selected_shelves)
|
||||
|
||||
|
||||
def check_shelf_is_unique(shelf, to_save, shelf_id=False):
|
||||
def check_shelf_is_unique(shelf, title, shelf_id=False):
|
||||
if shelf_id:
|
||||
ident = ub.Shelf.id != shelf_id
|
||||
else:
|
||||
ident = true()
|
||||
if shelf.is_public == 1:
|
||||
is_shelf_name_unique = ub.session.query(ub.Shelf) \
|
||||
.filter((ub.Shelf.name == to_save["title"]) & (ub.Shelf.is_public == 1)) \
|
||||
.filter((ub.Shelf.name == title) & (ub.Shelf.is_public == 1)) \
|
||||
.filter(ident) \
|
||||
.first() is None
|
||||
|
||||
if not is_shelf_name_unique:
|
||||
log.error("A public shelf with the name '{}' already exists.".format(to_save["title"]))
|
||||
flash(_(u"A public shelf with the name '%(title)s' already exists.", title=to_save["title"]),
|
||||
log.error("A public shelf with the name '{}' already exists.".format(title))
|
||||
flash(_(u"A public shelf with the name '%(title)s' already exists.", title=title),
|
||||
category="error")
|
||||
else:
|
||||
is_shelf_name_unique = ub.session.query(ub.Shelf) \
|
||||
.filter((ub.Shelf.name == to_save["title"]) & (ub.Shelf.is_public == 0) &
|
||||
.filter((ub.Shelf.name == title) & (ub.Shelf.is_public == 0) &
|
||||
(ub.Shelf.user_id == int(current_user.id))) \
|
||||
.filter(ident) \
|
||||
.first() is None
|
||||
|
||||
if not is_shelf_name_unique:
|
||||
log.error("A private shelf with the name '{}' already exists.".format(to_save["title"]))
|
||||
flash(_(u"A private shelf with the name '%(title)s' already exists.", title=to_save["title"]),
|
||||
log.error("A private shelf with the name '{}' already exists.".format(title))
|
||||
flash(_(u"A private shelf with the name '%(title)s' already exists.", title=title),
|
||||
category="error")
|
||||
return is_shelf_name_unique
|
||||
|
||||
|
@ -378,7 +380,9 @@ def order_shelf(shelf_id):
|
|||
|
||||
|
||||
def change_shelf_order(shelf_id, order):
|
||||
result = calibre_db.session.query(db.Books).join(ub.BookShelf, ub.BookShelf.book_id == db.Books.id) \
|
||||
result = calibre_db.session.query(db.Books).outerjoin(db.books_series_link,
|
||||
db.Books.id == db.books_series_link.c.book)\
|
||||
.outerjoin(db.Series).join(ub.BookShelf, ub.BookShelf.book_id == db.Books.id) \
|
||||
.filter(ub.BookShelf.shelf == shelf_id).order_by(*order).all()
|
||||
for index, entry in enumerate(result):
|
||||
book = ub.session.query(ub.BookShelf).filter(ub.BookShelf.shelf == shelf_id) \
|
||||
|
@ -408,9 +412,11 @@ def render_show_shelf(shelf_type, shelf_id, page_no, sort_param):
|
|||
if sort_param == 'old':
|
||||
change_shelf_order(shelf_id, [db.Books.timestamp])
|
||||
if sort_param == 'authaz':
|
||||
change_shelf_order(shelf_id, [db.Books.author_sort.asc()])
|
||||
change_shelf_order(shelf_id, [db.Books.author_sort.asc(), db.Series.name, db.Books.series_index])
|
||||
if sort_param == 'authza':
|
||||
change_shelf_order(shelf_id, [db.Books.author_sort.desc()])
|
||||
change_shelf_order(shelf_id, [db.Books.author_sort.desc(),
|
||||
db.Series.name.desc(),
|
||||
db.Books.series_index.desc()])
|
||||
page = "shelf.html"
|
||||
pagesize = 0
|
||||
else:
|
||||
|
|
|
@ -3291,7 +3291,6 @@ div.btn-group[role=group][aria-label="Download, send to Kindle, reading"] .dropd
|
|||
transform-origin: center top;
|
||||
border: 0;
|
||||
left: 0 !important;
|
||||
max-height: 80%;
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
||||
|
|
|
@ -413,7 +413,11 @@ if($("body.advsearch").length > 0) {
|
|||
});
|
||||
$('#add-to-shelf').height("40px");
|
||||
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) {
|
||||
|
||||
position = $('div[aria-label="Add to shelves"]').offset().left
|
||||
|
|
|
@ -609,7 +609,10 @@ $(function() {
|
|||
if (xhr.status < 400) {
|
||||
$("#spinning_success").hide();
|
||||
clearInterval(rebootInterval);
|
||||
handle_response(data.result);
|
||||
if (data.result) {
|
||||
handle_response(data.result);
|
||||
data.result = "";
|
||||
}
|
||||
}
|
||||
},
|
||||
});
|
||||
|
|
|
@ -662,33 +662,34 @@ function move_header_elements() {
|
|||
}
|
||||
});
|
||||
$(".multi_selector").selectpicker();
|
||||
|
||||
if (! $._data($(".multi_head").get(0), "events") ) {
|
||||
// Functions have to be here, otherwise the callbacks are not fired if visible columns are changed
|
||||
$(".multi_head").on("click", function () {
|
||||
var val = $(this).data("set");
|
||||
var field = $(this).data("name");
|
||||
var result = $('#user-table').bootstrapTable('getSelections').map(a => a.id);
|
||||
var values = $("#" + field).val();
|
||||
confirmDialog(
|
||||
"restrictions",
|
||||
"GeneralChangeModal",
|
||||
0,
|
||||
function () {
|
||||
$.ajax({
|
||||
method: "post",
|
||||
url: window.location.pathname + "/../../ajax/editlistusers/" + field,
|
||||
data: {"pk": result, "value": values, "action": val},
|
||||
success: function (data) {
|
||||
handleListServerResponse(data);
|
||||
},
|
||||
error: function (data) {
|
||||
handleListServerResponse([{type: "danger", message: data.responseText}])
|
||||
},
|
||||
});
|
||||
}
|
||||
);
|
||||
});
|
||||
if ($(".multi_head").length) {
|
||||
if (!$._data($(".multi_head").get(0), "events")) {
|
||||
// Functions have to be here, otherwise the callbacks are not fired if visible columns are changed
|
||||
$(".multi_head").on("click", function () {
|
||||
var val = $(this).data("set");
|
||||
var field = $(this).data("name");
|
||||
var result = $('#user-table').bootstrapTable('getSelections').map(a => a.id);
|
||||
var values = $("#" + field).val();
|
||||
confirmDialog(
|
||||
"restrictions",
|
||||
"GeneralChangeModal",
|
||||
0,
|
||||
function () {
|
||||
$.ajax({
|
||||
method: "post",
|
||||
url: window.location.pathname + "/../../ajax/editlistusers/" + field,
|
||||
data: {"pk": result, "value": values, "action": val},
|
||||
success: function (data) {
|
||||
handleListServerResponse(data);
|
||||
},
|
||||
error: function (data) {
|
||||
handleListServerResponse([{type: "danger", message: data.responseText}])
|
||||
},
|
||||
});
|
||||
}
|
||||
);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
$("#user_delete_selection").click(function () {
|
||||
|
@ -700,38 +701,41 @@ function move_header_elements() {
|
|||
$("#select_default_language").on("change", function () {
|
||||
selectHeader(this, "default_language");
|
||||
});
|
||||
|
||||
if (! $._data($(".check_head").get(0), "events") ) {
|
||||
$(".check_head").on("change", function () {
|
||||
var val = $(this).data("set");
|
||||
var name = $(this).data("name");
|
||||
var data = $(this).data("val");
|
||||
checkboxHeader(val, name, data);
|
||||
});
|
||||
if ($(".check_head").length) {
|
||||
if (!$._data($(".check_head").get(0), "events")) {
|
||||
$(".check_head").on("change", function () {
|
||||
var val = $(this).data("set");
|
||||
var name = $(this).data("name");
|
||||
var data = $(this).data("val");
|
||||
checkboxHeader(val, name, data);
|
||||
});
|
||||
}
|
||||
}
|
||||
if (! $._data($(".button_head").get(0), "events") ) {
|
||||
$(".button_head").on("click", function () {
|
||||
var result = $('#user-table').bootstrapTable('getSelections').map(a => a.id);
|
||||
confirmDialog(
|
||||
"btndeluser",
|
||||
"GeneralDeleteModal",
|
||||
0,
|
||||
function () {
|
||||
$.ajax({
|
||||
method: "post",
|
||||
url: window.location.pathname + "/../../ajax/deleteuser",
|
||||
data: {"userid": result},
|
||||
success: function (data) {
|
||||
selections = selections.filter((el) => !result.includes(el));
|
||||
handleListServerResponse(data);
|
||||
},
|
||||
error: function (data) {
|
||||
handleListServerResponse([{type: "danger", message: data.responseText}])
|
||||
},
|
||||
});
|
||||
}
|
||||
);
|
||||
});
|
||||
if ($(".button_head").length) {
|
||||
if (!$._data($(".button_head").get(0), "events")) {
|
||||
$(".button_head").on("click", function () {
|
||||
var result = $('#user-table').bootstrapTable('getSelections').map(a => a.id);
|
||||
confirmDialog(
|
||||
"btndeluser",
|
||||
"GeneralDeleteModal",
|
||||
0,
|
||||
function () {
|
||||
$.ajax({
|
||||
method: "post",
|
||||
url: window.location.pathname + "/../../ajax/deleteuser",
|
||||
data: {"userid": result},
|
||||
success: function (data) {
|
||||
selections = selections.filter((el) => !result.includes(el));
|
||||
handleListServerResponse(data);
|
||||
},
|
||||
error: function (data) {
|
||||
handleListServerResponse([{type: "danger", message: data.responseText}])
|
||||
},
|
||||
});
|
||||
}
|
||||
);
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -52,10 +52,11 @@ def process_wait(command, serr=subprocess.PIPE, pattern=""):
|
|||
p.wait()
|
||||
for line in p.stdout.readlines():
|
||||
if isinstance(line, bytes):
|
||||
line = line.decode('utf-8')
|
||||
line = line.decode('utf-8', errors="ignore")
|
||||
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 %}
|
||||
<section class="author-bio">
|
||||
{%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 %}
|
||||
|
||||
{%if author.about is not none %}
|
||||
|
@ -37,14 +37,14 @@
|
|||
<div class="cover">
|
||||
<a href="{{ url_for('web.show_book', book_id=entry.id) }}">
|
||||
<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 %}
|
||||
</span>
|
||||
</a>
|
||||
</div>
|
||||
<div class="meta">
|
||||
<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>
|
||||
<p class="author">
|
||||
{% for author in entry.authors %}
|
||||
|
@ -104,11 +104,11 @@
|
|||
<div class="col-sm-3 col-lg-2 col-xs-6 book">
|
||||
<div class="cover">
|
||||
<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>
|
||||
</div>
|
||||
<div class="meta">
|
||||
<p class="title">{{entry.title|shortentitle}}</p>
|
||||
<p title="{{ entry.title }}" class="title">{{entry.title|shortentitle}}</p>
|
||||
<p class="author">
|
||||
{% for author in entry.authors %}
|
||||
{% if loop.index > g.config_authors_max and g.config_authors_max != 0 %}
|
||||
|
|
|
@ -3,7 +3,7 @@
|
|||
{% if book %}
|
||||
<div class="col-sm-3 col-lg-3 col-xs-12">
|
||||
<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>
|
||||
{% if g.user.role_delete_books() %}
|
||||
<div class="text-center">
|
||||
|
|
|
@ -20,7 +20,7 @@
|
|||
<input type="checkbox" id="config_use_google_drive" name="config_use_google_drive" data-control="gdrive_settings" {% if config.config_use_google_drive %}checked{% endif %} >
|
||||
<label for="config_use_google_drive">{{_('Use Google Drive?')}}</label>
|
||||
</div>
|
||||
{% if not gdriveError %}
|
||||
{% if not gdriveError and config.config_use_google_drive %}
|
||||
{% if show_authenticate_google_drive and config.config_use_google_drive %}
|
||||
<div class="form-group required">
|
||||
<a href="{{ url_for('gdrive.authenticate_google_drive') }}" id="gdrive_auth" class="btn btn-primary">{{_('Authenticate Google Drive')}}</a>
|
||||
|
|
|
@ -4,7 +4,7 @@
|
|||
<div class="row">
|
||||
<div class="col-sm-3 col-lg-3 col-xs-5">
|
||||
<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 class="col-sm-9 col-lg-9 book-meta">
|
||||
|
@ -122,7 +122,7 @@
|
|||
{% endif %}
|
||||
|
||||
{% 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 %}
|
||||
|
||||
{% if entry.languages.__len__() > 0 %}
|
||||
|
|
|
@ -9,7 +9,7 @@
|
|||
{% if entry.has_cover is defined %}
|
||||
<a href="{{ url_for('web.show_book', book_id=entry.id) }}" data-toggle="modal" data-target="#bookDetailsModal" data-remote="false">
|
||||
<span class="img">
|
||||
<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 %}
|
||||
</span>
|
||||
</a>
|
||||
|
@ -17,7 +17,7 @@
|
|||
</div>
|
||||
<div class="meta">
|
||||
<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>
|
||||
<p class="author">
|
||||
{% for author in entry.authors %}
|
||||
|
|
|
@ -29,14 +29,14 @@
|
|||
<div class="cover">
|
||||
<a href="{{url_for('web.books_list', data=data, sort_param='stored', book_id=entry[0].series[0].id )}}">
|
||||
<span class="img">
|
||||
<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>
|
||||
</a>
|
||||
</div>
|
||||
<div class="meta">
|
||||
<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>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
@ -9,14 +9,14 @@
|
|||
<div class="cover">
|
||||
<a href="{{ url_for('web.show_book', book_id=entry.id) }}" data-toggle="modal" data-target="#bookDetailsModal" data-remote="false">
|
||||
<span class="img">
|
||||
<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 %}
|
||||
</span>
|
||||
</a>
|
||||
</div>
|
||||
<div class="meta">
|
||||
<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>
|
||||
<p class="author">
|
||||
{% for author in entry.authors %}
|
||||
|
@ -86,14 +86,14 @@
|
|||
<div class="cover">
|
||||
<a href="{{ url_for('web.show_book', book_id=entry.id) }}" data-toggle="modal" data-target="#bookDetailsModal" data-remote="false">
|
||||
<span class="img">
|
||||
<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 %}
|
||||
</span>
|
||||
</a>
|
||||
</div>
|
||||
<div class="meta">
|
||||
<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>
|
||||
<p class="author">
|
||||
{% for author in entry.authors %}
|
||||
|
|
|
@ -3,7 +3,7 @@
|
|||
<head>
|
||||
<meta charset="utf-8">
|
||||
<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="viewport" content="width=device-width, user-scalable=no">
|
||||
<meta name="apple-mobile-web-app-capable" content="yes">
|
||||
|
|
|
@ -1,10 +1,10 @@
|
|||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<title>Comic Reader</title>
|
||||
<meta charset="utf-8">
|
||||
<meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1">
|
||||
<meta name="description" content="">
|
||||
<title>{{_('Comic Reader')}} | {{title}}</title>
|
||||
<meta name="viewport" content="width=device-width, user-scalable=no">
|
||||
<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') }}">
|
||||
|
||||
<title>Djvu HTML5 browser demo</title>
|
||||
<title>{{_('DJVU Reader')}} | {{title}}</title>
|
||||
|
||||
<script type="text/javascript" language="javascript"
|
||||
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="google" content="notranslate">
|
||||
<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') }}">
|
||||
|
|
|
@ -3,7 +3,7 @@
|
|||
<head>
|
||||
<meta charset="utf-8">
|
||||
<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="viewport" content="width=device-width">
|
||||
<meta name="apple-mobile-web-app-capable" content="yes">
|
||||
|
|
|
@ -44,7 +44,7 @@
|
|||
{% if entry.has_cover is defined %}
|
||||
<a href="{{ url_for('web.show_book', book_id=entry.id) }}" data-toggle="modal" data-target="#bookDetailsModal" data-remote="false">
|
||||
<span class="img">
|
||||
<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 %}
|
||||
</span>
|
||||
</a>
|
||||
|
@ -52,7 +52,7 @@
|
|||
</div>
|
||||
<div class="meta">
|
||||
<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>
|
||||
<p class="author">
|
||||
{% for author in entry.authors %}
|
||||
|
|
|
@ -31,14 +31,14 @@
|
|||
<div class="cover">
|
||||
<a href="{{ url_for('web.show_book', book_id=entry.id) }}" data-toggle="modal" data-target="#bookDetailsModal" data-remote="false">
|
||||
<span class="img">
|
||||
<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 %}
|
||||
</span>
|
||||
</a>
|
||||
</div>
|
||||
<div class="meta">
|
||||
<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>
|
||||
<p class="author">
|
||||
{% for author in entry.authors %}
|
||||
|
|
|
@ -9,9 +9,9 @@
|
|||
<div class="row">
|
||||
<div class="col-lg-2 col-sm-4 hidden-xs">
|
||||
{% 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 %}
|
||||
<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 %}
|
||||
</div>
|
||||
<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="meta">
|
||||
<p class="title">{{entry.title|shortentitle}}</p>
|
||||
<p title="{{entry.title}}" class="title">{{entry.title|shortentitle}}</p>
|
||||
<p class="author">
|
||||
{% 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>
|
||||
|
|
|
@ -67,15 +67,14 @@
|
|||
</div>
|
||||
{% endif %}
|
||||
<div class="col-sm-6">
|
||||
{% for element in sidebar %}
|
||||
{% if element['config_show'] %}
|
||||
<div class="form-group">
|
||||
<input type="checkbox" name="show_{{element['visibility']}}" id="show_{{element['visibility']}}" {% if content.check_visibility(element['visibility']) %}checked{% endif %}>
|
||||
<label for="show_{{element['visibility']}}">{{element['show_text']}}</label>
|
||||
</div>
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
|
||||
{% for element in sidebar %}
|
||||
{% if element['config_show'] %}
|
||||
<div class="form-group">
|
||||
<input type="checkbox" name="show_{{element['visibility']}}" id="show_{{element['visibility']}}" {% if content.check_visibility(element['visibility']) %}checked{% endif %}>
|
||||
<label for="show_{{element['visibility']}}">{{element['show_text']}}</label>
|
||||
</div>
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
<div class="form-group">
|
||||
<input type="checkbox" name="Show_detail_random" id="Show_detail_random" {% if content.show_detail_random() %}checked{% endif %}>
|
||||
<label for="Show_detail_random">{{_('Show Random Books in Detail View')}}</label>
|
||||
|
@ -131,32 +130,33 @@
|
|||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
<div class="col-sm-12">
|
||||
<div id="user_submit" class="btn btn-default">{{_('Save')}}</div>
|
||||
{% if not profile %}
|
||||
<div class="btn btn-default" data-back="{{ url_for('admin.admin') }}" id="back">{{_('Cancel')}}</div>
|
||||
{% endif %}
|
||||
{% if g.user and g.user.role_admin() and not profile and not new_user and not content.role_anonymous() %}
|
||||
<div class="btn btn-danger" id="btndeluser" data-value="{{ content.id }}" data-remote="false" >{{_('Delete User')}}</div>
|
||||
{% endif %}
|
||||
<div class="col-sm-12">
|
||||
<div id="user_submit" class="btn btn-default">{{_('Save')}}</div>
|
||||
{% if not profile %}
|
||||
<div class="btn btn-default" data-back="{{ url_for('admin.admin') }}" id="back">{{_('Cancel')}}</div>
|
||||
{% endif %}
|
||||
{% if g.user and g.user.role_admin() and not profile and not new_user and not content.role_anonymous() %}
|
||||
<div class="btn btn-danger" id="btndeluser" data-value="{{ content.id }}" data-remote="false" >{{_('Delete User')}}</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
<div class="modal fade" id="modal_kobo_token" tabindex="-1" role="dialog" aria-labelledby="kobo_tokenModalLabel">
|
||||
<div class="modal-dialog modal-lg" role="document">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<button type="button" class="close" data-dismiss="modal" aria-label="Close"><span aria-hidden="true">×</span></button>
|
||||
<h4 class="modal-title" id="kobo_tokenModalLabel">{{_('Generate Kobo Auth URL')}}</h4>
|
||||
</div>
|
||||
<div class="modal-body">...</div>
|
||||
<div class="modal-footer">
|
||||
<button type="button" id="kobo_close" class="btn btn-default" data-dismiss="modal">{{_('Close')}}</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="modal fade" id="modal_kobo_token" tabindex="-1" role="dialog" aria-labelledby="kobo_tokenModalLabel">
|
||||
<div class="modal-dialog modal-lg" role="document">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<button type="button" class="close" data-dismiss="modal" aria-label="Close"><span aria-hidden="true">×</span></button>
|
||||
<h4 class="modal-title" id="kobo_tokenModalLabel">{{_('Generate Kobo Auth URL')}}</h4>
|
||||
</div>
|
||||
<div class="modal-body">...</div>
|
||||
<div class="modal-footer">
|
||||
<button type="button" id="kobo_close" class="btn btn-default" data-dismiss="modal">{{_('Close')}}</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{% endblock %}
|
||||
{% block modal %}
|
||||
|
|
43
cps/ub.py
43
cps/ub.py
|
@ -27,6 +27,8 @@ from flask import session as flask_session
|
|||
from binascii import hexlify
|
||||
|
||||
from flask_login import AnonymousUserMixin, current_user
|
||||
from flask_login import user_logged_in
|
||||
from contextlib import contextmanager
|
||||
|
||||
try:
|
||||
from flask_dance.consumer.backend.sqla import OAuthConsumerMixin
|
||||
|
@ -79,6 +81,36 @@ def delete_user_session(user_id, session_key):
|
|||
def check_user_session(user_id, session_key):
|
||||
return session_key in logged_in.get(str(user_id), [])
|
||||
|
||||
def signal_store_user_session(object, user):
|
||||
store_user_session()
|
||||
|
||||
def store_user_session():
|
||||
if flask_session.get('_user_id', ""):
|
||||
try:
|
||||
if not check_user_session(flask_session.get('_user_id', ""), flask_session.get('_id', "")):
|
||||
user_session = User_Sessions(flask_session.get('_user_id', ""), flask_session.get('_id', ""))
|
||||
session.add(user_session)
|
||||
session.commit()
|
||||
except (exc.OperationalError, exc.InvalidRequestError):
|
||||
session.rollback()
|
||||
# log.debug(flask_session.get('_id', ""))
|
||||
|
||||
def delete_user_session(user_id, session_key):
|
||||
try:
|
||||
# log.debug(session_key)
|
||||
session.query(User_Sessions).filter(User_Sessions.user_id==user_id,
|
||||
User_Sessions.session_key==session_key).delete()
|
||||
session.commit()
|
||||
except (exc.OperationalError, exc.InvalidRequestError):
|
||||
session.rollback()
|
||||
|
||||
|
||||
def check_user_session(user_id, session_key):
|
||||
return bool(session.query(User_Sessions).filter(User_Sessions.user_id==user_id,
|
||||
User_Sessions.session_key==session_key).one_or_none())
|
||||
|
||||
user_logged_in.connect(signal_store_user_session)
|
||||
|
||||
def store_ids(result):
|
||||
ids = list()
|
||||
for element in result:
|
||||
|
@ -279,6 +311,17 @@ class Anonymous(AnonymousUserMixin, UserBase):
|
|||
flask_session['view'][page][prop] = value
|
||||
return None
|
||||
|
||||
class User_Sessions(Base):
|
||||
__tablename__ = 'user_session'
|
||||
|
||||
id = Column(Integer, primary_key=True)
|
||||
user_id = Column(Integer, ForeignKey('user.id'))
|
||||
session_key = Column(String, default="")
|
||||
|
||||
def __init__(self, user_id, session_key):
|
||||
self.user_id = user_id
|
||||
self.session_key = session_key
|
||||
|
||||
|
||||
# Baseclass representing Shelfs in calibre-web in app.db
|
||||
class Shelf(Base):
|
||||
|
|
|
@ -21,7 +21,8 @@ import binascii
|
|||
|
||||
from sqlalchemy.sql.expression import func
|
||||
from werkzeug.security import check_password_hash
|
||||
from flask_login import login_required
|
||||
from flask_login import login_required, login_user
|
||||
|
||||
|
||||
from . import lm, ub, config, constants, services
|
||||
|
||||
|
@ -58,6 +59,7 @@ def load_user_from_request(request):
|
|||
if rp_header_username:
|
||||
user = _fetch_user_by_name(rp_header_username)
|
||||
if user:
|
||||
login_user(user)
|
||||
return user
|
||||
|
||||
auth_header = request.headers.get("Authorization")
|
||||
|
|
68
cps/web.py
68
cps/web.py
|
@ -360,9 +360,9 @@ def get_sort_function(sort, data):
|
|||
if sort == 'old':
|
||||
order = [db.Books.timestamp]
|
||||
if sort == 'authaz':
|
||||
order = [db.Books.author_sort.asc()]
|
||||
order = [db.Books.author_sort.asc(), db.Series.name, db.Books.series_index]
|
||||
if sort == 'authza':
|
||||
order = [db.Books.author_sort.desc()]
|
||||
order = [db.Books.author_sort.desc(), db.Series.name.desc(), db.Books.series_index.desc()]
|
||||
if sort == 'seriesasc':
|
||||
order = [db.Books.series_index.asc()]
|
||||
if sort == 'seriesdesc':
|
||||
|
@ -410,7 +410,10 @@ def render_books_list(data, sort, book_id, page):
|
|||
return render_adv_search_results(term, offset, order, config.config_books_per_page)
|
||||
else:
|
||||
website = data or "newest"
|
||||
entries, random, pagination = calibre_db.fill_indexpage(page, 0, db.Books, True, order)
|
||||
entries, random, pagination = calibre_db.fill_indexpage(page, 0, db.Books, True, order,
|
||||
db.books_series_link,
|
||||
db.Books.id == db.books_series_link.c.book,
|
||||
db.Series)
|
||||
return render_title_template('index.html', random=random, entries=entries, pagination=pagination,
|
||||
title=_(u"Books"), page=website)
|
||||
|
||||
|
@ -509,8 +512,10 @@ def render_author_books(page, author_id, order):
|
|||
flash(_(u"Oops! Selected book title is unavailable. File does not exist or is not accessible"),
|
||||
category="error")
|
||||
return redirect(url_for("web.index"))
|
||||
|
||||
author = calibre_db.session.query(db.Authors).get(author_id)
|
||||
if constants.sqlalchemy_version2:
|
||||
author = calibre_db.session.get(db.Authors, author_id)
|
||||
else:
|
||||
author = calibre_db.session.query(db.Authors).get(author_id)
|
||||
author_name = author.name.replace('|', ',')
|
||||
|
||||
author_info = None
|
||||
|
@ -713,7 +718,8 @@ def render_prepare_search_form(cc):
|
|||
|
||||
|
||||
def render_search_results(term, offset=None, order=None, limit=None):
|
||||
entries, result_count, pagination = calibre_db.get_search_results(term, offset, order, limit)
|
||||
join = db.books_series_link, db.Books.id == db.books_series_link.c.book, db.Series
|
||||
entries, result_count, pagination = calibre_db.get_search_results(term, offset, order, limit, *join)
|
||||
return render_title_template('search.html',
|
||||
searchterm=term,
|
||||
pagination=pagination,
|
||||
|
@ -775,8 +781,10 @@ def list_books():
|
|||
order = [db.Publishers.name.asc()] if order == "asc" else [db.Publishers.name.desc()]
|
||||
join = db.books_publishers_link,db.Books.id == db.books_publishers_link.c.book, db.Publishers
|
||||
elif sort == "authors":
|
||||
order = [db.Authors.name.asc()] if order == "asc" else [db.Authors.name.desc()]
|
||||
join = db.books_authors_link,db.Books.id == db.books_authors_link.c.book, db.Authors
|
||||
order = [db.Authors.name.asc(), db.Series.name, db.Books.series_index] if order == "asc" \
|
||||
else [db.Authors.name.desc(), db.Series.name.desc(), db.Books.series_index.desc()]
|
||||
join = db.books_authors_link, db.Books.id == db.books_authors_link.c.book, db.Authors, \
|
||||
db.books_series_link, db.Books.id == db.books_series_link.c.book, db.Series
|
||||
elif sort == "languages":
|
||||
order = [db.Languages.lang_code.asc()] if order == "asc" else [db.Languages.lang_code.desc()]
|
||||
join = db.books_languages_link,db.Books.id == db.books_languages_link.c.book, db.Languages
|
||||
|
@ -793,7 +801,7 @@ def list_books():
|
|||
filtered_count = len(books)
|
||||
else:
|
||||
books = calibre_db.session.query(db.Books).filter(calibre_db.common_filters()).all()
|
||||
entries = calibre_db.get_checkbox_sorted(books, state, off, limit,order)
|
||||
entries = calibre_db.get_checkbox_sorted(books, state, off, limit, order)
|
||||
elif search:
|
||||
entries, filtered_count, __ = calibre_db.get_search_results(search, off, order, limit, *join)
|
||||
else:
|
||||
|
@ -1242,7 +1250,9 @@ def render_adv_search_results(term, offset=None, order=None, limit=None):
|
|||
|
||||
cc = get_cc_columns(filter_config_custom_read=True)
|
||||
calibre_db.session.connection().connection.connection.create_function("lower", 1, db.lcase)
|
||||
q = calibre_db.session.query(db.Books).filter(calibre_db.common_filters(True))
|
||||
q = calibre_db.session.query(db.Books).outerjoin(db.books_series_link, db.Books.id == db.books_series_link.c.book)\
|
||||
.outerjoin(db.Series)\
|
||||
.filter(calibre_db.common_filters(True))
|
||||
|
||||
# parse multiselects to a complete dict
|
||||
tags = dict()
|
||||
|
@ -1591,15 +1601,14 @@ def change_profile(kobo_support, local_oauth_check, oauth_status, translations,
|
|||
if to_save.get("password"):
|
||||
current_user.password = generate_password_hash(to_save["password"])
|
||||
try:
|
||||
if to_save.get("allowed_tags", current_user.allowed_tags) != current_user.allowed_tags:
|
||||
current_user.allowed_tags = to_save["allowed_tags"].strip()
|
||||
if to_save.get("kindle_mail", current_user.kindle_mail) != current_user.kindle_mail:
|
||||
current_user.kindle_mail = valid_email(to_save["kindle_mail"])
|
||||
if to_save.get("email", current_user.email) != current_user.email:
|
||||
current_user.email = check_email(to_save["email"])
|
||||
if to_save.get("name", current_user.name) != current_user.name:
|
||||
# Query User name, if not existing, change
|
||||
current_user.name = check_username(to_save["name"])
|
||||
if current_user.role_admin():
|
||||
if to_save.get("name", current_user.name) != current_user.name:
|
||||
# Query User name, if not existing, change
|
||||
current_user.name = check_username(to_save["name"])
|
||||
current_user.random_books = 1 if to_save.get("show_random") == "on" else 0
|
||||
if to_save.get("default_language"):
|
||||
current_user.default_language = to_save["default_language"]
|
||||
|
@ -1609,10 +1618,16 @@ def change_profile(kobo_support, local_oauth_check, oauth_status, translations,
|
|||
|
||||
except Exception as ex:
|
||||
flash(str(ex), category="error")
|
||||
return render_title_template("user_edit.html", content=current_user,
|
||||
title=_(u"%(name)s's profile", name=current_user.name), page="me",
|
||||
return render_title_template("user_edit.html",
|
||||
content=current_user,
|
||||
translations=translations,
|
||||
profile=1,
|
||||
languages=languages,
|
||||
title=_(u"%(name)s's profile", name=current_user.name),
|
||||
page="me",
|
||||
kobo_support=kobo_support,
|
||||
registered_oauth=local_oauth_check, oauth_status=oauth_status)
|
||||
registered_oauth=local_oauth_check,
|
||||
oauth_status=oauth_status)
|
||||
|
||||
val = 0
|
||||
for key, __ in to_save.items():
|
||||
|
@ -1684,28 +1699,33 @@ def read_book(book_id, book_format):
|
|||
ub.Bookmark.format == book_format.upper())).first()
|
||||
if book_format.lower() == "epub":
|
||||
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":
|
||||
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":
|
||||
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":
|
||||
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:
|
||||
for fileExt in constants.EXTENSIONS_AUDIO:
|
||||
if book_format.lower() == fileExt:
|
||||
entries = calibre_db.get_filtered_book(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(),
|
||||
title=_(u"Read a Book"), entry=entries, bookmark=bookmark)
|
||||
entry=entries, bookmark=bookmark)
|
||||
for fileExt in ["cbr", "cbt", "cbz"]:
|
||||
if book_format.lower() == fileExt:
|
||||
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)
|
||||
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)
|
||||
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")
|
||||
|
|
File diff suppressed because it is too large
Load Diff
Loading…
Reference in New Issue
Block a user