Merge remote-tracking branch 'upstream/master'
This commit is contained in:
commit
5dd08e438c
|
@ -37,6 +37,7 @@ from . import config_sql, logger, cache_buster, cli, ub, db
|
||||||
from .reverseproxy import ReverseProxied
|
from .reverseproxy import ReverseProxied
|
||||||
from .server import WebServer
|
from .server import WebServer
|
||||||
|
|
||||||
|
|
||||||
mimetypes.init()
|
mimetypes.init()
|
||||||
mimetypes.add_type('application/xhtml+xml', '.xhtml')
|
mimetypes.add_type('application/xhtml+xml', '.xhtml')
|
||||||
mimetypes.add_type('application/epub+zip', '.epub')
|
mimetypes.add_type('application/epub+zip', '.epub')
|
||||||
|
@ -59,7 +60,7 @@ app = Flask(__name__)
|
||||||
app.config.update(
|
app.config.update(
|
||||||
SESSION_COOKIE_HTTPONLY=True,
|
SESSION_COOKIE_HTTPONLY=True,
|
||||||
SESSION_COOKIE_SAMESITE='Lax',
|
SESSION_COOKIE_SAMESITE='Lax',
|
||||||
REMEMBER_COOKIE_SAMESITE='Lax',
|
REMEMBER_COOKIE_SAMESITE='Lax', # will be available in flask-login 0.5.1 earliest
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@ -82,6 +83,8 @@ log = logger.create()
|
||||||
|
|
||||||
from . import services
|
from . import services
|
||||||
|
|
||||||
|
calibre_db = db.CalibreDB()
|
||||||
|
|
||||||
def create_app():
|
def create_app():
|
||||||
app.wsgi_app = ReverseProxied(app.wsgi_app)
|
app.wsgi_app = ReverseProxied(app.wsgi_app)
|
||||||
# For python2 convert path to unicode
|
# For python2 convert path to unicode
|
||||||
|
@ -98,7 +101,8 @@ def create_app():
|
||||||
app.secret_key = os.getenv('SECRET_KEY', config_sql.get_flask_session_key(ub.session))
|
app.secret_key = os.getenv('SECRET_KEY', config_sql.get_flask_session_key(ub.session))
|
||||||
|
|
||||||
web_server.init_app(app, config)
|
web_server.init_app(app, config)
|
||||||
db.setup_db(config)
|
calibre_db.setup_db(config, cli.settingspath)
|
||||||
|
calibre_db.start()
|
||||||
|
|
||||||
babel.init_app(app)
|
babel.init_app(app)
|
||||||
_BABEL_TRANSLATIONS.update(str(item) for item in babel.list_translations())
|
_BABEL_TRANSLATIONS.update(str(item) for item in babel.list_translations())
|
||||||
|
|
14
cps/about.py
14
cps/about.py
|
@ -30,7 +30,7 @@ import babel, pytz, requests, sqlalchemy
|
||||||
import werkzeug, flask, flask_login, flask_principal, jinja2
|
import werkzeug, flask, flask_login, flask_principal, jinja2
|
||||||
from flask_babel import gettext as _
|
from flask_babel import gettext as _
|
||||||
|
|
||||||
from . import db, converter, uploader, server, isoLanguages, constants
|
from . import db, calibre_db, converter, uploader, server, isoLanguages, constants
|
||||||
from .web import render_title_template
|
from .web import render_title_template
|
||||||
try:
|
try:
|
||||||
from flask_login import __version__ as flask_loginVersion
|
from flask_login import __version__ as flask_loginVersion
|
||||||
|
@ -85,10 +85,12 @@ _VERSIONS.update(uploader.get_versions())
|
||||||
@about.route("/stats")
|
@about.route("/stats")
|
||||||
@flask_login.login_required
|
@flask_login.login_required
|
||||||
def stats():
|
def stats():
|
||||||
counter = db.session.query(db.Books).count()
|
counter = calibre_db.session.query(db.Books).count()
|
||||||
authors = db.session.query(db.Authors).count()
|
authors = calibre_db.session.query(db.Authors).count()
|
||||||
categorys = db.session.query(db.Tags).count()
|
categorys = calibre_db.session.query(db.Tags).count()
|
||||||
series = db.session.query(db.Series).count()
|
series = calibre_db.session.query(db.Series).count()
|
||||||
_VERSIONS['ebook converter'] = _(converter.get_version())
|
_VERSIONS['ebook converter'] = _(converter.get_calibre_version())
|
||||||
|
_VERSIONS['unrar'] = _(converter.get_unrar_version())
|
||||||
|
_VERSIONS['kepubify'] = _(converter.get_kepubify_version())
|
||||||
return render_title_template('stats.html', bookcounter=counter, authorcounter=authors, versions=_VERSIONS,
|
return render_title_template('stats.html', bookcounter=counter, authorcounter=authors, versions=_VERSIONS,
|
||||||
categorycounter=categorys, seriecounter=series, title=_(u"Statistics"), page="stat")
|
categorycounter=categorys, seriecounter=series, title=_(u"Statistics"), page="stat")
|
||||||
|
|
591
cps/admin.py
591
cps/admin.py
|
@ -22,6 +22,7 @@
|
||||||
|
|
||||||
from __future__ import division, print_function, unicode_literals
|
from __future__ import division, print_function, unicode_literals
|
||||||
import os
|
import os
|
||||||
|
import re
|
||||||
import base64
|
import base64
|
||||||
import json
|
import json
|
||||||
import time
|
import time
|
||||||
|
@ -37,8 +38,8 @@ from sqlalchemy.exc import IntegrityError
|
||||||
from sqlalchemy.sql.expression import func
|
from sqlalchemy.sql.expression import func
|
||||||
|
|
||||||
from . import constants, logger, helper, services
|
from . import constants, logger, helper, services
|
||||||
from . import 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 speaking_language, check_valid_domain, send_test_mail, reset_password, generate_password_hash
|
from .helper import check_valid_domain, send_test_mail, reset_password, generate_password_hash
|
||||||
from .gdriveutils import is_gdrive_ready, gdrive_support
|
from .gdriveutils import is_gdrive_ready, gdrive_support
|
||||||
from .web import admin_required, render_title_template, before_request, unconfigured, login_required_if_no_ano
|
from .web import admin_required, render_title_template, before_request, unconfigured, login_required_if_no_ano
|
||||||
|
|
||||||
|
@ -85,7 +86,7 @@ def shutdown():
|
||||||
showtext = {}
|
showtext = {}
|
||||||
if task in (0, 1): # valid commandos received
|
if task in (0, 1): # valid commandos received
|
||||||
# close all database connections
|
# close all database connections
|
||||||
db.dispose()
|
calibre_db.dispose()
|
||||||
ub.dispose()
|
ub.dispose()
|
||||||
|
|
||||||
if task == 0:
|
if task == 0:
|
||||||
|
@ -98,7 +99,7 @@ def shutdown():
|
||||||
|
|
||||||
if task == 2:
|
if task == 2:
|
||||||
log.warning("reconnecting to calibre database")
|
log.warning("reconnecting to calibre database")
|
||||||
db.setup_db(config)
|
calibre_db.setup_db(config, ub.app_DB_path)
|
||||||
showtext['text'] = _(u'Reconnect successful')
|
showtext['text'] = _(u'Reconnect successful')
|
||||||
return json.dumps(showtext)
|
return json.dumps(showtext)
|
||||||
|
|
||||||
|
@ -147,10 +148,10 @@ def configuration():
|
||||||
@login_required
|
@login_required
|
||||||
@admin_required
|
@admin_required
|
||||||
def view_configuration():
|
def view_configuration():
|
||||||
readColumn = db.session.query(db.Custom_Columns)\
|
readColumn = calibre_db.session.query(db.Custom_Columns)\
|
||||||
.filter(and_(db.Custom_Columns.datatype == 'bool',db.Custom_Columns.mark_for_delete == 0)).all()
|
.filter(and_(db.Custom_Columns.datatype == 'bool', db.Custom_Columns.mark_for_delete == 0)).all()
|
||||||
restrictColumns= db.session.query(db.Custom_Columns)\
|
restrictColumns= calibre_db.session.query(db.Custom_Columns)\
|
||||||
.filter(and_(db.Custom_Columns.datatype == 'text',db.Custom_Columns.mark_for_delete == 0)).all()
|
.filter(and_(db.Custom_Columns.datatype == 'text', db.Custom_Columns.mark_for_delete == 0)).all()
|
||||||
return render_title_template("config_view_edit.html", conf=config, readColumns=readColumn,
|
return render_title_template("config_view_edit.html", conf=config, readColumns=readColumn,
|
||||||
restrictColumns=restrictColumns,
|
restrictColumns=restrictColumns,
|
||||||
title=_(u"UI Configuration"), page="uiconfig")
|
title=_(u"UI Configuration"), page="uiconfig")
|
||||||
|
@ -168,7 +169,6 @@ def update_view_configuration():
|
||||||
|
|
||||||
_config_string("config_calibre_web_title")
|
_config_string("config_calibre_web_title")
|
||||||
_config_string("config_columns_to_ignore")
|
_config_string("config_columns_to_ignore")
|
||||||
# _config_string("config_mature_content_tags")
|
|
||||||
reboot_required |= _config_string("config_title_regex")
|
reboot_required |= _config_string("config_title_regex")
|
||||||
|
|
||||||
_config_int("config_read_column")
|
_config_int("config_read_column")
|
||||||
|
@ -426,7 +426,6 @@ def delete_restriction(res_type):
|
||||||
return ""
|
return ""
|
||||||
|
|
||||||
|
|
||||||
#@admi.route("/ajax/listrestriction/<int:type>/<int:user_id>", defaults={'user_id': '0'})
|
|
||||||
@admi.route("/ajax/listrestriction/<int:res_type>")
|
@admi.route("/ajax/listrestriction/<int:res_type>")
|
||||||
@login_required
|
@login_required
|
||||||
@admin_required
|
@admin_required
|
||||||
|
@ -472,6 +471,7 @@ def list_restriction(res_type):
|
||||||
response.headers["Content-Type"] = "application/json; charset=utf-8"
|
response.headers["Content-Type"] = "application/json; charset=utf-8"
|
||||||
return response
|
return response
|
||||||
|
|
||||||
|
|
||||||
@admi.route("/config", methods=["GET", "POST"])
|
@admi.route("/config", methods=["GET", "POST"])
|
||||||
@unconfigured
|
@unconfigured
|
||||||
def basic_configuration():
|
def basic_configuration():
|
||||||
|
@ -481,19 +481,23 @@ def basic_configuration():
|
||||||
return _configuration_result()
|
return _configuration_result()
|
||||||
|
|
||||||
|
|
||||||
def _configuration_update_helper():
|
def _config_int(to_save, x, func=int):
|
||||||
reboot_required = False
|
return config.set_from_dictionary(to_save, x, func)
|
||||||
db_change = False
|
|
||||||
to_save = request.form.to_dict()
|
|
||||||
|
|
||||||
_config_string = lambda x: config.set_from_dictionary(to_save, x, lambda y: y.strip() if y else y)
|
|
||||||
_config_int = lambda x: config.set_from_dictionary(to_save, x, int)
|
|
||||||
_config_checkbox = lambda x: config.set_from_dictionary(to_save, x, lambda y: y == "on", False)
|
|
||||||
_config_checkbox_int = lambda x: config.set_from_dictionary(to_save, x, lambda y: 1 if (y == "on") else 0, 0)
|
|
||||||
|
|
||||||
db_change |= _config_string("config_calibre_dir")
|
def _config_checkbox(to_save, x):
|
||||||
|
return config.set_from_dictionary(to_save, x, lambda y: y == "on", False)
|
||||||
|
|
||||||
# Google drive setup
|
|
||||||
|
def _config_checkbox_int(to_save, x):
|
||||||
|
return config.set_from_dictionary(to_save, x, lambda y: 1 if (y == "on") else 0, 0)
|
||||||
|
|
||||||
|
|
||||||
|
def _config_string(to_save, x):
|
||||||
|
return config.set_from_dictionary(to_save, x, lambda y: y.strip() if y else y)
|
||||||
|
|
||||||
|
|
||||||
|
def _configuration_gdrive_helper(to_save):
|
||||||
if not os.path.isfile(gdriveutils.SETTINGS_YAML):
|
if not os.path.isfile(gdriveutils.SETTINGS_YAML):
|
||||||
config.config_use_google_drive = False
|
config.config_use_google_drive = False
|
||||||
|
|
||||||
|
@ -512,144 +516,173 @@ def _configuration_update_helper():
|
||||||
|
|
||||||
# always show google drive settings, but in case of error deny support
|
# always show google drive settings, but in case of error deny support
|
||||||
config.config_use_google_drive = (not gdriveError) and ("config_use_google_drive" in to_save)
|
config.config_use_google_drive = (not gdriveError) and ("config_use_google_drive" in to_save)
|
||||||
if _config_string("config_google_drive_folder"):
|
if _config_string(to_save, "config_google_drive_folder"):
|
||||||
gdriveutils.deleteDatabaseOnChange()
|
gdriveutils.deleteDatabaseOnChange()
|
||||||
|
return gdriveError
|
||||||
|
|
||||||
reboot_required |= _config_int("config_port")
|
def _configuration_oauth_helper(to_save):
|
||||||
|
active_oauths = 0
|
||||||
|
reboot_required = False
|
||||||
|
for element in oauthblueprints:
|
||||||
|
if to_save["config_" + str(element['id']) + "_oauth_client_id"] != element['oauth_client_id'] \
|
||||||
|
or to_save["config_" + str(element['id']) + "_oauth_client_secret"] != element['oauth_client_secret']:
|
||||||
|
reboot_required = True
|
||||||
|
element['oauth_client_id'] = to_save["config_" + str(element['id']) + "_oauth_client_id"]
|
||||||
|
element['oauth_client_secret'] = to_save["config_" + str(element['id']) + "_oauth_client_secret"]
|
||||||
|
if to_save["config_" + str(element['id']) + "_oauth_client_id"] \
|
||||||
|
and to_save["config_" + str(element['id']) + "_oauth_client_secret"]:
|
||||||
|
active_oauths += 1
|
||||||
|
element["active"] = 1
|
||||||
|
else:
|
||||||
|
element["active"] = 0
|
||||||
|
ub.session.query(ub.OAuthProvider).filter(ub.OAuthProvider.id == element['id']).update(
|
||||||
|
{"oauth_client_id": to_save["config_" + str(element['id']) + "_oauth_client_id"],
|
||||||
|
"oauth_client_secret": to_save["config_" + str(element['id']) + "_oauth_client_secret"],
|
||||||
|
"active": element["active"]})
|
||||||
|
return reboot_required
|
||||||
|
|
||||||
reboot_required |= _config_string("config_keyfile")
|
def _configuration_logfile_helper(to_save, gdriveError):
|
||||||
|
reboot_required = False
|
||||||
|
reboot_required |= _config_int(to_save, "config_log_level")
|
||||||
|
reboot_required |= _config_string(to_save, "config_logfile")
|
||||||
|
if not logger.is_valid_logfile(config.config_logfile):
|
||||||
|
return reboot_required, _configuration_result(_('Logfile Location is not Valid, Please Enter Correct Path'), gdriveError)
|
||||||
|
|
||||||
|
reboot_required |= _config_checkbox_int(to_save, "config_access_log")
|
||||||
|
reboot_required |= _config_string(to_save, "config_access_logfile")
|
||||||
|
if not logger.is_valid_logfile(config.config_access_logfile):
|
||||||
|
return reboot_required, _configuration_result(_('Access Logfile Location is not Valid, Please Enter Correct Path'), gdriveError)
|
||||||
|
return reboot_required, None
|
||||||
|
|
||||||
|
def _configuration_ldap_helper(to_save, gdriveError):
|
||||||
|
reboot_required = False
|
||||||
|
reboot_required |= _config_string(to_save, "config_ldap_provider_url")
|
||||||
|
reboot_required |= _config_int(to_save, "config_ldap_port")
|
||||||
|
reboot_required |= _config_int(to_save, "config_ldap_authentication")
|
||||||
|
reboot_required |= _config_string(to_save, "config_ldap_dn")
|
||||||
|
reboot_required |= _config_string(to_save, "config_ldap_serv_username")
|
||||||
|
reboot_required |= _config_string(to_save, "config_ldap_user_object")
|
||||||
|
reboot_required |= _config_string(to_save, "config_ldap_group_object_filter")
|
||||||
|
reboot_required |= _config_string(to_save, "config_ldap_group_members_field")
|
||||||
|
reboot_required |= _config_checkbox(to_save, "config_ldap_openldap")
|
||||||
|
reboot_required |= _config_int(to_save, "config_ldap_encryption")
|
||||||
|
reboot_required |= _config_string(to_save, "config_ldap_cert_path")
|
||||||
|
_config_string(to_save, "config_ldap_group_name")
|
||||||
|
if "config_ldap_serv_password" in to_save and to_save["config_ldap_serv_password"] != "":
|
||||||
|
reboot_required |= 1
|
||||||
|
config.set_from_dictionary(to_save, "config_ldap_serv_password", base64.b64encode, encode='UTF-8')
|
||||||
|
config.save()
|
||||||
|
|
||||||
|
if not config.config_ldap_provider_url \
|
||||||
|
or not config.config_ldap_port \
|
||||||
|
or not config.config_ldap_dn \
|
||||||
|
or not config.config_ldap_user_object:
|
||||||
|
return reboot_required, _configuration_result(_('Please Enter a LDAP Provider, '
|
||||||
|
'Port, DN and User Object Identifier'), gdriveError)
|
||||||
|
|
||||||
|
if config.config_ldap_authentication > constants.LDAP_AUTH_ANONYMOUS:
|
||||||
|
if config.config_ldap_authentication > constants.LDAP_AUTH_UNAUTHENTICATE:
|
||||||
|
if not config.config_ldap_serv_username or not bool(config.config_ldap_serv_password):
|
||||||
|
return reboot_required, _configuration_result('Please Enter a LDAP Service Account and Password', gdriveError)
|
||||||
|
else:
|
||||||
|
if not config.config_ldap_serv_username:
|
||||||
|
return reboot_required, _configuration_result('Please Enter a LDAP Service Account', gdriveError)
|
||||||
|
|
||||||
|
if config.config_ldap_group_object_filter:
|
||||||
|
if config.config_ldap_group_object_filter.count("%s") != 1:
|
||||||
|
return reboot_required, _configuration_result(_('LDAP Group Object Filter Needs to Have One "%s" Format Identifier'),
|
||||||
|
gdriveError)
|
||||||
|
if config.config_ldap_group_object_filter.count("(") != config.config_ldap_group_object_filter.count(")"):
|
||||||
|
return reboot_required, _configuration_result(_('LDAP Group Object Filter Has Unmatched Parenthesis'),
|
||||||
|
gdriveError)
|
||||||
|
|
||||||
|
if config.config_ldap_user_object.count("%s") != 1:
|
||||||
|
return reboot_required, _configuration_result(_('LDAP User Object Filter needs to Have One "%s" Format Identifier'),
|
||||||
|
gdriveError)
|
||||||
|
if config.config_ldap_user_object.count("(") != config.config_ldap_user_object.count(")"):
|
||||||
|
return reboot_required, _configuration_result(_('LDAP User Object Filter Has Unmatched Parenthesis'),
|
||||||
|
gdriveError)
|
||||||
|
|
||||||
|
if config.config_ldap_cert_path and not os.path.isdir(config.config_ldap_cert_path):
|
||||||
|
return reboot_required, _configuration_result(_('LDAP Certificate Location is not Valid, Please Enter Correct Path'),
|
||||||
|
gdriveError)
|
||||||
|
return reboot_required, None
|
||||||
|
|
||||||
|
|
||||||
|
def _configuration_update_helper():
|
||||||
|
reboot_required = False
|
||||||
|
db_change = False
|
||||||
|
to_save = request.form.to_dict()
|
||||||
|
|
||||||
|
to_save['config_calibre_dir'] = re.sub('[\\/]metadata\.db$', '', to_save['config_calibre_dir'], flags=re.IGNORECASE)
|
||||||
|
db_change |= _config_string(to_save, "config_calibre_dir")
|
||||||
|
|
||||||
|
# Google drive setup
|
||||||
|
gdriveError = _configuration_gdrive_helper(to_save)
|
||||||
|
|
||||||
|
reboot_required |= _config_int(to_save, "config_port")
|
||||||
|
|
||||||
|
reboot_required |= _config_string(to_save, "config_keyfile")
|
||||||
if config.config_keyfile and not os.path.isfile(config.config_keyfile):
|
if config.config_keyfile and not os.path.isfile(config.config_keyfile):
|
||||||
return _configuration_result(_('Keyfile Location is not Valid, Please Enter Correct Path'), gdriveError)
|
return _configuration_result(_('Keyfile Location is not Valid, Please Enter Correct Path'), gdriveError)
|
||||||
|
|
||||||
reboot_required |= _config_string("config_certfile")
|
reboot_required |= _config_string(to_save, "config_certfile")
|
||||||
if config.config_certfile and not os.path.isfile(config.config_certfile):
|
if config.config_certfile and not os.path.isfile(config.config_certfile):
|
||||||
return _configuration_result(_('Certfile Location is not Valid, Please Enter Correct Path'), gdriveError)
|
return _configuration_result(_('Certfile Location is not Valid, Please Enter Correct Path'), gdriveError)
|
||||||
|
|
||||||
_config_checkbox_int("config_uploading")
|
_config_checkbox_int(to_save, "config_uploading")
|
||||||
_config_checkbox_int("config_anonbrowse")
|
_config_checkbox_int(to_save, "config_anonbrowse")
|
||||||
_config_checkbox_int("config_public_reg")
|
_config_checkbox_int(to_save, "config_public_reg")
|
||||||
reboot_required |= _config_checkbox_int("config_kobo_sync")
|
_config_checkbox_int(to_save, "config_register_email")
|
||||||
_config_checkbox_int("config_kobo_proxy")
|
reboot_required |= _config_checkbox_int(to_save, "config_kobo_sync")
|
||||||
|
_config_checkbox_int(to_save, "config_kobo_proxy")
|
||||||
|
|
||||||
|
_config_string(to_save, "config_upload_formats")
|
||||||
|
constants.EXTENSIONS_UPLOAD = [x.lstrip().rstrip() for x in config.config_upload_formats.split(',')]
|
||||||
|
|
||||||
_config_int("config_ebookconverter")
|
_config_string(to_save, "config_calibre")
|
||||||
_config_string("config_calibre")
|
_config_string(to_save, "config_converterpath")
|
||||||
_config_string("config_converterpath")
|
_config_string(to_save, "config_kepubifypath")
|
||||||
|
|
||||||
reboot_required |= _config_int("config_login_type")
|
reboot_required |= _config_int(to_save, "config_login_type")
|
||||||
|
|
||||||
#LDAP configurator,
|
#LDAP configurator,
|
||||||
if config.config_login_type == constants.LOGIN_LDAP:
|
if config.config_login_type == constants.LOGIN_LDAP:
|
||||||
reboot_required |= _config_string("config_ldap_provider_url")
|
reboot, message = _configuration_ldap_helper(to_save, gdriveError)
|
||||||
reboot_required |= _config_int("config_ldap_port")
|
if message:
|
||||||
reboot_required |= _config_int("config_ldap_authentication")
|
return message
|
||||||
reboot_required |= _config_string("config_ldap_dn")
|
reboot_required |= reboot
|
||||||
reboot_required |= _config_string("config_ldap_serv_username")
|
|
||||||
reboot_required |= _config_string("config_ldap_user_object")
|
|
||||||
reboot_required |= _config_string("config_ldap_group_object_filter")
|
|
||||||
reboot_required |= _config_string("config_ldap_group_members_field")
|
|
||||||
reboot_required |= _config_checkbox("config_ldap_openldap")
|
|
||||||
reboot_required |= _config_int("config_ldap_encryption")
|
|
||||||
reboot_required |= _config_string("config_ldap_cert_path")
|
|
||||||
_config_string("config_ldap_group_name")
|
|
||||||
if "config_ldap_serv_password" in to_save and to_save["config_ldap_serv_password"] != "":
|
|
||||||
reboot_required |= 1
|
|
||||||
config.set_from_dictionary(to_save, "config_ldap_serv_password", base64.b64encode, encode='UTF-8')
|
|
||||||
config.save()
|
|
||||||
|
|
||||||
if not config.config_ldap_provider_url \
|
|
||||||
or not config.config_ldap_port \
|
|
||||||
or not config.config_ldap_dn \
|
|
||||||
or not config.config_ldap_user_object:
|
|
||||||
return _configuration_result(_('Please Enter a LDAP Provider, '
|
|
||||||
'Port, DN and User Object Identifier'), gdriveError)
|
|
||||||
|
|
||||||
if config.config_ldap_authentication > constants.LDAP_AUTH_ANONYMOUS:
|
|
||||||
if config.config_ldap_authentication > constants.LDAP_AUTH_UNAUTHENTICATE:
|
|
||||||
if not config.config_ldap_serv_username or not bool(config.config_ldap_serv_password):
|
|
||||||
return _configuration_result('Please Enter a LDAP Service Account and Password', gdriveError)
|
|
||||||
else:
|
|
||||||
if not config.config_ldap_serv_username:
|
|
||||||
return _configuration_result('Please Enter a LDAP Service Account', gdriveError)
|
|
||||||
|
|
||||||
#_config_checkbox("config_ldap_use_ssl")
|
|
||||||
#_config_checkbox("config_ldap_use_tls")
|
|
||||||
# reboot_required |= _config_checkbox("config_ldap_openldap")
|
|
||||||
# _config_checkbox("config_ldap_require_cert")
|
|
||||||
|
|
||||||
if config.config_ldap_group_object_filter:
|
|
||||||
if config.config_ldap_group_object_filter.count("%s") != 1:
|
|
||||||
return _configuration_result(_('LDAP Group Object Filter Needs to Have One "%s" Format Identifier'),
|
|
||||||
gdriveError)
|
|
||||||
if config.config_ldap_group_object_filter.count("(") != config.config_ldap_group_object_filter.count(")"):
|
|
||||||
return _configuration_result(_('LDAP Group Object Filter Has Unmatched Parenthesis'),
|
|
||||||
gdriveError)
|
|
||||||
|
|
||||||
if config.config_ldap_user_object.count("%s") != 1:
|
|
||||||
return _configuration_result(_('LDAP User Object Filter needs to Have One "%s" Format Identifier'),
|
|
||||||
gdriveError)
|
|
||||||
if config.config_ldap_user_object.count("(") != config.config_ldap_user_object.count(")"):
|
|
||||||
return _configuration_result(_('LDAP User Object Filter Has Unmatched Parenthesis'),
|
|
||||||
gdriveError)
|
|
||||||
|
|
||||||
if config.config_ldap_cert_path and not os.path.isdir(config.config_ldap_cert_path):
|
|
||||||
return _configuration_result(_('LDAP Certificate Location is not Valid, Please Enter Correct Path'),
|
|
||||||
gdriveError)
|
|
||||||
|
|
||||||
# Remote login configuration
|
# Remote login configuration
|
||||||
_config_checkbox("config_remote_login")
|
_config_checkbox(to_save, "config_remote_login")
|
||||||
if not config.config_remote_login:
|
if not config.config_remote_login:
|
||||||
ub.session.query(ub.RemoteAuthToken).filter(ub.RemoteAuthToken.token_type==0).delete()
|
ub.session.query(ub.RemoteAuthToken).filter(ub.RemoteAuthToken.token_type==0).delete()
|
||||||
|
|
||||||
# Goodreads configuration
|
# Goodreads configuration
|
||||||
_config_checkbox("config_use_goodreads")
|
_config_checkbox(to_save, "config_use_goodreads")
|
||||||
_config_string("config_goodreads_api_key")
|
_config_string(to_save, "config_goodreads_api_key")
|
||||||
_config_string("config_goodreads_api_secret")
|
_config_string(to_save, "config_goodreads_api_secret")
|
||||||
if services.goodreads_support:
|
if services.goodreads_support:
|
||||||
services.goodreads_support.connect(config.config_goodreads_api_key,
|
services.goodreads_support.connect(config.config_goodreads_api_key,
|
||||||
config.config_goodreads_api_secret,
|
config.config_goodreads_api_secret,
|
||||||
config.config_use_goodreads)
|
config.config_use_goodreads)
|
||||||
|
|
||||||
_config_int("config_updatechannel")
|
_config_int(to_save, "config_updatechannel")
|
||||||
|
|
||||||
# Reverse proxy login configuration
|
# Reverse proxy login configuration
|
||||||
_config_checkbox("config_allow_reverse_proxy_header_login")
|
_config_checkbox(to_save, "config_allow_reverse_proxy_header_login")
|
||||||
_config_string("config_reverse_proxy_login_header_name")
|
_config_string(to_save, "config_reverse_proxy_login_header_name")
|
||||||
|
|
||||||
# GitHub OAuth configuration
|
# OAuth configuration
|
||||||
if config.config_login_type == constants.LOGIN_OAUTH:
|
if config.config_login_type == constants.LOGIN_OAUTH:
|
||||||
active_oauths = 0
|
reboot_required |= _configuration_oauth_helper(to_save)
|
||||||
|
|
||||||
for element in oauthblueprints:
|
|
||||||
if to_save["config_" + str(element['id']) + "_oauth_client_id"] != element['oauth_client_id'] \
|
|
||||||
or to_save["config_" + str(element['id']) + "_oauth_client_secret"] != element['oauth_client_secret']:
|
|
||||||
reboot_required = True
|
|
||||||
element['oauth_client_id'] = to_save["config_" + str(element['id']) + "_oauth_client_id"]
|
|
||||||
element['oauth_client_secret'] = to_save["config_" + str(element['id']) + "_oauth_client_secret"]
|
|
||||||
if to_save["config_"+str(element['id'])+"_oauth_client_id"] \
|
|
||||||
and to_save["config_"+str(element['id'])+"_oauth_client_secret"]:
|
|
||||||
active_oauths += 1
|
|
||||||
element["active"] = 1
|
|
||||||
else:
|
|
||||||
element["active"] = 0
|
|
||||||
ub.session.query(ub.OAuthProvider).filter(ub.OAuthProvider.id == element['id']).update(
|
|
||||||
{"oauth_client_id":to_save["config_"+str(element['id'])+"_oauth_client_id"],
|
|
||||||
"oauth_client_secret":to_save["config_"+str(element['id'])+"_oauth_client_secret"],
|
|
||||||
"active":element["active"]})
|
|
||||||
|
|
||||||
|
|
||||||
reboot_required |= _config_int("config_log_level")
|
|
||||||
reboot_required |= _config_string("config_logfile")
|
|
||||||
if not logger.is_valid_logfile(config.config_logfile):
|
|
||||||
return _configuration_result(_('Logfile Location is not Valid, Please Enter Correct Path'), gdriveError)
|
|
||||||
|
|
||||||
reboot_required |= _config_checkbox_int("config_access_log")
|
|
||||||
reboot_required |= _config_string("config_access_logfile")
|
|
||||||
if not logger.is_valid_logfile(config.config_access_logfile):
|
|
||||||
return _configuration_result(_('Access Logfile Location is not Valid, Please Enter Correct Path'), gdriveError)
|
|
||||||
|
|
||||||
|
reboot, message = _configuration_logfile_helper(to_save, gdriveError)
|
||||||
|
if message:
|
||||||
|
return message
|
||||||
|
reboot_required |= reboot
|
||||||
# Rarfile Content configuration
|
# Rarfile Content configuration
|
||||||
_config_string("config_rarfile_location")
|
_config_string(to_save, "config_rarfile_location")
|
||||||
unrar_status = helper.check_unrar(config.config_rarfile_location)
|
unrar_status = helper.check_unrar(config.config_rarfile_location)
|
||||||
if unrar_status:
|
if unrar_status:
|
||||||
return _configuration_result(unrar_status, gdriveError)
|
return _configuration_result(unrar_status, gdriveError)
|
||||||
|
@ -663,9 +696,10 @@ def _configuration_update_helper():
|
||||||
return _configuration_result('%s' % e, gdriveError)
|
return _configuration_result('%s' % e, gdriveError)
|
||||||
|
|
||||||
if db_change:
|
if db_change:
|
||||||
# reload(db)
|
if not calibre_db.setup_db(config, ub.app_DB_path):
|
||||||
if not db.setup_db(config):
|
|
||||||
return _configuration_result(_('DB Location is not Valid, Please Enter Correct Path'), gdriveError)
|
return _configuration_result(_('DB Location is not Valid, Please Enter Correct Path'), gdriveError)
|
||||||
|
if not os.access(os.path.join(config.config_calibre_dir, "metadata.db"), os.W_OK):
|
||||||
|
flash(_(u"DB is not Writeable"), category="warning")
|
||||||
|
|
||||||
config.save()
|
config.save()
|
||||||
flash(_(u"Calibre-Web configuration updated"), category="success")
|
flash(_(u"Calibre-Web configuration updated"), category="success")
|
||||||
|
@ -701,62 +735,155 @@ def _configuration_result(error_flash=None, gdriveError=None):
|
||||||
title=_(u"Basic Configuration"), page="config")
|
title=_(u"Basic Configuration"), page="config")
|
||||||
|
|
||||||
|
|
||||||
|
def _handle_new_user(to_save, content,languages, translations, kobo_support):
|
||||||
|
content.default_language = to_save["default_language"]
|
||||||
|
# content.mature_content = "Show_mature_content" in to_save
|
||||||
|
content.locale = to_save.get("locale", content.locale)
|
||||||
|
|
||||||
|
content.sidebar_view = sum(int(key[5:]) for key in to_save if key.startswith('show_'))
|
||||||
|
if "show_detail_random" in to_save:
|
||||||
|
content.sidebar_view |= constants.DETAIL_RANDOM
|
||||||
|
|
||||||
|
content.role = constants.selected_roles(to_save)
|
||||||
|
|
||||||
|
if not to_save["nickname"] or not to_save["email"] or not to_save["password"]:
|
||||||
|
flash(_(u"Please fill out all fields!"), category="error")
|
||||||
|
return render_title_template("user_edit.html", new_user=1, content=content, translations=translations,
|
||||||
|
registered_oauth=oauth_check, kobo_support=kobo_support,
|
||||||
|
title=_(u"Add new user"))
|
||||||
|
content.password = generate_password_hash(to_save["password"])
|
||||||
|
existing_user = ub.session.query(ub.User).filter(func.lower(ub.User.nickname) == to_save["nickname"].lower()) \
|
||||||
|
.first()
|
||||||
|
existing_email = ub.session.query(ub.User).filter(ub.User.email == to_save["email"].lower()) \
|
||||||
|
.first()
|
||||||
|
if not existing_user and not existing_email:
|
||||||
|
content.nickname = to_save["nickname"]
|
||||||
|
if config.config_public_reg and not check_valid_domain(to_save["email"]):
|
||||||
|
flash(_(u"E-mail is not from valid domain"), category="error")
|
||||||
|
return render_title_template("user_edit.html", new_user=1, content=content, translations=translations,
|
||||||
|
registered_oauth=oauth_check, kobo_support=kobo_support,
|
||||||
|
title=_(u"Add new user"))
|
||||||
|
else:
|
||||||
|
content.email = to_save["email"]
|
||||||
|
else:
|
||||||
|
flash(_(u"Found an existing account for this e-mail address or nickname."), category="error")
|
||||||
|
return render_title_template("user_edit.html", new_user=1, content=content, translations=translations,
|
||||||
|
languages=languages, title=_(u"Add new user"), page="newuser",
|
||||||
|
kobo_support=kobo_support, registered_oauth=oauth_check)
|
||||||
|
try:
|
||||||
|
content.allowed_tags = config.config_allowed_tags
|
||||||
|
content.denied_tags = config.config_denied_tags
|
||||||
|
content.allowed_column_value = config.config_allowed_column_value
|
||||||
|
content.denied_column_value = config.config_denied_column_value
|
||||||
|
ub.session.add(content)
|
||||||
|
ub.session.commit()
|
||||||
|
flash(_(u"User '%(user)s' created", user=content.nickname), category="success")
|
||||||
|
return redirect(url_for('admin.admin'))
|
||||||
|
except IntegrityError:
|
||||||
|
ub.session.rollback()
|
||||||
|
flash(_(u"Found an existing account for this e-mail address or nickname."), category="error")
|
||||||
|
|
||||||
|
|
||||||
|
def _handle_edit_user(to_save, content,languages, translations, kobo_support, downloads):
|
||||||
|
if "delete" in to_save:
|
||||||
|
if ub.session.query(ub.User).filter(ub.User.role.op('&')(constants.ROLE_ADMIN) == constants.ROLE_ADMIN,
|
||||||
|
ub.User.id != content.id).count():
|
||||||
|
ub.session.query(ub.User).filter(ub.User.id == content.id).delete()
|
||||||
|
ub.session.commit()
|
||||||
|
flash(_(u"User '%(nick)s' deleted", nick=content.nickname), category="success")
|
||||||
|
return redirect(url_for('admin.admin'))
|
||||||
|
else:
|
||||||
|
flash(_(u"No admin user remaining, can't delete user", nick=content.nickname), category="error")
|
||||||
|
return redirect(url_for('admin.admin'))
|
||||||
|
else:
|
||||||
|
if not ub.session.query(ub.User).filter(ub.User.role.op('&')(constants.ROLE_ADMIN) == constants.ROLE_ADMIN,
|
||||||
|
ub.User.id != content.id).count() and \
|
||||||
|
not 'admin_role' in to_save:
|
||||||
|
flash(_(u"No admin user remaining, can't remove admin role", nick=content.nickname), category="error")
|
||||||
|
return redirect(url_for('admin.admin'))
|
||||||
|
|
||||||
|
if "password" in to_save and to_save["password"]:
|
||||||
|
content.password = generate_password_hash(to_save["password"])
|
||||||
|
anonymous = content.is_anonymous
|
||||||
|
content.role = constants.selected_roles(to_save)
|
||||||
|
if anonymous:
|
||||||
|
content.role |= constants.ROLE_ANONYMOUS
|
||||||
|
else:
|
||||||
|
content.role &= ~constants.ROLE_ANONYMOUS
|
||||||
|
|
||||||
|
val = [int(k[5:]) for k in to_save if k.startswith('show_')]
|
||||||
|
sidebar = ub.get_sidebar_config()
|
||||||
|
for element in sidebar:
|
||||||
|
value = element['visibility']
|
||||||
|
if value in val and not content.check_visibility(value):
|
||||||
|
content.sidebar_view |= value
|
||||||
|
elif not value in val and content.check_visibility(value):
|
||||||
|
content.sidebar_view &= ~value
|
||||||
|
|
||||||
|
if "Show_detail_random" in to_save:
|
||||||
|
content.sidebar_view |= constants.DETAIL_RANDOM
|
||||||
|
else:
|
||||||
|
content.sidebar_view &= ~constants.DETAIL_RANDOM
|
||||||
|
|
||||||
|
if "default_language" in to_save:
|
||||||
|
content.default_language = to_save["default_language"]
|
||||||
|
if "locale" in to_save and to_save["locale"]:
|
||||||
|
content.locale = to_save["locale"]
|
||||||
|
if to_save["email"] and to_save["email"] != content.email:
|
||||||
|
existing_email = ub.session.query(ub.User).filter(ub.User.email == to_save["email"].lower()) \
|
||||||
|
.first()
|
||||||
|
if not existing_email:
|
||||||
|
content.email = to_save["email"]
|
||||||
|
else:
|
||||||
|
flash(_(u"Found an existing account for this e-mail address."), category="error")
|
||||||
|
return render_title_template("user_edit.html",
|
||||||
|
translations=translations,
|
||||||
|
languages=languages,
|
||||||
|
mail_configured=config.get_mail_server_configured(),
|
||||||
|
kobo_support=kobo_support,
|
||||||
|
new_user=0,
|
||||||
|
content=content,
|
||||||
|
downloads=downloads,
|
||||||
|
registered_oauth=oauth_check,
|
||||||
|
title=_(u"Edit User %(nick)s", nick=content.nickname), page="edituser")
|
||||||
|
if "nickname" in to_save and to_save["nickname"] != content.nickname:
|
||||||
|
# Query User nickname, if not existing, change
|
||||||
|
if not ub.session.query(ub.User).filter(ub.User.nickname == to_save["nickname"]).scalar():
|
||||||
|
content.nickname = to_save["nickname"]
|
||||||
|
else:
|
||||||
|
flash(_(u"This username is already taken"), category="error")
|
||||||
|
return render_title_template("user_edit.html",
|
||||||
|
translations=translations,
|
||||||
|
languages=languages,
|
||||||
|
mail_configured=config.get_mail_server_configured(),
|
||||||
|
new_user=0, content=content,
|
||||||
|
downloads=downloads,
|
||||||
|
registered_oauth=oauth_check,
|
||||||
|
kobo_support=kobo_support,
|
||||||
|
title=_(u"Edit User %(nick)s", nick=content.nickname),
|
||||||
|
page="edituser")
|
||||||
|
|
||||||
|
if "kindle_mail" in to_save and to_save["kindle_mail"] != content.kindle_mail:
|
||||||
|
content.kindle_mail = to_save["kindle_mail"]
|
||||||
|
try:
|
||||||
|
ub.session.commit()
|
||||||
|
flash(_(u"User '%(nick)s' updated", nick=content.nickname), category="success")
|
||||||
|
except IntegrityError:
|
||||||
|
ub.session.rollback()
|
||||||
|
flash(_(u"An unknown error occured."), category="error")
|
||||||
|
|
||||||
|
|
||||||
@admi.route("/admin/user/new", methods=["GET", "POST"])
|
@admi.route("/admin/user/new", methods=["GET", "POST"])
|
||||||
@login_required
|
@login_required
|
||||||
@admin_required
|
@admin_required
|
||||||
def new_user():
|
def new_user():
|
||||||
content = ub.User()
|
content = ub.User()
|
||||||
languages = speaking_language()
|
languages = calibre_db.speaking_language()
|
||||||
translations = [LC('en')] + babel.list_translations()
|
translations = [LC('en')] + babel.list_translations()
|
||||||
kobo_support = feature_support['kobo'] and config.config_kobo_sync
|
kobo_support = feature_support['kobo'] and config.config_kobo_sync
|
||||||
if request.method == "POST":
|
if request.method == "POST":
|
||||||
to_save = request.form.to_dict()
|
to_save = request.form.to_dict()
|
||||||
content.default_language = to_save["default_language"]
|
_handle_new_user(to_save, content, languages, translations, kobo_support)
|
||||||
# content.mature_content = "Show_mature_content" in to_save
|
|
||||||
content.locale = to_save.get("locale", content.locale)
|
|
||||||
|
|
||||||
content.sidebar_view = sum(int(key[5:]) for key in to_save if key.startswith('show_'))
|
|
||||||
if "show_detail_random" in to_save:
|
|
||||||
content.sidebar_view |= constants.DETAIL_RANDOM
|
|
||||||
|
|
||||||
content.role = constants.selected_roles(to_save)
|
|
||||||
|
|
||||||
if not to_save["nickname"] or not to_save["email"] or not to_save["password"]:
|
|
||||||
flash(_(u"Please fill out all fields!"), category="error")
|
|
||||||
return render_title_template("user_edit.html", new_user=1, content=content, translations=translations,
|
|
||||||
registered_oauth=oauth_check, kobo_support=kobo_support,
|
|
||||||
title=_(u"Add new user"))
|
|
||||||
content.password = generate_password_hash(to_save["password"])
|
|
||||||
existing_user = ub.session.query(ub.User).filter(func.lower(ub.User.nickname) == to_save["nickname"].lower())\
|
|
||||||
.first()
|
|
||||||
existing_email = ub.session.query(ub.User).filter(ub.User.email == to_save["email"].lower())\
|
|
||||||
.first()
|
|
||||||
if not existing_user and not existing_email:
|
|
||||||
content.nickname = to_save["nickname"]
|
|
||||||
if config.config_public_reg and not check_valid_domain(to_save["email"]):
|
|
||||||
flash(_(u"E-mail is not from valid domain"), category="error")
|
|
||||||
return render_title_template("user_edit.html", new_user=1, content=content, translations=translations,
|
|
||||||
registered_oauth=oauth_check, kobo_support=kobo_support,
|
|
||||||
title=_(u"Add new user"))
|
|
||||||
else:
|
|
||||||
content.email = to_save["email"]
|
|
||||||
else:
|
|
||||||
flash(_(u"Found an existing account for this e-mail address or nickname."), category="error")
|
|
||||||
return render_title_template("user_edit.html", new_user=1, content=content, translations=translations,
|
|
||||||
languages=languages, title=_(u"Add new user"), page="newuser",
|
|
||||||
kobo_support=kobo_support, registered_oauth=oauth_check)
|
|
||||||
try:
|
|
||||||
content.allowed_tags = config.config_allowed_tags
|
|
||||||
content.denied_tags = config.config_denied_tags
|
|
||||||
content.allowed_column_value = config.config_allowed_column_value
|
|
||||||
content.denied_column_value = config.config_denied_column_value
|
|
||||||
ub.session.add(content)
|
|
||||||
ub.session.commit()
|
|
||||||
flash(_(u"User '%(user)s' created", user=content.nickname), category="success")
|
|
||||||
return redirect(url_for('admin.admin'))
|
|
||||||
except IntegrityError:
|
|
||||||
ub.session.rollback()
|
|
||||||
flash(_(u"Found an existing account for this e-mail address or nickname."), category="error")
|
|
||||||
else:
|
else:
|
||||||
content.role = config.config_default_role
|
content.role = config.config_default_role
|
||||||
content.sidebar_view = config.config_default_show
|
content.sidebar_view = config.config_default_show
|
||||||
|
@ -770,8 +897,7 @@ def new_user():
|
||||||
@admin_required
|
@admin_required
|
||||||
def edit_mailsettings():
|
def edit_mailsettings():
|
||||||
content = config.get_mail_settings()
|
content = config.get_mail_settings()
|
||||||
# log.debug("edit_mailsettings %r", content)
|
return render_title_template("email_edit.html", content=content, title=_(u"Edit E-mail Server Settings"),
|
||||||
return render_title_template("email_edit.html", content=content, title=_(u"Edit e-mail server settings"),
|
|
||||||
page="mailset")
|
page="mailset")
|
||||||
|
|
||||||
|
|
||||||
|
@ -780,17 +906,15 @@ def edit_mailsettings():
|
||||||
@admin_required
|
@admin_required
|
||||||
def update_mailsettings():
|
def update_mailsettings():
|
||||||
to_save = request.form.to_dict()
|
to_save = request.form.to_dict()
|
||||||
log.debug("update_mailsettings %r", to_save)
|
# log.debug("update_mailsettings %r", to_save)
|
||||||
|
|
||||||
_config_string = lambda x: config.set_from_dictionary(to_save, x, lambda y: y.strip() if y else y)
|
_config_string(to_save, "mail_server")
|
||||||
_config_int = lambda x: config.set_from_dictionary(to_save, x, int)
|
_config_int(to_save, "mail_port")
|
||||||
|
_config_int(to_save, "mail_use_ssl")
|
||||||
_config_string("mail_server")
|
_config_string(to_save, "mail_login")
|
||||||
_config_int("mail_port")
|
_config_string(to_save, "mail_password")
|
||||||
_config_int("mail_use_ssl")
|
_config_string(to_save, "mail_from")
|
||||||
_config_string("mail_login")
|
_config_int(to_save, "mail_size", lambda y: int(y)*1024*1024)
|
||||||
_config_string("mail_password")
|
|
||||||
_config_string("mail_from")
|
|
||||||
config.save()
|
config.save()
|
||||||
|
|
||||||
if to_save.get("test"):
|
if to_save.get("test"):
|
||||||
|
@ -818,105 +942,18 @@ def edit_user(user_id):
|
||||||
flash(_(u"User not found"), category="error")
|
flash(_(u"User not found"), category="error")
|
||||||
return redirect(url_for('admin.admin'))
|
return redirect(url_for('admin.admin'))
|
||||||
downloads = list()
|
downloads = list()
|
||||||
languages = speaking_language()
|
languages = calibre_db.speaking_language()
|
||||||
translations = babel.list_translations() + [LC('en')]
|
translations = babel.list_translations() + [LC('en')]
|
||||||
kobo_support = feature_support['kobo'] and config.config_kobo_sync
|
kobo_support = feature_support['kobo'] and config.config_kobo_sync
|
||||||
for book in content.downloads:
|
for book in content.downloads:
|
||||||
downloadbook = db.session.query(db.Books).filter(db.Books.id == book.book_id).first()
|
downloadbook = calibre_db.get_book(book.book_id)
|
||||||
if downloadbook:
|
if downloadbook:
|
||||||
downloads.append(downloadbook)
|
downloads.append(downloadbook)
|
||||||
else:
|
else:
|
||||||
ub.delete_download(book.book_id)
|
ub.delete_download(book.book_id)
|
||||||
# ub.session.query(ub.Downloads).filter(book.book_id == ub.Downloads.book_id).delete()
|
|
||||||
# ub.session.commit()
|
|
||||||
if request.method == "POST":
|
if request.method == "POST":
|
||||||
to_save = request.form.to_dict()
|
to_save = request.form.to_dict()
|
||||||
if "delete" in to_save:
|
_handle_edit_user(to_save, content, languages, translations, kobo_support, downloads)
|
||||||
if ub.session.query(ub.User).filter(ub.User.role.op('&')(constants.ROLE_ADMIN) == constants.ROLE_ADMIN,
|
|
||||||
ub.User.id != content.id).count():
|
|
||||||
ub.session.query(ub.User).filter(ub.User.id == content.id).delete()
|
|
||||||
ub.session.commit()
|
|
||||||
flash(_(u"User '%(nick)s' deleted", nick=content.nickname), category="success")
|
|
||||||
return redirect(url_for('admin.admin'))
|
|
||||||
else:
|
|
||||||
flash(_(u"No admin user remaining, can't delete user", nick=content.nickname), category="error")
|
|
||||||
return redirect(url_for('admin.admin'))
|
|
||||||
else:
|
|
||||||
if not ub.session.query(ub.User).filter(ub.User.role.op('&')(constants.ROLE_ADMIN) == constants.ROLE_ADMIN,
|
|
||||||
ub.User.id != content.id).count() and \
|
|
||||||
not 'admin_role' in to_save:
|
|
||||||
flash(_(u"No admin user remaining, can't remove admin role", nick=content.nickname), category="error")
|
|
||||||
return redirect(url_for('admin.admin'))
|
|
||||||
|
|
||||||
if "password" in to_save and to_save["password"]:
|
|
||||||
content.password = generate_password_hash(to_save["password"])
|
|
||||||
anonymous = content.is_anonymous
|
|
||||||
content.role = constants.selected_roles(to_save)
|
|
||||||
if anonymous:
|
|
||||||
content.role |= constants.ROLE_ANONYMOUS
|
|
||||||
else:
|
|
||||||
content.role &= ~constants.ROLE_ANONYMOUS
|
|
||||||
|
|
||||||
val = [int(k[5:]) for k in to_save if k.startswith('show_')]
|
|
||||||
sidebar = ub.get_sidebar_config()
|
|
||||||
for element in sidebar:
|
|
||||||
value = element['visibility']
|
|
||||||
if value in val and not content.check_visibility(value):
|
|
||||||
content.sidebar_view |= value
|
|
||||||
elif not value in val and content.check_visibility(value):
|
|
||||||
content.sidebar_view &= ~value
|
|
||||||
|
|
||||||
if "Show_detail_random" in to_save:
|
|
||||||
content.sidebar_view |= constants.DETAIL_RANDOM
|
|
||||||
else:
|
|
||||||
content.sidebar_view &= ~constants.DETAIL_RANDOM
|
|
||||||
|
|
||||||
if "default_language" in to_save:
|
|
||||||
content.default_language = to_save["default_language"]
|
|
||||||
if "locale" in to_save and to_save["locale"]:
|
|
||||||
content.locale = to_save["locale"]
|
|
||||||
if to_save["email"] and to_save["email"] != content.email:
|
|
||||||
existing_email = ub.session.query(ub.User).filter(ub.User.email == to_save["email"].lower()) \
|
|
||||||
.first()
|
|
||||||
if not existing_email:
|
|
||||||
content.email = to_save["email"]
|
|
||||||
else:
|
|
||||||
flash(_(u"Found an existing account for this e-mail address."), category="error")
|
|
||||||
return render_title_template("user_edit.html",
|
|
||||||
translations=translations,
|
|
||||||
languages=languages,
|
|
||||||
mail_configured = config.get_mail_server_configured(),
|
|
||||||
kobo_support=kobo_support,
|
|
||||||
new_user=0,
|
|
||||||
content=content,
|
|
||||||
downloads=downloads,
|
|
||||||
registered_oauth=oauth_check,
|
|
||||||
title=_(u"Edit User %(nick)s", nick=content.nickname), page="edituser")
|
|
||||||
if "nickname" in to_save and to_save["nickname"] != content.nickname:
|
|
||||||
# Query User nickname, if not existing, change
|
|
||||||
if not ub.session.query(ub.User).filter(ub.User.nickname == to_save["nickname"]).scalar():
|
|
||||||
content.nickname = to_save["nickname"]
|
|
||||||
else:
|
|
||||||
flash(_(u"This username is already taken"), category="error")
|
|
||||||
return render_title_template("user_edit.html",
|
|
||||||
translations=translations,
|
|
||||||
languages=languages,
|
|
||||||
mail_configured=config.get_mail_server_configured(),
|
|
||||||
new_user=0, content=content,
|
|
||||||
downloads=downloads,
|
|
||||||
registered_oauth=oauth_check,
|
|
||||||
kobo_support=kobo_support,
|
|
||||||
title=_(u"Edit User %(nick)s", nick=content.nickname),
|
|
||||||
page="edituser")
|
|
||||||
|
|
||||||
if "kindle_mail" in to_save and to_save["kindle_mail"] != content.kindle_mail:
|
|
||||||
content.kindle_mail = to_save["kindle_mail"]
|
|
||||||
try:
|
|
||||||
ub.session.commit()
|
|
||||||
flash(_(u"User '%(nick)s' updated", nick=content.nickname), category="success")
|
|
||||||
except IntegrityError:
|
|
||||||
ub.session.rollback()
|
|
||||||
flash(_(u"An unknown error occured."), category="error")
|
|
||||||
return render_title_template("user_edit.html",
|
return render_title_template("user_edit.html",
|
||||||
translations=translations,
|
translations=translations,
|
||||||
languages=languages,
|
languages=languages,
|
||||||
|
|
55
cps/comic.py
55
cps/comic.py
|
@ -18,10 +18,17 @@
|
||||||
|
|
||||||
from __future__ import division, print_function, unicode_literals
|
from __future__ import division, print_function, unicode_literals
|
||||||
import os
|
import os
|
||||||
|
import io
|
||||||
|
|
||||||
from . import logger, isoLanguages
|
from . import logger, isoLanguages
|
||||||
from .constants import BookMeta
|
from .constants import BookMeta
|
||||||
|
|
||||||
|
try:
|
||||||
|
from PIL import Image as PILImage
|
||||||
|
use_PIL = True
|
||||||
|
except ImportError as e:
|
||||||
|
use_PIL = False
|
||||||
|
|
||||||
|
|
||||||
log = logger.create()
|
log = logger.create()
|
||||||
|
|
||||||
|
@ -29,18 +36,43 @@ log = logger.create()
|
||||||
try:
|
try:
|
||||||
from comicapi.comicarchive import ComicArchive, MetaDataStyle
|
from comicapi.comicarchive import ComicArchive, MetaDataStyle
|
||||||
use_comic_meta = True
|
use_comic_meta = True
|
||||||
|
try:
|
||||||
|
from comicapi import __version__ as comic_version
|
||||||
|
except (ImportError):
|
||||||
|
comic_version = ''
|
||||||
except ImportError as e:
|
except ImportError as e:
|
||||||
log.debug('cannot import comicapi, extracting comic metadata will not work: %s', e)
|
log.debug('Cannot import comicapi, extracting comic metadata will not work: %s', e)
|
||||||
import zipfile
|
import zipfile
|
||||||
import tarfile
|
import tarfile
|
||||||
try:
|
try:
|
||||||
import rarfile
|
import rarfile
|
||||||
use_rarfile = True
|
use_rarfile = True
|
||||||
except ImportError as e:
|
except ImportError as e:
|
||||||
log.debug('cannot import rarfile, extracting cover files from rar files will not work: %s', e)
|
log.debug('Cannot import rarfile, extracting cover files from rar files will not work: %s', e)
|
||||||
use_rarfile = False
|
use_rarfile = False
|
||||||
use_comic_meta = False
|
use_comic_meta = False
|
||||||
|
|
||||||
|
def _cover_processing(tmp_file_name, img, extension):
|
||||||
|
if use_PIL:
|
||||||
|
# convert to jpg because calibre only supports jpg
|
||||||
|
if extension in ('.png', '.webp'):
|
||||||
|
imgc = PILImage.open(io.BytesIO(img))
|
||||||
|
im = imgc.convert('RGB')
|
||||||
|
tmp_bytesio = io.BytesIO()
|
||||||
|
im.save(tmp_bytesio, format='JPEG')
|
||||||
|
img = tmp_bytesio.getvalue()
|
||||||
|
|
||||||
|
prefix = os.path.dirname(tmp_file_name)
|
||||||
|
if img:
|
||||||
|
tmp_cover_name = prefix + '/cover.jpg'
|
||||||
|
image = open(tmp_cover_name, 'wb')
|
||||||
|
image.write(img)
|
||||||
|
image.close()
|
||||||
|
else:
|
||||||
|
tmp_cover_name = None
|
||||||
|
return tmp_cover_name
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
def _extractCover(tmp_file_name, original_file_extension, rarExceutable):
|
def _extractCover(tmp_file_name, original_file_extension, rarExceutable):
|
||||||
cover_data = extension = None
|
cover_data = extension = None
|
||||||
|
@ -50,7 +82,7 @@ def _extractCover(tmp_file_name, original_file_extension, rarExceutable):
|
||||||
ext = os.path.splitext(name)
|
ext = os.path.splitext(name)
|
||||||
if len(ext) > 1:
|
if len(ext) > 1:
|
||||||
extension = ext[1].lower()
|
extension = ext[1].lower()
|
||||||
if extension == '.jpg' or extension == '.jpeg':
|
if extension in ('.jpg', '.jpeg', '.png', '.webp'):
|
||||||
cover_data = archive.getPage(index)
|
cover_data = archive.getPage(index)
|
||||||
break
|
break
|
||||||
else:
|
else:
|
||||||
|
@ -60,7 +92,7 @@ def _extractCover(tmp_file_name, original_file_extension, rarExceutable):
|
||||||
ext = os.path.splitext(name)
|
ext = os.path.splitext(name)
|
||||||
if len(ext) > 1:
|
if len(ext) > 1:
|
||||||
extension = ext[1].lower()
|
extension = ext[1].lower()
|
||||||
if extension == '.jpg' or extension == '.jpeg':
|
if extension in ('.jpg', '.jpeg', '.png', '.webp'):
|
||||||
cover_data = cf.read(name)
|
cover_data = cf.read(name)
|
||||||
break
|
break
|
||||||
elif original_file_extension.upper() == '.CBT':
|
elif original_file_extension.upper() == '.CBT':
|
||||||
|
@ -69,7 +101,7 @@ def _extractCover(tmp_file_name, original_file_extension, rarExceutable):
|
||||||
ext = os.path.splitext(name)
|
ext = os.path.splitext(name)
|
||||||
if len(ext) > 1:
|
if len(ext) > 1:
|
||||||
extension = ext[1].lower()
|
extension = ext[1].lower()
|
||||||
if extension == '.jpg' or extension == '.jpeg':
|
if extension in ('.jpg', '.jpeg', '.png', '.webp'):
|
||||||
cover_data = cf.extractfile(name).read()
|
cover_data = cf.extractfile(name).read()
|
||||||
break
|
break
|
||||||
elif original_file_extension.upper() == '.CBR' and use_rarfile:
|
elif original_file_extension.upper() == '.CBR' and use_rarfile:
|
||||||
|
@ -80,21 +112,12 @@ def _extractCover(tmp_file_name, original_file_extension, rarExceutable):
|
||||||
ext = os.path.splitext(name)
|
ext = os.path.splitext(name)
|
||||||
if len(ext) > 1:
|
if len(ext) > 1:
|
||||||
extension = ext[1].lower()
|
extension = ext[1].lower()
|
||||||
if extension == '.jpg' or extension == '.jpeg':
|
if extension in ('.jpg', '.jpeg', '.png', '.webp'):
|
||||||
cover_data = cf.read(name)
|
cover_data = cf.read(name)
|
||||||
break
|
break
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
log.debug('Rarfile failed with error: %s', e)
|
log.debug('Rarfile failed with error: %s', e)
|
||||||
|
return _cover_processing(tmp_file_name, cover_data, extension)
|
||||||
prefix = os.path.dirname(tmp_file_name)
|
|
||||||
if cover_data:
|
|
||||||
tmp_cover_name = prefix + '/cover' + extension
|
|
||||||
image = open(tmp_cover_name, 'wb')
|
|
||||||
image.write(cover_data)
|
|
||||||
image.close()
|
|
||||||
else:
|
|
||||||
tmp_cover_name = None
|
|
||||||
return tmp_cover_name
|
|
||||||
|
|
||||||
|
|
||||||
def get_comic_info(tmp_file_path, original_file_name, original_file_extension, rarExceutable):
|
def get_comic_info(tmp_file_path, original_file_name, original_file_extension, rarExceutable):
|
||||||
|
|
|
@ -53,6 +53,7 @@ class _Settings(_Base):
|
||||||
mail_login = Column(String, default='mail@example.com')
|
mail_login = Column(String, default='mail@example.com')
|
||||||
mail_password = Column(String, default='mypassword')
|
mail_password = Column(String, default='mypassword')
|
||||||
mail_from = Column(String, default='automailer <mail@example.com>')
|
mail_from = Column(String, default='automailer <mail@example.com>')
|
||||||
|
mail_size = Column(Integer, default=25*1024*1024)
|
||||||
|
|
||||||
config_calibre_dir = Column(String)
|
config_calibre_dir = Column(String)
|
||||||
config_port = Column(Integer, default=constants.DEFAULT_PORT)
|
config_port = Column(Integer, default=constants.DEFAULT_PORT)
|
||||||
|
@ -96,7 +97,7 @@ class _Settings(_Base):
|
||||||
config_use_goodreads = Column(Boolean, default=False)
|
config_use_goodreads = Column(Boolean, default=False)
|
||||||
config_goodreads_api_key = Column(String)
|
config_goodreads_api_key = Column(String)
|
||||||
config_goodreads_api_secret = Column(String)
|
config_goodreads_api_secret = Column(String)
|
||||||
|
config_register_email = Column(Boolean, default=False)
|
||||||
config_login_type = Column(Integer, default=0)
|
config_login_type = Column(Integer, default=0)
|
||||||
|
|
||||||
config_kobo_proxy = Column(Boolean, default=False)
|
config_kobo_proxy = Column(Boolean, default=False)
|
||||||
|
@ -116,10 +117,11 @@ class _Settings(_Base):
|
||||||
config_ldap_group_members_field = Column(String, default='memberUid')
|
config_ldap_group_members_field = Column(String, default='memberUid')
|
||||||
config_ldap_group_name = Column(String, default='calibreweb')
|
config_ldap_group_name = Column(String, default='calibreweb')
|
||||||
|
|
||||||
config_ebookconverter = Column(Integer, default=0)
|
config_kepubifypath = Column(String, default=None)
|
||||||
config_converterpath = Column(String)
|
config_converterpath = Column(String, default=None)
|
||||||
config_calibre = Column(String)
|
config_calibre = Column(String)
|
||||||
config_rarfile_location = Column(String)
|
config_rarfile_location = Column(String, default=None)
|
||||||
|
config_upload_formats = Column(String, default=','.join(constants.EXTENSIONS_UPLOAD))
|
||||||
|
|
||||||
config_updatechannel = Column(Integer, default=constants.UPDATE_STABLE)
|
config_updatechannel = Column(Integer, default=constants.UPDATE_STABLE)
|
||||||
|
|
||||||
|
@ -140,6 +142,22 @@ class _ConfigSQL(object):
|
||||||
self.config_calibre_dir = None
|
self.config_calibre_dir = None
|
||||||
self.load()
|
self.load()
|
||||||
|
|
||||||
|
change = False
|
||||||
|
if self.config_converterpath == None:
|
||||||
|
change = True
|
||||||
|
self.config_converterpath = autodetect_calibre_binary()
|
||||||
|
|
||||||
|
if self.config_kepubifypath == None:
|
||||||
|
change = True
|
||||||
|
self.config_kepubifypath = autodetect_kepubify_binary()
|
||||||
|
|
||||||
|
if self.config_rarfile_location == None:
|
||||||
|
change = True
|
||||||
|
self.config_rarfile_location = autodetect_unrar_binary()
|
||||||
|
if change:
|
||||||
|
self.save()
|
||||||
|
|
||||||
|
|
||||||
def _read_from_storage(self):
|
def _read_from_storage(self):
|
||||||
if self._settings is None:
|
if self._settings is None:
|
||||||
log.debug("_ConfigSQL._read_from_storage")
|
log.debug("_ConfigSQL._read_from_storage")
|
||||||
|
@ -264,7 +282,8 @@ class _ConfigSQL(object):
|
||||||
setattr(self, k, v)
|
setattr(self, k, v)
|
||||||
|
|
||||||
if self.config_google_drive_watch_changes_response:
|
if self.config_google_drive_watch_changes_response:
|
||||||
self.config_google_drive_watch_changes_response = json.loads(self.config_google_drive_watch_changes_response)
|
self.config_google_drive_watch_changes_response = \
|
||||||
|
json.loads(self.config_google_drive_watch_changes_response)
|
||||||
|
|
||||||
have_metadata_db = bool(self.config_calibre_dir)
|
have_metadata_db = bool(self.config_calibre_dir)
|
||||||
if have_metadata_db:
|
if have_metadata_db:
|
||||||
|
@ -272,8 +291,13 @@ class _ConfigSQL(object):
|
||||||
db_file = os.path.join(self.config_calibre_dir, 'metadata.db')
|
db_file = os.path.join(self.config_calibre_dir, 'metadata.db')
|
||||||
have_metadata_db = os.path.isfile(db_file)
|
have_metadata_db = os.path.isfile(db_file)
|
||||||
self.db_configured = have_metadata_db
|
self.db_configured = have_metadata_db
|
||||||
|
constants.EXTENSIONS_UPLOAD = [x.lstrip().rstrip() for x in self.config_upload_formats.split(',')]
|
||||||
logger.setup(self.config_logfile, self.config_log_level)
|
logfile = logger.setup(self.config_logfile, self.config_log_level)
|
||||||
|
if logfile != self.config_logfile:
|
||||||
|
log.warning("Log path %s not valid, falling back to default", self.config_logfile)
|
||||||
|
self.config_logfile = logfile
|
||||||
|
self._session.merge(s)
|
||||||
|
self._session.commit()
|
||||||
|
|
||||||
def save(self):
|
def save(self):
|
||||||
'''Apply all configuration values to the underlying storage.'''
|
'''Apply all configuration values to the underlying storage.'''
|
||||||
|
@ -334,17 +358,41 @@ def _migrate_table(session, orm_class):
|
||||||
if changed:
|
if changed:
|
||||||
session.commit()
|
session.commit()
|
||||||
|
|
||||||
|
|
||||||
def autodetect_calibre_binary():
|
def autodetect_calibre_binary():
|
||||||
if sys.platform == "win32":
|
if sys.platform == "win32":
|
||||||
calibre_path = ["C:\\program files\calibre\calibre-convert.exe",
|
calibre_path = ["C:\\program files\calibre\ebook-convert.exe",
|
||||||
"C:\\program files(x86)\calibre\calibre-convert.exe"]
|
"C:\\program files(x86)\calibre\ebook-convert.exe",
|
||||||
|
"C:\\program files(x86)\calibre2\ebook-convert.exe",
|
||||||
|
"C:\\program files\calibre2\ebook-convert.exe"]
|
||||||
else:
|
else:
|
||||||
calibre_path = ["/opt/calibre/ebook-convert"]
|
calibre_path = ["/opt/calibre/ebook-convert"]
|
||||||
for element in calibre_path:
|
for element in calibre_path:
|
||||||
if os.path.isfile(element) and os.access(element, os.X_OK):
|
if os.path.isfile(element) and os.access(element, os.X_OK):
|
||||||
return element
|
return element
|
||||||
return None
|
return ""
|
||||||
|
|
||||||
|
def autodetect_unrar_binary():
|
||||||
|
if sys.platform == "win32":
|
||||||
|
calibre_path = ["C:\\program files\\WinRar\\unRAR.exe",
|
||||||
|
"C:\\program files(x86)\\WinRar\\unRAR.exe"]
|
||||||
|
else:
|
||||||
|
calibre_path = ["/usr/bin/unrar"]
|
||||||
|
for element in calibre_path:
|
||||||
|
if os.path.isfile(element) and os.access(element, os.X_OK):
|
||||||
|
return element
|
||||||
|
return ""
|
||||||
|
|
||||||
|
def autodetect_kepubify_binary():
|
||||||
|
if sys.platform == "win32":
|
||||||
|
calibre_path = ["C:\\program files\\kepubify\\kepubify-windows-64Bit.exe",
|
||||||
|
"C:\\program files(x86)\\kepubify\\kepubify-windows-64Bit.exe"]
|
||||||
|
else:
|
||||||
|
calibre_path = ["/opt/kepubify/kepubify-linux-64bit", "/opt/kepubify/kepubify-linux-32bit"]
|
||||||
|
for element in calibre_path:
|
||||||
|
if os.path.isfile(element) and os.access(element, os.X_OK):
|
||||||
|
return element
|
||||||
|
return ""
|
||||||
|
|
||||||
def _migrate_database(session):
|
def _migrate_database(session):
|
||||||
# make sure the table is created, if it does not exist
|
# make sure the table is created, if it does not exist
|
||||||
|
|
|
@ -81,6 +81,7 @@ SIDEBAR_PUBLISHER = 1 << 12
|
||||||
SIDEBAR_RATING = 1 << 13
|
SIDEBAR_RATING = 1 << 13
|
||||||
SIDEBAR_FORMAT = 1 << 14
|
SIDEBAR_FORMAT = 1 << 14
|
||||||
SIDEBAR_ARCHIVED = 1 << 15
|
SIDEBAR_ARCHIVED = 1 << 15
|
||||||
|
# SIDEBAR_LIST = 1 << 16
|
||||||
|
|
||||||
ADMIN_USER_ROLES = sum(r for r in ALL_ROLES.values()) & ~ROLE_ANONYMOUS
|
ADMIN_USER_ROLES = sum(r for r in ALL_ROLES.values()) & ~ROLE_ANONYMOUS
|
||||||
ADMIN_USER_SIDEBAR = (SIDEBAR_ARCHIVED << 1) - 1
|
ADMIN_USER_SIDEBAR = (SIDEBAR_ARCHIVED << 1) - 1
|
||||||
|
@ -111,11 +112,9 @@ del env_CALIBRE_PORT
|
||||||
|
|
||||||
|
|
||||||
EXTENSIONS_AUDIO = {'mp3', 'mp4', 'ogg', 'opus', 'wav', 'flac'}
|
EXTENSIONS_AUDIO = {'mp3', 'mp4', 'ogg', 'opus', 'wav', 'flac'}
|
||||||
EXTENSIONS_CONVERT = {'pdf', 'epub', 'mobi', 'azw3', 'docx', 'rtf', 'fb2', 'lit', 'lrf', 'txt', 'htmlz', 'rtf', 'odt'}
|
EXTENSIONS_CONVERT = ['pdf', 'epub', 'mobi', 'azw3', 'docx', 'rtf', 'fb2', 'lit', 'lrf', 'txt', 'htmlz', 'rtf', 'odt']
|
||||||
EXTENSIONS_UPLOAD = {'txt', 'pdf', 'epub', 'mobi', 'azw', 'azw3', 'cbr', 'cbz', 'cbt', 'djvu', 'prc', 'doc', 'docx',
|
EXTENSIONS_UPLOAD = {'txt', 'pdf', 'epub', 'kepub', 'mobi', 'azw', 'azw3', 'cbr', 'cbz', 'cbt', 'djvu', 'prc', 'doc', 'docx',
|
||||||
'fb2', 'html', 'rtf', 'lit', 'odt', 'mp3', 'mp4', 'ogg', 'opus', 'wav', 'flac'}
|
'fb2', 'html', 'rtf', 'lit', 'odt', 'mp3', 'mp4', 'ogg', 'opus', 'wav', 'flac'}
|
||||||
# EXTENSIONS_READER = set(['txt', 'pdf', 'epub', 'zip', 'cbz', 'tar', 'cbt'] +
|
|
||||||
# (['rar','cbr'] if feature_support['rar'] else []))
|
|
||||||
|
|
||||||
|
|
||||||
def has_flag(value, bit_flag):
|
def has_flag(value, bit_flag):
|
||||||
|
|
|
@ -19,6 +19,7 @@
|
||||||
from __future__ import division, print_function, unicode_literals
|
from __future__ import division, print_function, unicode_literals
|
||||||
import os
|
import os
|
||||||
import re
|
import re
|
||||||
|
import sys
|
||||||
from flask_babel import gettext as _
|
from flask_babel import gettext as _
|
||||||
|
|
||||||
from . import config, logger
|
from . import config, logger
|
||||||
|
@ -29,8 +30,8 @@ log = logger.create()
|
||||||
|
|
||||||
# _() necessary to make babel aware of string for translation
|
# _() necessary to make babel aware of string for translation
|
||||||
_NOT_CONFIGURED = _('not configured')
|
_NOT_CONFIGURED = _('not configured')
|
||||||
_NOT_INSTALLED = 'not installed'
|
_NOT_INSTALLED = _('not installed')
|
||||||
_EXECUTION_ERROR = 'Execution permissions missing'
|
_EXECUTION_ERROR = _('Execution permissions missing')
|
||||||
|
|
||||||
|
|
||||||
def _get_command_version(path, pattern, argument=None):
|
def _get_command_version(path, pattern, argument=None):
|
||||||
|
@ -48,10 +49,15 @@ def _get_command_version(path, pattern, argument=None):
|
||||||
return _NOT_INSTALLED
|
return _NOT_INSTALLED
|
||||||
|
|
||||||
|
|
||||||
def get_version():
|
def get_calibre_version():
|
||||||
version = None
|
return _get_command_version(config.config_converterpath, r'ebook-convert.*\(calibre', '--version') \
|
||||||
if config.config_ebookconverter == 1:
|
or _NOT_CONFIGURED
|
||||||
version = _get_command_version(config.config_converterpath, r'Amazon kindlegen\(')
|
|
||||||
elif config.config_ebookconverter == 2:
|
|
||||||
version = _get_command_version(config.config_converterpath, r'ebook-convert.*\(calibre', '--version')
|
def get_unrar_version():
|
||||||
return version or _NOT_CONFIGURED
|
return _get_command_version(config.config_rarfile_location, r'UNRAR.*\d') or _NOT_CONFIGURED
|
||||||
|
|
||||||
|
def get_kepubify_version():
|
||||||
|
return _get_command_version(config.config_kepubifypath, r'kepubify\s','--version') or _NOT_CONFIGURED
|
||||||
|
|
||||||
|
|
||||||
|
|
495
cps/db.py
Executable file → Normal file
495
cps/db.py
Executable file → Normal file
|
@ -22,18 +22,34 @@ import sys
|
||||||
import os
|
import os
|
||||||
import re
|
import re
|
||||||
import ast
|
import ast
|
||||||
|
import json
|
||||||
|
from datetime import datetime
|
||||||
|
import threading
|
||||||
|
|
||||||
from sqlalchemy import create_engine
|
from sqlalchemy import create_engine
|
||||||
from sqlalchemy import Table, Column, ForeignKey
|
from sqlalchemy import Table, Column, ForeignKey, CheckConstraint
|
||||||
from sqlalchemy import String, Integer, Boolean, TIMESTAMP, Float, DateTime
|
from sqlalchemy import String, Integer, Boolean, TIMESTAMP, Float
|
||||||
from sqlalchemy.orm import relationship, sessionmaker, scoped_session
|
from sqlalchemy.orm import relationship, sessionmaker, scoped_session
|
||||||
from sqlalchemy.ext.declarative import declarative_base
|
from sqlalchemy.ext.declarative import declarative_base
|
||||||
|
from sqlalchemy.exc import OperationalError
|
||||||
|
from flask_login import current_user
|
||||||
|
from sqlalchemy.sql.expression import and_, true, false, text, func, or_
|
||||||
|
from babel import Locale as LC
|
||||||
|
from babel.core import UnknownLocaleError
|
||||||
|
from flask_babel import gettext as _
|
||||||
|
|
||||||
|
from . import logger, ub, isoLanguages
|
||||||
|
from .pagination import Pagination
|
||||||
|
|
||||||
|
try:
|
||||||
|
import unidecode
|
||||||
|
use_unidecode = True
|
||||||
|
except ImportError:
|
||||||
|
use_unidecode = False
|
||||||
|
|
||||||
|
|
||||||
session = None
|
|
||||||
cc_exceptions = ['datetime', 'comments', 'composite', 'series']
|
cc_exceptions = ['datetime', 'comments', 'composite', 'series']
|
||||||
cc_classes = {}
|
cc_classes = {}
|
||||||
engine = None
|
|
||||||
|
|
||||||
Base = declarative_base()
|
Base = declarative_base()
|
||||||
|
|
||||||
|
@ -72,9 +88,9 @@ class Identifiers(Base):
|
||||||
__tablename__ = 'identifiers'
|
__tablename__ = 'identifiers'
|
||||||
|
|
||||||
id = Column(Integer, primary_key=True)
|
id = Column(Integer, primary_key=True)
|
||||||
type = Column(String)
|
type = Column(String(collation='NOCASE'), nullable=False, default="isbn")
|
||||||
val = Column(String)
|
val = Column(String(collation='NOCASE'), nullable=False)
|
||||||
book = Column(Integer, ForeignKey('books.id'))
|
book = Column(Integer, ForeignKey('books.id'), nullable=False)
|
||||||
|
|
||||||
def __init__(self, val, id_type, book):
|
def __init__(self, val, id_type, book):
|
||||||
self.val = val
|
self.val = val
|
||||||
|
@ -126,8 +142,8 @@ class Comments(Base):
|
||||||
__tablename__ = 'comments'
|
__tablename__ = 'comments'
|
||||||
|
|
||||||
id = Column(Integer, primary_key=True)
|
id = Column(Integer, primary_key=True)
|
||||||
text = Column(String)
|
text = Column(String(collation='NOCASE'), nullable=False)
|
||||||
book = Column(Integer, ForeignKey('books.id'))
|
book = Column(Integer, ForeignKey('books.id'), nullable=False)
|
||||||
|
|
||||||
def __init__(self, text, book):
|
def __init__(self, text, book):
|
||||||
self.text = text
|
self.text = text
|
||||||
|
@ -141,7 +157,7 @@ class Tags(Base):
|
||||||
__tablename__ = 'tags'
|
__tablename__ = 'tags'
|
||||||
|
|
||||||
id = Column(Integer, primary_key=True, autoincrement=True)
|
id = Column(Integer, primary_key=True, autoincrement=True)
|
||||||
name = Column(String)
|
name = Column(String(collation='NOCASE'), unique=True, nullable=False)
|
||||||
|
|
||||||
def __init__(self, name):
|
def __init__(self, name):
|
||||||
self.name = name
|
self.name = name
|
||||||
|
@ -154,9 +170,9 @@ class Authors(Base):
|
||||||
__tablename__ = 'authors'
|
__tablename__ = 'authors'
|
||||||
|
|
||||||
id = Column(Integer, primary_key=True)
|
id = Column(Integer, primary_key=True)
|
||||||
name = Column(String)
|
name = Column(String(collation='NOCASE'), unique=True, nullable=False)
|
||||||
sort = Column(String)
|
sort = Column(String(collation='NOCASE'))
|
||||||
link = Column(String)
|
link = Column(String, nullable=False, default="")
|
||||||
|
|
||||||
def __init__(self, name, sort, link):
|
def __init__(self, name, sort, link):
|
||||||
self.name = name
|
self.name = name
|
||||||
|
@ -171,8 +187,8 @@ class Series(Base):
|
||||||
__tablename__ = 'series'
|
__tablename__ = 'series'
|
||||||
|
|
||||||
id = Column(Integer, primary_key=True)
|
id = Column(Integer, primary_key=True)
|
||||||
name = Column(String)
|
name = Column(String(collation='NOCASE'), unique=True, nullable=False)
|
||||||
sort = Column(String)
|
sort = Column(String(collation='NOCASE'))
|
||||||
|
|
||||||
def __init__(self, name, sort):
|
def __init__(self, name, sort):
|
||||||
self.name = name
|
self.name = name
|
||||||
|
@ -186,7 +202,7 @@ class Ratings(Base):
|
||||||
__tablename__ = 'ratings'
|
__tablename__ = 'ratings'
|
||||||
|
|
||||||
id = Column(Integer, primary_key=True)
|
id = Column(Integer, primary_key=True)
|
||||||
rating = Column(Integer)
|
rating = Column(Integer, CheckConstraint('rating>-1 AND rating<11'), unique=True)
|
||||||
|
|
||||||
def __init__(self, rating):
|
def __init__(self, rating):
|
||||||
self.rating = rating
|
self.rating = rating
|
||||||
|
@ -199,7 +215,7 @@ class Languages(Base):
|
||||||
__tablename__ = 'languages'
|
__tablename__ = 'languages'
|
||||||
|
|
||||||
id = Column(Integer, primary_key=True)
|
id = Column(Integer, primary_key=True)
|
||||||
lang_code = Column(String)
|
lang_code = Column(String(collation='NOCASE'), nullable=False, unique=True)
|
||||||
|
|
||||||
def __init__(self, lang_code):
|
def __init__(self, lang_code):
|
||||||
self.lang_code = lang_code
|
self.lang_code = lang_code
|
||||||
|
@ -212,8 +228,8 @@ class Publishers(Base):
|
||||||
__tablename__ = 'publishers'
|
__tablename__ = 'publishers'
|
||||||
|
|
||||||
id = Column(Integer, primary_key=True)
|
id = Column(Integer, primary_key=True)
|
||||||
name = Column(String)
|
name = Column(String(collation='NOCASE'), nullable=False, unique=True)
|
||||||
sort = Column(String)
|
sort = Column(String(collation='NOCASE'))
|
||||||
|
|
||||||
def __init__(self, name, sort):
|
def __init__(self, name, sort):
|
||||||
self.name = name
|
self.name = name
|
||||||
|
@ -225,12 +241,13 @@ class Publishers(Base):
|
||||||
|
|
||||||
class Data(Base):
|
class Data(Base):
|
||||||
__tablename__ = 'data'
|
__tablename__ = 'data'
|
||||||
|
__table_args__ = {'schema':'calibre'}
|
||||||
|
|
||||||
id = Column(Integer, primary_key=True)
|
id = Column(Integer, primary_key=True)
|
||||||
book = Column(Integer, ForeignKey('books.id'))
|
book = Column(Integer, ForeignKey('books.id'), nullable=False)
|
||||||
format = Column(String)
|
format = Column(String(collation='NOCASE'), nullable=False)
|
||||||
uncompressed_size = Column(Integer)
|
uncompressed_size = Column(Integer, nullable=False)
|
||||||
name = Column(String)
|
name = Column(String, nullable=False)
|
||||||
|
|
||||||
def __init__(self, book, book_format, uncompressed_size, name):
|
def __init__(self, book, book_format, uncompressed_size, name):
|
||||||
self.book = book
|
self.book = book
|
||||||
|
@ -247,17 +264,20 @@ class Books(Base):
|
||||||
|
|
||||||
DEFAULT_PUBDATE = "0101-01-01 00:00:00+00:00"
|
DEFAULT_PUBDATE = "0101-01-01 00:00:00+00:00"
|
||||||
|
|
||||||
id = Column(Integer, primary_key=True)
|
id = Column(Integer, primary_key=True, autoincrement=True)
|
||||||
title = Column(String)
|
title = Column(String(collation='NOCASE'), nullable=False, default='Unknown')
|
||||||
sort = Column(String)
|
sort = Column(String(collation='NOCASE'))
|
||||||
author_sort = Column(String)
|
author_sort = Column(String(collation='NOCASE'))
|
||||||
timestamp = Column(TIMESTAMP)
|
timestamp = Column(TIMESTAMP, default=datetime.utcnow)
|
||||||
pubdate = Column(String)
|
pubdate = Column(String) # , default=datetime.utcnow)
|
||||||
series_index = Column(String)
|
series_index = Column(String, nullable=False, default="1.0")
|
||||||
last_modified = Column(TIMESTAMP)
|
last_modified = Column(TIMESTAMP, default=datetime.utcnow)
|
||||||
path = Column(String)
|
path = Column(String, default="", nullable=False)
|
||||||
has_cover = Column(Integer)
|
has_cover = Column(Integer, default=0)
|
||||||
uuid = Column(String)
|
uuid = Column(String)
|
||||||
|
isbn = Column(String(collation='NOCASE'), default="")
|
||||||
|
# Iccn = Column(String(collation='NOCASE'), default="")
|
||||||
|
flags = Column(Integer, nullable=False, default=1)
|
||||||
|
|
||||||
authors = relationship('Authors', secondary=books_authors_link, backref='books')
|
authors = relationship('Authors', secondary=books_authors_link, backref='books')
|
||||||
tags = relationship('Tags', secondary=books_tags_link, backref='books',order_by="Tags.name")
|
tags = relationship('Tags', secondary=books_tags_link, backref='books',order_by="Tags.name")
|
||||||
|
@ -310,131 +330,324 @@ class Custom_Columns(Base):
|
||||||
return display_dict
|
return display_dict
|
||||||
|
|
||||||
|
|
||||||
def update_title_sort(config, conn=None):
|
class CalibreDB(threading.Thread):
|
||||||
# user defined sort function for calibre databases (Series, etc.)
|
|
||||||
def _title_sort(title):
|
|
||||||
# calibre sort stuff
|
|
||||||
title_pat = re.compile(config.config_title_regex, re.IGNORECASE)
|
|
||||||
match = title_pat.search(title)
|
|
||||||
if match:
|
|
||||||
prep = match.group(1)
|
|
||||||
title = title[len(prep):] + ', ' + prep
|
|
||||||
return title.strip()
|
|
||||||
|
|
||||||
conn = conn or session.connection().connection.connection
|
def __init__(self):
|
||||||
conn.create_function("title_sort", 1, _title_sort)
|
threading.Thread.__init__(self)
|
||||||
|
self.engine = None
|
||||||
|
self.session = None
|
||||||
|
self.queue = None
|
||||||
|
self.log = None
|
||||||
|
self.config = None
|
||||||
|
|
||||||
|
def add_queue(self,queue):
|
||||||
|
self.queue = queue
|
||||||
|
self.log = logger.create()
|
||||||
|
|
||||||
|
def run(self):
|
||||||
|
while True:
|
||||||
|
i = self.queue.get()
|
||||||
|
if i == 'dummy':
|
||||||
|
self.queue.task_done()
|
||||||
|
break
|
||||||
|
if i['task'] == 'add_format':
|
||||||
|
cur_book = self.session.query(Books).filter(Books.id == i['id']).first()
|
||||||
|
cur_book.data.append(i['format'])
|
||||||
|
try:
|
||||||
|
# db.session.merge(cur_book)
|
||||||
|
self.session.commit()
|
||||||
|
except OperationalError as e:
|
||||||
|
self.session.rollback()
|
||||||
|
self.log.error("Database error: %s", e)
|
||||||
|
# self._handleError(_(u"Database error: %(error)s.", error=e))
|
||||||
|
# return
|
||||||
|
self.queue.task_done()
|
||||||
|
|
||||||
|
|
||||||
def setup_db(config):
|
def stop(self):
|
||||||
dispose()
|
self.queue.put('dummy')
|
||||||
global engine
|
|
||||||
|
|
||||||
if not config.config_calibre_dir:
|
def setup_db(self, config, app_db_path):
|
||||||
config.invalidate()
|
self.config = config
|
||||||
return False
|
self.dispose()
|
||||||
|
# global engine
|
||||||
|
|
||||||
dbpath = os.path.join(config.config_calibre_dir, "metadata.db")
|
if not config.config_calibre_dir:
|
||||||
if not os.path.exists(dbpath):
|
config.invalidate()
|
||||||
config.invalidate()
|
return False
|
||||||
return False
|
|
||||||
|
|
||||||
try:
|
dbpath = os.path.join(config.config_calibre_dir, "metadata.db")
|
||||||
engine = create_engine('sqlite:///{0}'.format(dbpath),
|
if not os.path.exists(dbpath):
|
||||||
echo=False,
|
config.invalidate()
|
||||||
isolation_level="SERIALIZABLE",
|
return False
|
||||||
connect_args={'check_same_thread': False})
|
|
||||||
conn = engine.connect()
|
|
||||||
# conn.text_factory = lambda b: b.decode(errors = 'ignore') possible fix for #1302
|
|
||||||
except Exception as e:
|
|
||||||
config.invalidate(e)
|
|
||||||
return False
|
|
||||||
|
|
||||||
config.db_configured = True
|
try:
|
||||||
update_title_sort(config, conn.connection)
|
#engine = create_engine('sqlite:///{0}'.format(dbpath),
|
||||||
|
self.engine = create_engine('sqlite://',
|
||||||
|
echo=False,
|
||||||
|
isolation_level="SERIALIZABLE",
|
||||||
|
connect_args={'check_same_thread': False})
|
||||||
|
self.engine.execute("attach database '{}' as calibre;".format(dbpath))
|
||||||
|
self.engine.execute("attach database '{}' as app_settings;".format(app_db_path))
|
||||||
|
|
||||||
if not cc_classes:
|
conn = self.engine.connect()
|
||||||
cc = conn.execute("SELECT id, datatype FROM custom_columns")
|
# conn.text_factory = lambda b: b.decode(errors = 'ignore') possible fix for #1302
|
||||||
|
except Exception as e:
|
||||||
|
config.invalidate(e)
|
||||||
|
return False
|
||||||
|
|
||||||
cc_ids = []
|
config.db_configured = True
|
||||||
books_custom_column_links = {}
|
self.update_title_sort(config, conn.connection)
|
||||||
for row in cc:
|
|
||||||
if row.datatype not in cc_exceptions:
|
if not cc_classes:
|
||||||
books_custom_column_links[row.id] = Table('books_custom_column_' + str(row.id) + '_link', Base.metadata,
|
cc = conn.execute("SELECT id, datatype FROM custom_columns")
|
||||||
Column('book', Integer, ForeignKey('books.id'),
|
|
||||||
primary_key=True),
|
cc_ids = []
|
||||||
Column('value', Integer,
|
books_custom_column_links = {}
|
||||||
ForeignKey('custom_column_' + str(row.id) + '.id'),
|
for row in cc:
|
||||||
primary_key=True)
|
if row.datatype not in cc_exceptions:
|
||||||
)
|
books_custom_column_links[row.id] = Table('books_custom_column_' + str(row.id) + '_link', Base.metadata,
|
||||||
cc_ids.append([row.id, row.datatype])
|
Column('book', Integer, ForeignKey('books.id'),
|
||||||
if row.datatype == 'bool':
|
primary_key=True),
|
||||||
ccdict = {'__tablename__': 'custom_column_' + str(row.id),
|
Column('value', Integer,
|
||||||
'id': Column(Integer, primary_key=True),
|
ForeignKey('custom_column_' + str(row.id) + '.id'),
|
||||||
'book': Column(Integer, ForeignKey('books.id')),
|
primary_key=True)
|
||||||
'value': Column(Boolean)}
|
)
|
||||||
elif row.datatype == 'int':
|
cc_ids.append([row.id, row.datatype])
|
||||||
ccdict = {'__tablename__': 'custom_column_' + str(row.id),
|
if row.datatype == 'bool':
|
||||||
'id': Column(Integer, primary_key=True),
|
ccdict = {'__tablename__': 'custom_column_' + str(row.id),
|
||||||
'book': Column(Integer, ForeignKey('books.id')),
|
'id': Column(Integer, primary_key=True),
|
||||||
'value': Column(Integer)}
|
'book': Column(Integer, ForeignKey('books.id')),
|
||||||
elif row.datatype == 'float':
|
'value': Column(Boolean)}
|
||||||
ccdict = {'__tablename__': 'custom_column_' + str(row.id),
|
elif row.datatype == 'int':
|
||||||
'id': Column(Integer, primary_key=True),
|
ccdict = {'__tablename__': 'custom_column_' + str(row.id),
|
||||||
'book': Column(Integer, ForeignKey('books.id')),
|
'id': Column(Integer, primary_key=True),
|
||||||
'value': Column(Float)}
|
'book': Column(Integer, ForeignKey('books.id')),
|
||||||
|
'value': Column(Integer)}
|
||||||
|
elif row.datatype == 'float':
|
||||||
|
ccdict = {'__tablename__': 'custom_column_' + str(row.id),
|
||||||
|
'id': Column(Integer, primary_key=True),
|
||||||
|
'book': Column(Integer, ForeignKey('books.id')),
|
||||||
|
'value': Column(Float)}
|
||||||
|
else:
|
||||||
|
ccdict = {'__tablename__': 'custom_column_' + str(row.id),
|
||||||
|
'id': Column(Integer, primary_key=True),
|
||||||
|
'value': Column(String)}
|
||||||
|
cc_classes[row.id] = type(str('Custom_Column_' + str(row.id)), (Base,), ccdict)
|
||||||
|
|
||||||
|
for cc_id in cc_ids:
|
||||||
|
if (cc_id[1] == 'bool') or (cc_id[1] == 'int') or (cc_id[1] == 'float'):
|
||||||
|
setattr(Books,
|
||||||
|
'custom_column_' + str(cc_id[0]),
|
||||||
|
relationship(cc_classes[cc_id[0]],
|
||||||
|
primaryjoin=(
|
||||||
|
Books.id == cc_classes[cc_id[0]].book),
|
||||||
|
backref='books'))
|
||||||
else:
|
else:
|
||||||
ccdict = {'__tablename__': 'custom_column_' + str(row.id),
|
setattr(Books,
|
||||||
'id': Column(Integer, primary_key=True),
|
'custom_column_' + str(cc_id[0]),
|
||||||
'value': Column(String)}
|
relationship(cc_classes[cc_id[0]],
|
||||||
cc_classes[row.id] = type(str('Custom_Column_' + str(row.id)), (Base,), ccdict)
|
secondary=books_custom_column_links[cc_id[0]],
|
||||||
|
backref='books'))
|
||||||
|
|
||||||
for cc_id in cc_ids:
|
Session = scoped_session(sessionmaker(autocommit=False,
|
||||||
if (cc_id[1] == 'bool') or (cc_id[1] == 'int') or (cc_id[1] == 'float'):
|
autoflush=False,
|
||||||
setattr(Books, 'custom_column_' + str(cc_id[0]), relationship(cc_classes[cc_id[0]],
|
bind=self.engine))
|
||||||
primaryjoin=(
|
self.session = Session()
|
||||||
Books.id == cc_classes[cc_id[0]].book),
|
return True
|
||||||
backref='books'))
|
|
||||||
else:
|
|
||||||
setattr(Books, 'custom_column_' + str(cc_id[0]), relationship(cc_classes[cc_id[0]],
|
|
||||||
secondary=books_custom_column_links[cc_id[0]],
|
|
||||||
backref='books'))
|
|
||||||
|
|
||||||
|
def get_book(self, book_id):
|
||||||
|
return self.session.query(Books).filter(Books.id == book_id).first()
|
||||||
|
|
||||||
global session
|
def get_filtered_book(self, book_id, allow_show_archived=False):
|
||||||
Session = scoped_session(sessionmaker(autocommit=False,
|
return self.session.query(Books).filter(Books.id == book_id).\
|
||||||
autoflush=False,
|
filter(self.common_filters(allow_show_archived)).first()
|
||||||
bind=engine))
|
|
||||||
session = Session()
|
|
||||||
return True
|
|
||||||
|
|
||||||
|
def get_book_by_uuid(self, book_uuid):
|
||||||
|
return self.session.query(Books).filter(Books.uuid == book_uuid).first()
|
||||||
|
|
||||||
def dispose():
|
def get_book_format(self, book_id, format):
|
||||||
global session
|
return self.session.query(Data).filter(Data.book == book_id).filter(Data.format == format).first()
|
||||||
|
|
||||||
old_session = session
|
# Language and content filters for displaying in the UI
|
||||||
session = None
|
def common_filters(self, allow_show_archived=False):
|
||||||
if old_session:
|
if not allow_show_archived:
|
||||||
try: old_session.close()
|
archived_books = (
|
||||||
except: pass
|
ub.session.query(ub.ArchivedBook)
|
||||||
if old_session.bind:
|
.filter(ub.ArchivedBook.user_id == int(current_user.id))
|
||||||
try: old_session.bind.dispose()
|
.filter(ub.ArchivedBook.is_archived == True)
|
||||||
except Exception: pass
|
.all()
|
||||||
|
)
|
||||||
|
archived_book_ids = [archived_book.book_id for archived_book in archived_books]
|
||||||
|
archived_filter = Books.id.notin_(archived_book_ids)
|
||||||
|
else:
|
||||||
|
archived_filter = true()
|
||||||
|
|
||||||
for attr in list(Books.__dict__.keys()):
|
if current_user.filter_language() != "all":
|
||||||
if attr.startswith("custom_column_"):
|
lang_filter = Books.languages.any(Languages.lang_code == current_user.filter_language())
|
||||||
setattr(Books, attr, None)
|
else:
|
||||||
|
lang_filter = true()
|
||||||
|
negtags_list = current_user.list_denied_tags()
|
||||||
|
postags_list = current_user.list_allowed_tags()
|
||||||
|
neg_content_tags_filter = false() if negtags_list == [''] else Books.tags.any(Tags.name.in_(negtags_list))
|
||||||
|
pos_content_tags_filter = true() if postags_list == [''] else Books.tags.any(Tags.name.in_(postags_list))
|
||||||
|
if self.config.config_restricted_column:
|
||||||
|
pos_cc_list = current_user.allowed_column_value.split(',')
|
||||||
|
pos_content_cc_filter = true() if pos_cc_list == [''] else \
|
||||||
|
getattr(Books, 'custom_column_' + str(self.config.config_restricted_column)). \
|
||||||
|
any(cc_classes[self.config.config_restricted_column].value.in_(pos_cc_list))
|
||||||
|
neg_cc_list = current_user.denied_column_value.split(',')
|
||||||
|
neg_content_cc_filter = false() if neg_cc_list == [''] else \
|
||||||
|
getattr(Books, 'custom_column_' + str(self.config.config_restricted_column)). \
|
||||||
|
any(cc_classes[self.config.config_restricted_column].value.in_(neg_cc_list))
|
||||||
|
else:
|
||||||
|
pos_content_cc_filter = true()
|
||||||
|
neg_content_cc_filter = false()
|
||||||
|
return and_(lang_filter, pos_content_tags_filter, ~neg_content_tags_filter,
|
||||||
|
pos_content_cc_filter, ~neg_content_cc_filter, archived_filter)
|
||||||
|
|
||||||
for db_class in cc_classes.values():
|
# Fill indexpage with all requested data from database
|
||||||
Base.metadata.remove(db_class.__table__)
|
def fill_indexpage(self, page, database, db_filter, order, *join):
|
||||||
cc_classes.clear()
|
return self.fill_indexpage_with_archived_books(page, database, db_filter, order, False, *join)
|
||||||
|
|
||||||
for table in reversed(Base.metadata.sorted_tables):
|
def fill_indexpage_with_archived_books(self, page, database, db_filter, order, allow_show_archived, *join):
|
||||||
name = table.key
|
if current_user.show_detail_random():
|
||||||
if name.startswith("custom_column_") or name.startswith("books_custom_column_"):
|
randm = self.session.query(Books) \
|
||||||
if table is not None:
|
.filter(self.common_filters(allow_show_archived)) \
|
||||||
Base.metadata.remove(table)
|
.order_by(func.random()) \
|
||||||
|
.limit(self.config.config_random_books)
|
||||||
|
else:
|
||||||
|
randm = false()
|
||||||
|
off = int(int(self.config.config_books_per_page) * (page - 1))
|
||||||
|
query = self.session.query(database) \
|
||||||
|
.join(*join, isouter=True) \
|
||||||
|
.filter(db_filter) \
|
||||||
|
.filter(self.common_filters(allow_show_archived))
|
||||||
|
pagination = Pagination(page, self.config.config_books_per_page,
|
||||||
|
len(query.all()))
|
||||||
|
entries = query.order_by(*order).offset(off).limit(self.config.config_books_per_page).all()
|
||||||
|
for book in entries:
|
||||||
|
book = self.order_authors(book)
|
||||||
|
return entries, randm, pagination
|
||||||
|
|
||||||
def reconnect_db(config):
|
# Orders all Authors in the list according to authors sort
|
||||||
session.close()
|
def order_authors(self, entry):
|
||||||
engine.dispose()
|
sort_authors = entry.author_sort.split('&')
|
||||||
setup_db(config)
|
authors_ordered = list()
|
||||||
|
error = False
|
||||||
|
for auth in sort_authors:
|
||||||
|
# ToDo: How to handle not found authorname
|
||||||
|
result = self.session.query(Authors).filter(Authors.sort == auth.lstrip().strip()).first()
|
||||||
|
if not result:
|
||||||
|
error = True
|
||||||
|
break
|
||||||
|
authors_ordered.append(result)
|
||||||
|
if not error:
|
||||||
|
entry.authors = authors_ordered
|
||||||
|
return entry
|
||||||
|
|
||||||
|
def get_typeahead(self, database, query, replace=('', ''), tag_filter=true()):
|
||||||
|
query = query or ''
|
||||||
|
self.session.connection().connection.connection.create_function("lower", 1, lcase)
|
||||||
|
entries = self.session.query(database).filter(tag_filter). \
|
||||||
|
filter(func.lower(database.name).ilike("%" + query + "%")).all()
|
||||||
|
json_dumps = json.dumps([dict(name=r.name.replace(*replace)) for r in entries])
|
||||||
|
return json_dumps
|
||||||
|
|
||||||
|
def check_exists_book(self, authr, title):
|
||||||
|
self.session.connection().connection.connection.create_function("lower", 1, lcase)
|
||||||
|
q = list()
|
||||||
|
authorterms = re.split(r'\s*&\s*', authr)
|
||||||
|
for authorterm in authorterms:
|
||||||
|
q.append(Books.authors.any(func.lower(Authors.name).ilike("%" + authorterm + "%")))
|
||||||
|
|
||||||
|
return self.session.query(Books)\
|
||||||
|
.filter(and_(Books.authors.any(and_(*q)), func.lower(Books.title).ilike("%" + title + "%"))).first()
|
||||||
|
|
||||||
|
# read search results from calibre-database and return it (function is used for feed and simple search
|
||||||
|
def get_search_results(self, term):
|
||||||
|
term.strip().lower()
|
||||||
|
self.session.connection().connection.connection.create_function("lower", 1, lcase)
|
||||||
|
q = list()
|
||||||
|
authorterms = re.split("[, ]+", term)
|
||||||
|
for authorterm in authorterms:
|
||||||
|
q.append(Books.authors.any(func.lower(Authors.name).ilike("%" + authorterm + "%")))
|
||||||
|
|
||||||
|
return self.session.query(Books).filter(self.common_filters()).filter(
|
||||||
|
or_(Books.tags.any(func.lower(Tags.name).ilike("%" + term + "%")),
|
||||||
|
Books.series.any(func.lower(Series.name).ilike("%" + term + "%")),
|
||||||
|
Books.authors.any(and_(*q)),
|
||||||
|
Books.publishers.any(func.lower(Publishers.name).ilike("%" + term + "%")),
|
||||||
|
func.lower(Books.title).ilike("%" + term + "%")
|
||||||
|
)).order_by(Books.sort).all()
|
||||||
|
|
||||||
|
# Creates for all stored languages a translated speaking name in the array for the UI
|
||||||
|
def speaking_language(self, languages=None):
|
||||||
|
from . import get_locale
|
||||||
|
|
||||||
|
if not languages:
|
||||||
|
languages = self.session.query(Languages) \
|
||||||
|
.join(books_languages_link) \
|
||||||
|
.join(Books) \
|
||||||
|
.filter(self.common_filters()) \
|
||||||
|
.group_by(text('books_languages_link.lang_code')).all()
|
||||||
|
for lang in languages:
|
||||||
|
try:
|
||||||
|
cur_l = LC.parse(lang.lang_code)
|
||||||
|
lang.name = cur_l.get_language_name(get_locale())
|
||||||
|
except UnknownLocaleError:
|
||||||
|
lang.name = _(isoLanguages.get(part3=lang.lang_code).name)
|
||||||
|
return languages
|
||||||
|
|
||||||
|
def update_title_sort(self, config, conn=None):
|
||||||
|
# user defined sort function for calibre databases (Series, etc.)
|
||||||
|
def _title_sort(title):
|
||||||
|
# calibre sort stuff
|
||||||
|
title_pat = re.compile(config.config_title_regex, re.IGNORECASE)
|
||||||
|
match = title_pat.search(title)
|
||||||
|
if match:
|
||||||
|
prep = match.group(1)
|
||||||
|
title = title[len(prep):] + ', ' + prep
|
||||||
|
return title.strip()
|
||||||
|
|
||||||
|
conn = conn or self.session.connection().connection.connection
|
||||||
|
conn.create_function("title_sort", 1, _title_sort)
|
||||||
|
|
||||||
|
def dispose(self):
|
||||||
|
# global session
|
||||||
|
|
||||||
|
old_session = self.session
|
||||||
|
self.session = None
|
||||||
|
if old_session:
|
||||||
|
try: old_session.close()
|
||||||
|
except: pass
|
||||||
|
if old_session.bind:
|
||||||
|
try: old_session.bind.dispose()
|
||||||
|
except Exception: pass
|
||||||
|
|
||||||
|
for attr in list(Books.__dict__.keys()):
|
||||||
|
if attr.startswith("custom_column_"):
|
||||||
|
setattr(Books, attr, None)
|
||||||
|
|
||||||
|
for db_class in cc_classes.values():
|
||||||
|
Base.metadata.remove(db_class.__table__)
|
||||||
|
cc_classes.clear()
|
||||||
|
|
||||||
|
for table in reversed(Base.metadata.sorted_tables):
|
||||||
|
name = table.key
|
||||||
|
if name.startswith("custom_column_") or name.startswith("books_custom_column_"):
|
||||||
|
if table is not None:
|
||||||
|
Base.metadata.remove(table)
|
||||||
|
|
||||||
|
def reconnect_db(self, config, app_db_path):
|
||||||
|
self.session.close()
|
||||||
|
self.engine.dispose()
|
||||||
|
self.setup_db(config, app_db_path)
|
||||||
|
|
||||||
|
def lcase(s):
|
||||||
|
try:
|
||||||
|
return unidecode.unidecode(s.lower())
|
||||||
|
except Exception as e:
|
||||||
|
log = logger.create()
|
||||||
|
log.exception(e)
|
||||||
|
return s.lower()
|
||||||
|
|
523
cps/editbooks.py
523
cps/editbooks.py
|
@ -24,16 +24,17 @@ from __future__ import division, print_function, unicode_literals
|
||||||
import os
|
import os
|
||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
import json
|
import json
|
||||||
from shutil import move, copyfile
|
from shutil import copyfile
|
||||||
from uuid import uuid4
|
from uuid import uuid4
|
||||||
|
|
||||||
from flask import Blueprint, request, flash, redirect, url_for, abort, Markup, Response
|
from flask import Blueprint, request, flash, redirect, url_for, abort, Markup, Response
|
||||||
from flask_babel import gettext as _
|
from flask_babel import gettext as _
|
||||||
from flask_login import current_user, login_required
|
from flask_login import current_user, login_required
|
||||||
|
from sqlalchemy.exc import OperationalError
|
||||||
|
|
||||||
from . import constants, logger, isoLanguages, gdriveutils, uploader, helper
|
from . import constants, logger, isoLanguages, gdriveutils, uploader, helper
|
||||||
from . import config, get_locale, db, ub, worker
|
from . import config, get_locale, ub, worker, db
|
||||||
from .helper import order_authors, common_filters
|
from . import calibre_db
|
||||||
from .web import login_required_if_no_ano, render_title_template, edit_required, upload_required
|
from .web import login_required_if_no_ano, render_title_template, edit_required, upload_required
|
||||||
|
|
||||||
|
|
||||||
|
@ -174,13 +175,15 @@ def modify_identifiers(input_identifiers, db_identifiers, db_session):
|
||||||
@login_required
|
@login_required
|
||||||
def delete_book(book_id, book_format):
|
def delete_book(book_id, book_format):
|
||||||
if current_user.role_delete_books():
|
if current_user.role_delete_books():
|
||||||
book = db.session.query(db.Books).filter(db.Books.id == book_id).first()
|
book = calibre_db.get_book(book_id)
|
||||||
if book:
|
if book:
|
||||||
try:
|
try:
|
||||||
result, error = helper.delete_book(book, config.config_calibre_dir, book_format=book_format.upper())
|
result, error = helper.delete_book(book, config.config_calibre_dir, book_format=book_format.upper())
|
||||||
if not result:
|
if not result:
|
||||||
flash(error, category="error")
|
flash(error, category="error")
|
||||||
return redirect(url_for('editbook.edit_book', book_id=book_id))
|
return redirect(url_for('editbook.edit_book', book_id=book_id))
|
||||||
|
if error:
|
||||||
|
flash(error, category="warning")
|
||||||
if not book_format:
|
if not book_format:
|
||||||
# delete book from Shelfs, Downloads, Read list
|
# delete book from Shelfs, Downloads, Read list
|
||||||
ub.session.query(ub.BookShelf).filter(ub.BookShelf.book_id == book_id).delete()
|
ub.session.query(ub.BookShelf).filter(ub.BookShelf.book_id == book_id).delete()
|
||||||
|
@ -190,13 +193,13 @@ def delete_book(book_id, book_format):
|
||||||
|
|
||||||
# check if only this book links to:
|
# check if only this book links to:
|
||||||
# author, language, series, tags, custom columns
|
# author, language, series, tags, custom columns
|
||||||
modify_database_object([u''], book.authors, db.Authors, db.session, 'author')
|
modify_database_object([u''], book.authors, db.Authors, calibre_db.session, 'author')
|
||||||
modify_database_object([u''], book.tags, db.Tags, db.session, 'tags')
|
modify_database_object([u''], book.tags, db.Tags, calibre_db.session, 'tags')
|
||||||
modify_database_object([u''], book.series, db.Series, db.session, 'series')
|
modify_database_object([u''], book.series, db.Series, calibre_db.session, 'series')
|
||||||
modify_database_object([u''], book.languages, db.Languages, db.session, 'languages')
|
modify_database_object([u''], book.languages, db.Languages, calibre_db.session, 'languages')
|
||||||
modify_database_object([u''], book.publishers, db.Publishers, db.session, 'publishers')
|
modify_database_object([u''], book.publishers, db.Publishers, calibre_db.session, 'publishers')
|
||||||
|
|
||||||
cc = db.session.query(db.Custom_Columns).\
|
cc = calibre_db.session.query(db.Custom_Columns).\
|
||||||
filter(db.Custom_Columns.datatype.notin_(db.cc_exceptions)).all()
|
filter(db.Custom_Columns.datatype.notin_(db.cc_exceptions)).all()
|
||||||
for c in cc:
|
for c in cc:
|
||||||
cc_string = "custom_column_" + str(c.id)
|
cc_string = "custom_column_" + str(c.id)
|
||||||
|
@ -206,32 +209,32 @@ def delete_book(book_id, book_format):
|
||||||
del_cc = getattr(book, cc_string)[0]
|
del_cc = getattr(book, cc_string)[0]
|
||||||
getattr(book, cc_string).remove(del_cc)
|
getattr(book, cc_string).remove(del_cc)
|
||||||
log.debug('remove ' + str(c.id))
|
log.debug('remove ' + str(c.id))
|
||||||
db.session.delete(del_cc)
|
calibre_db.session.delete(del_cc)
|
||||||
db.session.commit()
|
calibre_db.session.commit()
|
||||||
elif c.datatype == 'rating':
|
elif c.datatype == 'rating':
|
||||||
del_cc = getattr(book, cc_string)[0]
|
del_cc = getattr(book, cc_string)[0]
|
||||||
getattr(book, cc_string).remove(del_cc)
|
getattr(book, cc_string).remove(del_cc)
|
||||||
if len(del_cc.books) == 0:
|
if len(del_cc.books) == 0:
|
||||||
log.debug('remove ' + str(c.id))
|
log.debug('remove ' + str(c.id))
|
||||||
db.session.delete(del_cc)
|
calibre_db.session.delete(del_cc)
|
||||||
db.session.commit()
|
calibre_db.session.commit()
|
||||||
else:
|
else:
|
||||||
del_cc = getattr(book, cc_string)[0]
|
del_cc = getattr(book, cc_string)[0]
|
||||||
getattr(book, cc_string).remove(del_cc)
|
getattr(book, cc_string).remove(del_cc)
|
||||||
log.debug('remove ' + str(c.id))
|
log.debug('remove ' + str(c.id))
|
||||||
db.session.delete(del_cc)
|
calibre_db.session.delete(del_cc)
|
||||||
db.session.commit()
|
calibre_db.session.commit()
|
||||||
else:
|
else:
|
||||||
modify_database_object([u''], getattr(book, cc_string), db.cc_classes[c.id],
|
modify_database_object([u''], getattr(book, cc_string), db.cc_classes[c.id],
|
||||||
db.session, 'custom')
|
calibre_db.session, 'custom')
|
||||||
db.session.query(db.Books).filter(db.Books.id == book_id).delete()
|
calibre_db.session.query(db.Books).filter(db.Books.id == book_id).delete()
|
||||||
else:
|
else:
|
||||||
db.session.query(db.Data).filter(db.Data.book == book.id).\
|
calibre_db.session.query(db.Data).filter(db.Data.book == book.id).\
|
||||||
filter(db.Data.format == book_format).delete()
|
filter(db.Data.format == book_format).delete()
|
||||||
db.session.commit()
|
calibre_db.session.commit()
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
log.debug(e)
|
log.debug(e)
|
||||||
db.session.rollback()
|
calibre_db.session.rollback()
|
||||||
else:
|
else:
|
||||||
# book not found
|
# book not found
|
||||||
log.error('Book with id "%s" could not be deleted: not found', book_id)
|
log.error('Book with id "%s" could not be deleted: not found', book_id)
|
||||||
|
@ -244,11 +247,9 @@ def delete_book(book_id, book_format):
|
||||||
|
|
||||||
|
|
||||||
def render_edit_book(book_id):
|
def render_edit_book(book_id):
|
||||||
db.update_title_sort(config)
|
calibre_db.update_title_sort(config)
|
||||||
cc = db.session.query(db.Custom_Columns).filter(db.Custom_Columns.datatype.notin_(db.cc_exceptions)).all()
|
cc = calibre_db.session.query(db.Custom_Columns).filter(db.Custom_Columns.datatype.notin_(db.cc_exceptions)).all()
|
||||||
book = db.session.query(db.Books)\
|
book = calibre_db.get_filtered_book(book_id)
|
||||||
.filter(db.Books.id == book_id).filter(common_filters()).first()
|
|
||||||
|
|
||||||
if not book:
|
if not book:
|
||||||
flash(_(u"Error opening eBook. File does not exist or file is not accessible"), category="error")
|
flash(_(u"Error opening eBook. File does not exist or file is not accessible"), category="error")
|
||||||
return redirect(url_for("web.index"))
|
return redirect(url_for("web.index"))
|
||||||
|
@ -256,7 +257,7 @@ def render_edit_book(book_id):
|
||||||
for lang in book.languages:
|
for lang in book.languages:
|
||||||
lang.language_name = isoLanguages.get_language_name(get_locale(), lang.lang_code)
|
lang.language_name = isoLanguages.get_language_name(get_locale(), lang.lang_code)
|
||||||
|
|
||||||
book = order_authors(book)
|
book = calibre_db.order_authors(book)
|
||||||
|
|
||||||
author_names = []
|
author_names = []
|
||||||
for authr in book.authors:
|
for authr in book.authors:
|
||||||
|
@ -264,19 +265,25 @@ def render_edit_book(book_id):
|
||||||
|
|
||||||
# Option for showing convertbook button
|
# Option for showing convertbook button
|
||||||
valid_source_formats=list()
|
valid_source_formats=list()
|
||||||
if config.config_ebookconverter == 2:
|
allowed_conversion_formats = list()
|
||||||
|
kepub_possible=None
|
||||||
|
if config.config_converterpath:
|
||||||
for file in book.data:
|
for file in book.data:
|
||||||
if file.format.lower() in constants.EXTENSIONS_CONVERT:
|
if file.format.lower() in constants.EXTENSIONS_CONVERT:
|
||||||
valid_source_formats.append(file.format.lower())
|
valid_source_formats.append(file.format.lower())
|
||||||
|
if config.config_kepubifypath and 'epub' in [file.format.lower() for file in book.data]:
|
||||||
|
kepub_possible = True
|
||||||
|
if not config.config_converterpath:
|
||||||
|
valid_source_formats.append('epub')
|
||||||
|
|
||||||
# Determine what formats don't already exist
|
# Determine what formats don't already exist
|
||||||
allowed_conversion_formats = constants.EXTENSIONS_CONVERT.copy()
|
if config.config_converterpath:
|
||||||
for file in book.data:
|
allowed_conversion_formats = constants.EXTENSIONS_CONVERT.copy()
|
||||||
try:
|
for file in book.data:
|
||||||
allowed_conversion_formats.remove(file.format.lower())
|
if file.format.lower() in allowed_conversion_formats:
|
||||||
except Exception:
|
allowed_conversion_formats.remove(file.format.lower())
|
||||||
log.warning('%s already removed from list.', file.format.lower())
|
if kepub_possible:
|
||||||
|
allowed_conversion_formats.append('kepub')
|
||||||
return render_title_template('book_edit.html', book=book, authors=author_names, cc=cc,
|
return render_title_template('book_edit.html', book=book, authors=author_names, cc=cc,
|
||||||
title=_(u"edit metadata"), page="editbook",
|
title=_(u"edit metadata"), page="editbook",
|
||||||
conversion_formats=allowed_conversion_formats,
|
conversion_formats=allowed_conversion_formats,
|
||||||
|
@ -293,7 +300,7 @@ def edit_book_ratings(to_save, book):
|
||||||
ratingx2 = int(float(to_save["rating"]) * 2)
|
ratingx2 = int(float(to_save["rating"]) * 2)
|
||||||
if ratingx2 != old_rating:
|
if ratingx2 != old_rating:
|
||||||
changed = True
|
changed = True
|
||||||
is_rating = db.session.query(db.Ratings).filter(db.Ratings.rating == ratingx2).first()
|
is_rating = calibre_db.session.query(db.Ratings).filter(db.Ratings.rating == ratingx2).first()
|
||||||
if is_rating:
|
if is_rating:
|
||||||
book.ratings.append(is_rating)
|
book.ratings.append(is_rating)
|
||||||
else:
|
else:
|
||||||
|
@ -307,15 +314,59 @@ def edit_book_ratings(to_save, book):
|
||||||
changed = True
|
changed = True
|
||||||
return changed
|
return changed
|
||||||
|
|
||||||
|
def edit_book_tags(tags, book):
|
||||||
|
input_tags = tags.split(',')
|
||||||
|
input_tags = list(map(lambda it: it.strip(), input_tags))
|
||||||
|
# if input_tags[0] !="": ??
|
||||||
|
return modify_database_object(input_tags, book.tags, db.Tags, calibre_db.session, 'tags')
|
||||||
|
|
||||||
def edit_book_languages(to_save, book):
|
|
||||||
input_languages = to_save["languages"].split(',')
|
def edit_book_series(series, book):
|
||||||
|
input_series = [series.strip()]
|
||||||
|
input_series = [x for x in input_series if x != '']
|
||||||
|
return modify_database_object(input_series, book.series, db.Series, calibre_db.session, 'series')
|
||||||
|
|
||||||
|
|
||||||
|
def edit_book_series_index(series_index, book):
|
||||||
|
# Add default series_index to book
|
||||||
|
modif_date = False
|
||||||
|
series_index = series_index or '1'
|
||||||
|
#if series_index == '':
|
||||||
|
# series_index = '1'
|
||||||
|
if book.series_index != series_index:
|
||||||
|
book.series_index = series_index
|
||||||
|
modif_date = True
|
||||||
|
return modif_date
|
||||||
|
|
||||||
|
# Handle book comments/description
|
||||||
|
def edit_book_comments(comments, book):
|
||||||
|
modif_date = False
|
||||||
|
if len(book.comments):
|
||||||
|
if book.comments[0].text != comments:
|
||||||
|
book.comments[0].text = comments
|
||||||
|
modif_date = True
|
||||||
|
else:
|
||||||
|
if comments:
|
||||||
|
book.comments.append(db.Comments(text=comments, book=book.id))
|
||||||
|
modif_date = True
|
||||||
|
return modif_date
|
||||||
|
|
||||||
|
|
||||||
|
def edit_book_languages(languages, book, upload=False):
|
||||||
|
input_languages = languages.split(',')
|
||||||
unknown_languages = []
|
unknown_languages = []
|
||||||
input_l = isoLanguages.get_language_codes(get_locale(), input_languages, unknown_languages)
|
input_l = isoLanguages.get_language_codes(get_locale(), input_languages, unknown_languages)
|
||||||
for l in unknown_languages:
|
for l in unknown_languages:
|
||||||
log.error('%s is not a valid language', l)
|
log.error('%s is not a valid language', l)
|
||||||
flash(_(u"%(langname)s is not a valid language", langname=l), category="error")
|
flash(_(u"%(langname)s is not a valid language", langname=l), category="warning")
|
||||||
return modify_database_object(list(input_l), book.languages, db.Languages, db.session, 'languages')
|
# ToDo: Not working correct
|
||||||
|
if upload and len(input_l) == 1:
|
||||||
|
# If the language of the file is excluded from the users view, it's not imported, to allow the user to view
|
||||||
|
# the book it's language is set to the filter language
|
||||||
|
if input_l[0] != current_user.filter_language() and current_user.filter_language() != "all":
|
||||||
|
input_l[0] = calibre_db.session.query(db.Languages). \
|
||||||
|
filter(db.Languages.lang_code == current_user.filter_language()).first()
|
||||||
|
return modify_database_object(input_l, book.languages, db.Languages, calibre_db.session, 'languages')
|
||||||
|
|
||||||
|
|
||||||
def edit_book_publisher(to_save, book):
|
def edit_book_publisher(to_save, book):
|
||||||
|
@ -323,15 +374,15 @@ def edit_book_publisher(to_save, book):
|
||||||
if to_save["publisher"]:
|
if to_save["publisher"]:
|
||||||
publisher = to_save["publisher"].rstrip().strip()
|
publisher = to_save["publisher"].rstrip().strip()
|
||||||
if len(book.publishers) == 0 or (len(book.publishers) > 0 and publisher != book.publishers[0].name):
|
if len(book.publishers) == 0 or (len(book.publishers) > 0 and publisher != book.publishers[0].name):
|
||||||
changed |= modify_database_object([publisher], book.publishers, db.Publishers, db.session, 'publisher')
|
changed |= modify_database_object([publisher], book.publishers, db.Publishers, calibre_db.session, 'publisher')
|
||||||
elif len(book.publishers):
|
elif len(book.publishers):
|
||||||
changed |= modify_database_object([], book.publishers, db.Publishers, db.session, 'publisher')
|
changed |= modify_database_object([], book.publishers, db.Publishers, calibre_db.session, 'publisher')
|
||||||
return changed
|
return changed
|
||||||
|
|
||||||
|
|
||||||
def edit_cc_data(book_id, book, to_save):
|
def edit_cc_data(book_id, book, to_save):
|
||||||
changed = False
|
changed = False
|
||||||
cc = db.session.query(db.Custom_Columns).filter(db.Custom_Columns.datatype.notin_(db.cc_exceptions)).all()
|
cc = calibre_db.session.query(db.Custom_Columns).filter(db.Custom_Columns.datatype.notin_(db.cc_exceptions)).all()
|
||||||
for c in cc:
|
for c in cc:
|
||||||
cc_string = "custom_column_" + str(c.id)
|
cc_string = "custom_column_" + str(c.id)
|
||||||
if not c.is_multiple:
|
if not c.is_multiple:
|
||||||
|
@ -354,12 +405,12 @@ def edit_cc_data(book_id, book, to_save):
|
||||||
else:
|
else:
|
||||||
del_cc = getattr(book, cc_string)[0]
|
del_cc = getattr(book, cc_string)[0]
|
||||||
getattr(book, cc_string).remove(del_cc)
|
getattr(book, cc_string).remove(del_cc)
|
||||||
db.session.delete(del_cc)
|
calibre_db.session.delete(del_cc)
|
||||||
changed = True
|
changed = True
|
||||||
else:
|
else:
|
||||||
cc_class = db.cc_classes[c.id]
|
cc_class = db.cc_classes[c.id]
|
||||||
new_cc = cc_class(value=to_save[cc_string], book=book_id)
|
new_cc = cc_class(value=to_save[cc_string], book=book_id)
|
||||||
db.session.add(new_cc)
|
calibre_db.session.add(new_cc)
|
||||||
changed = True
|
changed = True
|
||||||
|
|
||||||
else:
|
else:
|
||||||
|
@ -371,18 +422,18 @@ def edit_cc_data(book_id, book, to_save):
|
||||||
del_cc = getattr(book, cc_string)[0]
|
del_cc = getattr(book, cc_string)[0]
|
||||||
getattr(book, cc_string).remove(del_cc)
|
getattr(book, cc_string).remove(del_cc)
|
||||||
if len(del_cc.books) == 0:
|
if len(del_cc.books) == 0:
|
||||||
db.session.delete(del_cc)
|
calibre_db.session.delete(del_cc)
|
||||||
changed = True
|
changed = True
|
||||||
cc_class = db.cc_classes[c.id]
|
cc_class = db.cc_classes[c.id]
|
||||||
new_cc = db.session.query(cc_class).filter(
|
new_cc = calibre_db.session.query(cc_class).filter(
|
||||||
cc_class.value == to_save[cc_string].strip()).first()
|
cc_class.value == to_save[cc_string].strip()).first()
|
||||||
# if no cc val is found add it
|
# if no cc val is found add it
|
||||||
if new_cc is None:
|
if new_cc is None:
|
||||||
new_cc = cc_class(value=to_save[cc_string].strip())
|
new_cc = cc_class(value=to_save[cc_string].strip())
|
||||||
db.session.add(new_cc)
|
calibre_db.session.add(new_cc)
|
||||||
changed = True
|
changed = True
|
||||||
db.session.flush()
|
calibre_db.session.flush()
|
||||||
new_cc = db.session.query(cc_class).filter(
|
new_cc = calibre_db.session.query(cc_class).filter(
|
||||||
cc_class.value == to_save[cc_string].strip()).first()
|
cc_class.value == to_save[cc_string].strip()).first()
|
||||||
# add cc value to book
|
# add cc value to book
|
||||||
getattr(book, cc_string).append(new_cc)
|
getattr(book, cc_string).append(new_cc)
|
||||||
|
@ -392,13 +443,16 @@ def edit_cc_data(book_id, book, to_save):
|
||||||
del_cc = getattr(book, cc_string)[0]
|
del_cc = getattr(book, cc_string)[0]
|
||||||
getattr(book, cc_string).remove(del_cc)
|
getattr(book, cc_string).remove(del_cc)
|
||||||
if not del_cc.books or len(del_cc.books) == 0:
|
if not del_cc.books or len(del_cc.books) == 0:
|
||||||
db.session.delete(del_cc)
|
calibre_db.session.delete(del_cc)
|
||||||
changed = True
|
changed = True
|
||||||
else:
|
else:
|
||||||
input_tags = to_save[cc_string].split(',')
|
input_tags = to_save[cc_string].split(',')
|
||||||
input_tags = list(map(lambda it: it.strip(), input_tags))
|
input_tags = list(map(lambda it: it.strip(), input_tags))
|
||||||
changed |= modify_database_object(input_tags, getattr(book, cc_string), db.cc_classes[c.id], db.session,
|
changed |= modify_database_object(input_tags,
|
||||||
'custom')
|
getattr(book, cc_string),
|
||||||
|
db.cc_classes[c.id],
|
||||||
|
calibre_db.session,
|
||||||
|
'custom')
|
||||||
return changed
|
return changed
|
||||||
|
|
||||||
def upload_single_file(request, book, book_id):
|
def upload_single_file(request, book, book_id):
|
||||||
|
@ -435,17 +489,22 @@ def upload_single_file(request, book, book_id):
|
||||||
return redirect(url_for('web.show_book', book_id=book.id))
|
return redirect(url_for('web.show_book', book_id=book.id))
|
||||||
|
|
||||||
file_size = os.path.getsize(saved_filename)
|
file_size = os.path.getsize(saved_filename)
|
||||||
is_format = db.session.query(db.Data).filter(db.Data.book == book_id).\
|
is_format = calibre_db.get_book_format(book_id, file_ext.upper())
|
||||||
filter(db.Data.format == file_ext.upper()).first()
|
|
||||||
|
|
||||||
# Format entry already exists, no need to update the database
|
# Format entry already exists, no need to update the database
|
||||||
if is_format:
|
if is_format:
|
||||||
log.warning('Book format %s already existing', file_ext.upper())
|
log.warning('Book format %s already existing', file_ext.upper())
|
||||||
else:
|
else:
|
||||||
db_format = db.Data(book_id, file_ext.upper(), file_size, file_name)
|
try:
|
||||||
db.session.add(db_format)
|
db_format = db.Data(book_id, file_ext.upper(), file_size, file_name)
|
||||||
db.session.commit()
|
calibre_db.session.add(db_format)
|
||||||
db.update_title_sort(config)
|
calibre_db.session.commit()
|
||||||
|
calibre_db.update_title_sort(config)
|
||||||
|
except OperationalError as e:
|
||||||
|
calibre_db.session.rollback()
|
||||||
|
log.error('Database error: %s', e)
|
||||||
|
flash(_(u"Database error: %(error)s.", error=e), category="error")
|
||||||
|
return redirect(url_for('web.show_book', book_id=book.id))
|
||||||
|
|
||||||
# Queue uploader info
|
# Queue uploader info
|
||||||
uploadText=_(u"File format %(ext)s added to %(book)s", ext=file_ext.upper(), book=book.title)
|
uploadText=_(u"File format %(ext)s added to %(book)s", ext=file_ext.upper(), book=book.title)
|
||||||
|
@ -454,7 +513,7 @@ def upload_single_file(request, book, book_id):
|
||||||
|
|
||||||
return uploader.process(
|
return uploader.process(
|
||||||
saved_filename, *os.path.splitext(requested_file.filename),
|
saved_filename, *os.path.splitext(requested_file.filename),
|
||||||
rarExcecutable=config.config_rarfile_location)
|
rarExecutable=config.config_rarfile_location)
|
||||||
|
|
||||||
|
|
||||||
def upload_cover(request, book):
|
def upload_cover(request, book):
|
||||||
|
@ -481,9 +540,8 @@ def edit_book(book_id):
|
||||||
return render_edit_book(book_id)
|
return render_edit_book(book_id)
|
||||||
|
|
||||||
# create the function for sorting...
|
# create the function for sorting...
|
||||||
db.update_title_sort(config)
|
calibre_db.update_title_sort(config)
|
||||||
book = db.session.query(db.Books)\
|
book = calibre_db.get_filtered_book(book_id)
|
||||||
.filter(db.Books.id == book_id).filter(common_filters()).first()
|
|
||||||
|
|
||||||
# Book not found
|
# Book not found
|
||||||
if not book:
|
if not book:
|
||||||
|
@ -514,13 +572,13 @@ def edit_book(book_id):
|
||||||
if input_authors == ['']:
|
if input_authors == ['']:
|
||||||
input_authors = [_(u'Unknown')] # prevent empty Author
|
input_authors = [_(u'Unknown')] # prevent empty Author
|
||||||
|
|
||||||
modif_date |= modify_database_object(input_authors, book.authors, db.Authors, db.session, 'author')
|
modif_date |= modify_database_object(input_authors, book.authors, db.Authors, calibre_db.session, 'author')
|
||||||
|
|
||||||
# Search for each author if author is in database, if not, authorname and sorted authorname is generated new
|
# Search for each author if author is in database, if not, authorname and sorted authorname is generated new
|
||||||
# everything then is assembled for sorted author field in database
|
# everything then is assembled for sorted author field in database
|
||||||
sort_authors_list = list()
|
sort_authors_list = list()
|
||||||
for inp in input_authors:
|
for inp in input_authors:
|
||||||
stored_author = db.session.query(db.Authors).filter(db.Authors.name == inp).first()
|
stored_author = calibre_db.session.query(db.Authors).filter(db.Authors.name == inp).first()
|
||||||
if not stored_author:
|
if not stored_author:
|
||||||
stored_author = helper.get_sorted_author(inp)
|
stored_author = helper.get_sorted_author(inp)
|
||||||
else:
|
else:
|
||||||
|
@ -548,33 +606,21 @@ def edit_book(book_id):
|
||||||
else:
|
else:
|
||||||
flash(error, category="error")
|
flash(error, category="error")
|
||||||
|
|
||||||
if book.series_index != to_save["series_index"]:
|
# Add default series_index to book
|
||||||
book.series_index = to_save["series_index"]
|
modif_date |= edit_book_series_index(to_save["series_index"], book)
|
||||||
modif_date = True
|
|
||||||
|
|
||||||
# Handle book comments/description
|
# Handle book comments/description
|
||||||
if len(book.comments):
|
modif_date |= edit_book_comments(to_save["description"], book)
|
||||||
if book.comments[0].text != to_save["description"]:
|
|
||||||
book.comments[0].text = to_save["description"]
|
|
||||||
modif_date = True
|
|
||||||
else:
|
|
||||||
if to_save["description"]:
|
|
||||||
book.comments.append(db.Comments(text=to_save["description"], book=book.id))
|
|
||||||
modif_date = True
|
|
||||||
|
|
||||||
# Handle identifiers
|
# Handle identifiers
|
||||||
input_identifiers = identifier_list(to_save, book)
|
input_identifiers = identifier_list(to_save, book)
|
||||||
modif_date |= modify_identifiers(input_identifiers, book.identifiers, db.session)
|
modif_date |= modify_identifiers(input_identifiers, book.identifiers, calibre_db.session)
|
||||||
|
|
||||||
# Handle book tags
|
# Handle book tags
|
||||||
input_tags = to_save["tags"].split(',')
|
modif_date |= edit_book_tags(to_save['tags'], book)
|
||||||
input_tags = list(map(lambda it: it.strip(), input_tags))
|
|
||||||
modif_date |= modify_database_object(input_tags, book.tags, db.Tags, db.session, 'tags')
|
|
||||||
|
|
||||||
# Handle book series
|
# Handle book series
|
||||||
input_series = [to_save["series"].strip()]
|
modif_date |= edit_book_series(to_save["series"], book)
|
||||||
input_series = [x for x in input_series if x != '']
|
|
||||||
modif_date |= modify_database_object(input_series, book.series, db.Series, db.session, 'series')
|
|
||||||
|
|
||||||
if to_save["pubdate"]:
|
if to_save["pubdate"]:
|
||||||
try:
|
try:
|
||||||
|
@ -588,7 +634,7 @@ def edit_book(book_id):
|
||||||
modif_date |= edit_book_publisher(to_save, book)
|
modif_date |= edit_book_publisher(to_save, book)
|
||||||
|
|
||||||
# handle book languages
|
# handle book languages
|
||||||
modif_date |= edit_book_languages(to_save, book)
|
modif_date |= edit_book_languages(to_save['languages'], book)
|
||||||
|
|
||||||
# handle book ratings
|
# handle book ratings
|
||||||
modif_date |= edit_book_ratings(to_save, book)
|
modif_date |= edit_book_ratings(to_save, book)
|
||||||
|
@ -598,7 +644,7 @@ def edit_book(book_id):
|
||||||
|
|
||||||
if modif_date:
|
if modif_date:
|
||||||
book.last_modified = datetime.utcnow()
|
book.last_modified = datetime.utcnow()
|
||||||
db.session.commit()
|
calibre_db.session.commit()
|
||||||
if config.config_use_google_drive:
|
if config.config_use_google_drive:
|
||||||
gdriveutils.updateGdriveCalibreFromLocal()
|
gdriveutils.updateGdriveCalibreFromLocal()
|
||||||
if "detail_view" in to_save:
|
if "detail_view" in to_save:
|
||||||
|
@ -607,12 +653,12 @@ def edit_book(book_id):
|
||||||
flash(_("Metadata successfully updated"), category="success")
|
flash(_("Metadata successfully updated"), category="success")
|
||||||
return render_edit_book(book_id)
|
return render_edit_book(book_id)
|
||||||
else:
|
else:
|
||||||
db.session.rollback()
|
calibre_db.session.rollback()
|
||||||
flash(error, category="error")
|
flash(error, category="error")
|
||||||
return render_edit_book(book_id)
|
return render_edit_book(book_id)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
log.exception(e)
|
log.exception(e)
|
||||||
db.session.rollback()
|
calibre_db.session.rollback()
|
||||||
flash(_("Error editing book, please check logfile for details"), category="error")
|
flash(_("Error editing book, please check logfile for details"), category="error")
|
||||||
return redirect(url_for('web.show_book', book_id=book.id))
|
return redirect(url_for('web.show_book', book_id=book.id))
|
||||||
|
|
||||||
|
@ -652,179 +698,172 @@ def upload():
|
||||||
abort(404)
|
abort(404)
|
||||||
if request.method == 'POST' and 'btn-upload' in request.files:
|
if request.method == 'POST' and 'btn-upload' in request.files:
|
||||||
for requested_file in request.files.getlist("btn-upload"):
|
for requested_file in request.files.getlist("btn-upload"):
|
||||||
# create the function for sorting...
|
|
||||||
db.update_title_sort(config)
|
|
||||||
db.session.connection().connection.connection.create_function('uuid4', 0, lambda: str(uuid4()))
|
|
||||||
|
|
||||||
# check if file extension is correct
|
|
||||||
if '.' in requested_file.filename:
|
|
||||||
file_ext = requested_file.filename.rsplit('.', 1)[-1].lower()
|
|
||||||
if file_ext not in constants.EXTENSIONS_UPLOAD:
|
|
||||||
flash(
|
|
||||||
_("File extension '%(ext)s' is not allowed to be uploaded to this server",
|
|
||||||
ext=file_ext), category="error")
|
|
||||||
return Response(json.dumps({"location": url_for("web.index")}), mimetype='application/json')
|
|
||||||
else:
|
|
||||||
flash(_('File to be uploaded must have an extension'), category="error")
|
|
||||||
return Response(json.dumps({"location": url_for("web.index")}), mimetype='application/json')
|
|
||||||
|
|
||||||
# extract metadata from file
|
|
||||||
try:
|
try:
|
||||||
meta = uploader.upload(requested_file, config.config_rarfile_location)
|
modif_date = False
|
||||||
except (IOError, OSError):
|
# create the function for sorting...
|
||||||
log.error("File %s could not saved to temp dir", requested_file.filename)
|
calibre_db.update_title_sort(config)
|
||||||
flash(_(u"File %(filename)s could not saved to temp dir",
|
calibre_db.session.connection().connection.connection.create_function('uuid4', 0, lambda: str(uuid4()))
|
||||||
filename= requested_file.filename), category="error")
|
|
||||||
return Response(json.dumps({"location": url_for("web.index")}), mimetype='application/json')
|
|
||||||
title = meta.title
|
|
||||||
authr = meta.author
|
|
||||||
tags = meta.tags
|
|
||||||
series = meta.series
|
|
||||||
series_index = meta.series_id
|
|
||||||
title_dir = helper.get_valid_filename(title)
|
|
||||||
author_dir = helper.get_valid_filename(authr)
|
|
||||||
filepath = os.path.join(config.config_calibre_dir, author_dir, title_dir)
|
|
||||||
saved_filename = os.path.join(filepath, title_dir + meta.extension.lower())
|
|
||||||
|
|
||||||
if title != _(u'Unknown') and authr != _(u'Unknown'):
|
# check if file extension is correct
|
||||||
entry = helper.check_exists_book(authr, title)
|
if '.' in requested_file.filename:
|
||||||
if entry:
|
file_ext = requested_file.filename.rsplit('.', 1)[-1].lower()
|
||||||
log.info("Uploaded book probably exists in library")
|
if file_ext not in constants.EXTENSIONS_UPLOAD:
|
||||||
flash(_(u"Uploaded book probably exists in the library, consider to change before upload new: ")
|
flash(
|
||||||
+ Markup(render_title_template('book_exists_flash.html', entry=entry)), category="warning")
|
_("File extension '%(ext)s' is not allowed to be uploaded to this server",
|
||||||
|
ext=file_ext), category="error")
|
||||||
# check if file path exists, otherwise create it, copy file to calibre path and delete temp file
|
return Response(json.dumps({"location": url_for("web.index")}), mimetype='application/json')
|
||||||
if not os.path.exists(filepath):
|
else:
|
||||||
try:
|
flash(_('File to be uploaded must have an extension'), category="error")
|
||||||
os.makedirs(filepath)
|
|
||||||
except OSError:
|
|
||||||
log.error("Failed to create path %s (Permission denied)", filepath)
|
|
||||||
flash(_(u"Failed to create path %(path)s (Permission denied).", path=filepath), category="error")
|
|
||||||
return Response(json.dumps({"location": url_for("web.index")}), mimetype='application/json')
|
return Response(json.dumps({"location": url_for("web.index")}), mimetype='application/json')
|
||||||
try:
|
|
||||||
copyfile(meta.file_path, saved_filename)
|
|
||||||
os.unlink(meta.file_path)
|
|
||||||
except OSError as e:
|
|
||||||
log.error("Failed to move file %s: %s", saved_filename, e)
|
|
||||||
flash(_(u"Failed to Move File %(file)s: %(error)s", file=saved_filename, error=e), category="error")
|
|
||||||
return Response(json.dumps({"location": url_for("web.index")}), mimetype='application/json')
|
|
||||||
|
|
||||||
if meta.cover is None:
|
# extract metadata from file
|
||||||
has_cover = 0
|
|
||||||
copyfile(os.path.join(constants.STATIC_DIR, 'generic_cover.jpg'),
|
|
||||||
os.path.join(filepath, "cover.jpg"))
|
|
||||||
else:
|
|
||||||
has_cover = 1
|
|
||||||
try:
|
try:
|
||||||
copyfile(meta.cover, os.path.join(filepath, "cover.jpg"))
|
meta = uploader.upload(requested_file, config.config_rarfile_location)
|
||||||
os.unlink(meta.cover)
|
except (IOError, OSError):
|
||||||
|
log.error("File %s could not saved to temp dir", requested_file.filename)
|
||||||
|
flash(_(u"File %(filename)s could not saved to temp dir",
|
||||||
|
filename= requested_file.filename), category="error")
|
||||||
|
return Response(json.dumps({"location": url_for("web.index")}), mimetype='application/json')
|
||||||
|
title = meta.title
|
||||||
|
authr = meta.author
|
||||||
|
|
||||||
|
if title != _(u'Unknown') and authr != _(u'Unknown'):
|
||||||
|
entry = calibre_db.check_exists_book(authr, title)
|
||||||
|
if entry:
|
||||||
|
log.info("Uploaded book probably exists in library")
|
||||||
|
flash(_(u"Uploaded book probably exists in the library, consider to change before upload new: ")
|
||||||
|
+ Markup(render_title_template('book_exists_flash.html', entry=entry)), category="warning")
|
||||||
|
|
||||||
|
# handle authors
|
||||||
|
input_authors = authr.split('&')
|
||||||
|
# handle_authors(input_authors)
|
||||||
|
input_authors = list(map(lambda it: it.strip().replace(',', '|'), input_authors))
|
||||||
|
# we have all author names now
|
||||||
|
if input_authors == ['']:
|
||||||
|
input_authors = [_(u'Unknown')] # prevent empty Author
|
||||||
|
|
||||||
|
sort_authors_list=list()
|
||||||
|
db_author = None
|
||||||
|
for inp in input_authors:
|
||||||
|
stored_author = calibre_db.session.query(db.Authors).filter(db.Authors.name == inp).first()
|
||||||
|
if not stored_author:
|
||||||
|
if not db_author:
|
||||||
|
db_author = db.Authors(inp, helper.get_sorted_author(inp), "")
|
||||||
|
calibre_db.session.add(db_author)
|
||||||
|
calibre_db.session.commit()
|
||||||
|
sort_author = helper.get_sorted_author(inp)
|
||||||
|
else:
|
||||||
|
if not db_author:
|
||||||
|
db_author = stored_author
|
||||||
|
sort_author = stored_author.sort
|
||||||
|
sort_authors_list.append(sort_author) # helper.get_sorted_author(sort_author))
|
||||||
|
sort_authors = ' & '.join(sort_authors_list)
|
||||||
|
|
||||||
|
title_dir = helper.get_valid_filename(title)
|
||||||
|
author_dir = helper.get_valid_filename(db_author.name)
|
||||||
|
filepath = os.path.join(config.config_calibre_dir, author_dir, title_dir)
|
||||||
|
saved_filename = os.path.join(filepath, title_dir + meta.extension.lower())
|
||||||
|
|
||||||
|
# check if file path exists, otherwise create it, copy file to calibre path and delete temp file
|
||||||
|
if not os.path.exists(filepath):
|
||||||
|
try:
|
||||||
|
os.makedirs(filepath)
|
||||||
|
except OSError:
|
||||||
|
log.error("Failed to create path %s (Permission denied)", filepath)
|
||||||
|
flash(_(u"Failed to create path %(path)s (Permission denied).", path=filepath), category="error")
|
||||||
|
return Response(json.dumps({"location": url_for("web.index")}), mimetype='application/json')
|
||||||
|
try:
|
||||||
|
copyfile(meta.file_path, saved_filename)
|
||||||
|
os.unlink(meta.file_path)
|
||||||
except OSError as e:
|
except OSError as e:
|
||||||
log.error("Failed to move cover file %s: %s", os.path.join(filepath, "cover.jpg"), e)
|
log.error("Failed to move file %s: %s", saved_filename, e)
|
||||||
flash(_(u"Failed to Move Cover File %(file)s: %(error)s", file=os.path.join(filepath, "cover.jpg"),
|
flash(_(u"Failed to Move File %(file)s: %(error)s", file=saved_filename, error=e), category="error")
|
||||||
error=e),
|
|
||||||
category="error")
|
|
||||||
return Response(json.dumps({"location": url_for("web.index")}), mimetype='application/json')
|
return Response(json.dumps({"location": url_for("web.index")}), mimetype='application/json')
|
||||||
|
|
||||||
# handle authors
|
if meta.cover is None:
|
||||||
is_author = db.session.query(db.Authors).filter(db.Authors.name == authr).first()
|
has_cover = 0
|
||||||
if is_author:
|
copyfile(os.path.join(constants.STATIC_DIR, 'generic_cover.jpg'),
|
||||||
db_author = is_author
|
os.path.join(filepath, "cover.jpg"))
|
||||||
else:
|
|
||||||
db_author = db.Authors(authr, helper.get_sorted_author(authr), "")
|
|
||||||
db.session.add(db_author)
|
|
||||||
|
|
||||||
# handle series
|
|
||||||
db_series = None
|
|
||||||
is_series = db.session.query(db.Series).filter(db.Series.name == series).first()
|
|
||||||
if is_series:
|
|
||||||
db_series = is_series
|
|
||||||
elif series != '':
|
|
||||||
db_series = db.Series(series, "")
|
|
||||||
db.session.add(db_series)
|
|
||||||
|
|
||||||
# add language actually one value in list
|
|
||||||
input_language = meta.languages
|
|
||||||
db_language = None
|
|
||||||
if input_language != "":
|
|
||||||
input_language = isoLanguages.get(name=input_language).part3
|
|
||||||
hasLanguage = db.session.query(db.Languages).filter(db.Languages.lang_code == input_language).first()
|
|
||||||
if hasLanguage:
|
|
||||||
db_language = hasLanguage
|
|
||||||
else:
|
else:
|
||||||
db_language = db.Languages(input_language)
|
has_cover = 1
|
||||||
db.session.add(db_language)
|
|
||||||
|
|
||||||
# If the language of the file is excluded from the users view, it's not imported, to allow the user to view
|
# combine path and normalize path from windows systems
|
||||||
# the book it's language is set to the filter language
|
path = os.path.join(author_dir, title_dir).replace('\\', '/')
|
||||||
if db_language != current_user.filter_language() and current_user.filter_language() != "all":
|
# Calibre adds books with utc as timezone
|
||||||
db_language = db.session.query(db.Languages).\
|
db_book = db.Books(title, "", sort_authors, datetime.utcnow(), datetime(101, 1, 1),
|
||||||
filter(db.Languages.lang_code == current_user.filter_language()).first()
|
'1', datetime.utcnow(), path, has_cover, db_author, [], "")
|
||||||
|
|
||||||
# combine path and normalize path from windows systems
|
modif_date |= modify_database_object(input_authors, db_book.authors, db.Authors, calibre_db.session,
|
||||||
path = os.path.join(author_dir, title_dir).replace('\\', '/')
|
'author')
|
||||||
# Calibre adds books with utc as timezone
|
|
||||||
db_book = db.Books(title, "", db_author.sort, datetime.utcnow(), datetime(101, 1, 1),
|
|
||||||
series_index, datetime.utcnow(), path, has_cover, db_author, [], db_language)
|
|
||||||
db_book.authors.append(db_author)
|
|
||||||
if db_series:
|
|
||||||
db_book.series.append(db_series)
|
|
||||||
if db_language is not None:
|
|
||||||
db_book.languages.append(db_language)
|
|
||||||
file_size = os.path.getsize(saved_filename)
|
|
||||||
db_data = db.Data(db_book, meta.extension.upper()[1:], file_size, title_dir)
|
|
||||||
|
|
||||||
# handle tags
|
# Add series_index to book
|
||||||
input_tags = tags.split(',')
|
modif_date |= edit_book_series_index(meta.series_id, db_book)
|
||||||
input_tags = list(map(lambda it: it.strip(), input_tags))
|
|
||||||
if input_tags[0] !="":
|
|
||||||
modify_database_object(input_tags, db_book.tags, db.Tags, db.session, 'tags')
|
|
||||||
|
|
||||||
# flush content, get db_book.id available
|
# add languages
|
||||||
db_book.data.append(db_data)
|
modif_date |= edit_book_languages(meta.languages, db_book, upload=True)
|
||||||
db.session.add(db_book)
|
|
||||||
db.session.flush()
|
|
||||||
|
|
||||||
# add comment
|
# handle tags
|
||||||
book_id = db_book.id
|
modif_date |= edit_book_tags(meta.tags, db_book)
|
||||||
upload_comment = Markup(meta.description).unescape()
|
|
||||||
if upload_comment != "":
|
|
||||||
db.session.add(db.Comments(upload_comment, book_id))
|
|
||||||
|
|
||||||
# save data to database, reread data
|
# handle series
|
||||||
db.session.commit()
|
modif_date |= edit_book_series(meta.series, db_book)
|
||||||
db.update_title_sort(config)
|
|
||||||
# Reread book. It's important not to filter the result, as it could have language which hide it from
|
|
||||||
# current users view (tags are not stored/extracted from metadata and could also be limited)
|
|
||||||
book = db.session.query(db.Books).filter(db.Books.id == book_id).first()
|
|
||||||
# upload book to gdrive if nesseccary and add "(bookid)" to folder name
|
|
||||||
if config.config_use_google_drive:
|
|
||||||
gdriveutils.updateGdriveCalibreFromLocal()
|
|
||||||
error = helper.update_dir_stucture(book.id, config.config_calibre_dir)
|
|
||||||
db.session.commit()
|
|
||||||
if config.config_use_google_drive:
|
|
||||||
gdriveutils.updateGdriveCalibreFromLocal()
|
|
||||||
if error:
|
|
||||||
flash(error, category="error")
|
|
||||||
uploadText=_(u"File %(file)s uploaded", file=book.title)
|
|
||||||
worker.add_upload(current_user.nickname,
|
|
||||||
"<a href=\"" + url_for('web.show_book', book_id=book.id) + "\">" + uploadText + "</a>")
|
|
||||||
|
|
||||||
# create data for displaying display Full language name instead of iso639.part3language
|
# Add file to book
|
||||||
if db_language is not None:
|
file_size = os.path.getsize(saved_filename)
|
||||||
book.languages[0].language_name = _(meta.languages)
|
db_data = db.Data(db_book, meta.extension.upper()[1:], file_size, title_dir)
|
||||||
author_names = []
|
db_book.data.append(db_data)
|
||||||
for author in db_book.authors:
|
calibre_db.session.add(db_book)
|
||||||
author_names.append(author.name)
|
|
||||||
if len(request.files.getlist("btn-upload")) < 2:
|
# flush content, get db_book.id available
|
||||||
if current_user.role_edit() or current_user.role_admin():
|
calibre_db.session.flush()
|
||||||
resp = {"location": url_for('editbook.edit_book', book_id=db_book.id)}
|
|
||||||
return Response(json.dumps(resp), mimetype='application/json')
|
# Comments needs book id therfore only possiblw after flush
|
||||||
else:
|
modif_date |= edit_book_comments(Markup(meta.description).unescape(), db_book)
|
||||||
resp = {"location": url_for('web.show_book', book_id=db_book.id)}
|
|
||||||
return Response(json.dumps(resp), mimetype='application/json')
|
book_id = db_book.id
|
||||||
|
title = db_book.title
|
||||||
|
|
||||||
|
error = helper.update_dir_stucture(book_id, config.config_calibre_dir, input_authors[0])
|
||||||
|
|
||||||
|
# move cover to final directory, including book id
|
||||||
|
if has_cover:
|
||||||
|
try:
|
||||||
|
new_coverpath = os.path.join(config.config_calibre_dir, db_book.path, "cover.jpg")
|
||||||
|
copyfile(meta.cover, new_coverpath)
|
||||||
|
os.unlink(meta.cover)
|
||||||
|
except OSError as e:
|
||||||
|
log.error("Failed to move cover file %s: %s", new_coverpath, e)
|
||||||
|
flash(_(u"Failed to Move Cover File %(file)s: %(error)s", file=new_coverpath,
|
||||||
|
error=e),
|
||||||
|
category="error")
|
||||||
|
|
||||||
|
# save data to database, reread data
|
||||||
|
calibre_db.session.commit()
|
||||||
|
#calibre_db.setup_db(config, ub.app_DB_path)
|
||||||
|
# Reread book. It's important not to filter the result, as it could have language which hide it from
|
||||||
|
# current users view (tags are not stored/extracted from metadata and could also be limited)
|
||||||
|
#book = calibre_db.get_book(book_id)
|
||||||
|
if config.config_use_google_drive:
|
||||||
|
gdriveutils.updateGdriveCalibreFromLocal()
|
||||||
|
if error:
|
||||||
|
flash(error, category="error")
|
||||||
|
uploadText=_(u"File %(file)s uploaded", file=title)
|
||||||
|
worker.add_upload(current_user.nickname,
|
||||||
|
"<a href=\"" + url_for('web.show_book', book_id=book_id) + "\">" + uploadText + "</a>")
|
||||||
|
|
||||||
|
if len(request.files.getlist("btn-upload")) < 2:
|
||||||
|
if current_user.role_edit() or current_user.role_admin():
|
||||||
|
resp = {"location": url_for('editbook.edit_book', book_id=book_id)}
|
||||||
|
return Response(json.dumps(resp), mimetype='application/json')
|
||||||
|
else:
|
||||||
|
resp = {"location": url_for('web.show_book', book_id=book_id)}
|
||||||
|
return Response(json.dumps(resp), mimetype='application/json')
|
||||||
|
except OperationalError as e:
|
||||||
|
calibre_db.session.rollback()
|
||||||
|
log.error("Database error: %s", e)
|
||||||
|
flash(_(u"Database error: %(error)s.", error=e), category="error")
|
||||||
return Response(json.dumps({"location": url_for("web.index")}), mimetype='application/json')
|
return Response(json.dumps({"location": url_for("web.index")}), mimetype='application/json')
|
||||||
|
|
||||||
|
|
||||||
@editbook.route("/admin/book/convert/<int:book_id>", methods=['POST'])
|
@editbook.route("/admin/book/convert/<int:book_id>", methods=['POST'])
|
||||||
@login_required_if_no_ano
|
@login_required_if_no_ano
|
||||||
@edit_required
|
@edit_required
|
||||||
|
|
|
@ -39,7 +39,7 @@ try:
|
||||||
except ImportError:
|
except ImportError:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
from . import logger, gdriveutils, config, db
|
from . import logger, gdriveutils, config, ub, calibre_db
|
||||||
from .web import admin_required
|
from .web import admin_required
|
||||||
|
|
||||||
|
|
||||||
|
@ -145,7 +145,8 @@ def on_received_watch_confirmation():
|
||||||
dbpath = os.path.join(config.config_calibre_dir, "metadata.db")
|
dbpath = os.path.join(config.config_calibre_dir, "metadata.db")
|
||||||
else:
|
else:
|
||||||
dbpath = os.path.join(config.config_calibre_dir, "metadata.db").encode()
|
dbpath = os.path.join(config.config_calibre_dir, "metadata.db").encode()
|
||||||
if not response['deleted'] and response['file']['title'] == 'metadata.db' and response['file']['md5Checksum'] != hashlib.md5(dbpath):
|
if not response['deleted'] and response['file']['title'] == 'metadata.db' \
|
||||||
|
and response['file']['md5Checksum'] != hashlib.md5(dbpath):
|
||||||
tmpDir = tempfile.gettempdir()
|
tmpDir = tempfile.gettempdir()
|
||||||
log.info('Database file updated')
|
log.info('Database file updated')
|
||||||
copyfile(dbpath, os.path.join(tmpDir, "metadata.db_" + str(current_milli_time())))
|
copyfile(dbpath, os.path.join(tmpDir, "metadata.db_" + str(current_milli_time())))
|
||||||
|
@ -154,7 +155,7 @@ def on_received_watch_confirmation():
|
||||||
log.info('Setting up new DB')
|
log.info('Setting up new DB')
|
||||||
# prevent error on windows, as os.rename does on exisiting files
|
# prevent error on windows, as os.rename does on exisiting files
|
||||||
move(os.path.join(tmpDir, "tmp_metadata.db"), dbpath)
|
move(os.path.join(tmpDir, "tmp_metadata.db"), dbpath)
|
||||||
db.setup_db(config)
|
calibre_db.setup_db(config, ub.app_DB_path)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
log.exception(e)
|
log.exception(e)
|
||||||
updateMetaData()
|
updateMetaData()
|
||||||
|
|
243
cps/helper.py
243
cps/helper.py
|
@ -23,7 +23,6 @@ import os
|
||||||
import io
|
import io
|
||||||
import json
|
import json
|
||||||
import mimetypes
|
import mimetypes
|
||||||
import random
|
|
||||||
import re
|
import re
|
||||||
import shutil
|
import shutil
|
||||||
import time
|
import time
|
||||||
|
@ -42,6 +41,7 @@ from flask_login import current_user
|
||||||
from sqlalchemy.sql.expression import true, false, and_, or_, text, func
|
from sqlalchemy.sql.expression import true, false, and_, or_, text, func
|
||||||
from werkzeug.datastructures import Headers
|
from werkzeug.datastructures import Headers
|
||||||
from werkzeug.security import generate_password_hash
|
from werkzeug.security import generate_password_hash
|
||||||
|
from . import calibre_db
|
||||||
|
|
||||||
try:
|
try:
|
||||||
from urllib.parse import quote
|
from urllib.parse import quote
|
||||||
|
@ -74,8 +74,8 @@ log = logger.create()
|
||||||
|
|
||||||
# Convert existing book entry to new format
|
# Convert existing book entry to new format
|
||||||
def convert_book_format(book_id, calibrepath, old_book_format, new_book_format, user_id, kindle_mail=None):
|
def convert_book_format(book_id, calibrepath, old_book_format, new_book_format, user_id, kindle_mail=None):
|
||||||
book = db.session.query(db.Books).filter(db.Books.id == book_id).first()
|
book = calibre_db.get_book(book_id)
|
||||||
data = db.session.query(db.Data).filter(db.Data.book == book.id).filter(db.Data.format == old_book_format).first()
|
data = calibre_db.get_book_format(book.id, old_book_format)
|
||||||
if not data:
|
if not data:
|
||||||
error_message = _(u"%(format)s format not found for book id: %(book)d", format=old_book_format, book=book_id)
|
error_message = _(u"%(format)s format not found for book id: %(book)d", format=old_book_format, book=book_id)
|
||||||
log.error("convert_book_format: %s", error_message)
|
log.error("convert_book_format: %s", error_message)
|
||||||
|
@ -142,25 +142,27 @@ def check_send_to_kindle(entry):
|
||||||
"""
|
"""
|
||||||
if len(entry.data):
|
if len(entry.data):
|
||||||
bookformats = list()
|
bookformats = list()
|
||||||
if config.config_ebookconverter == 0:
|
if not config.config_converterpath:
|
||||||
# no converter - only for mobi and pdf formats
|
# no converter - only for mobi and pdf formats
|
||||||
for ele in iter(entry.data):
|
for ele in iter(entry.data):
|
||||||
if 'MOBI' in ele.format:
|
if ele.uncompressed_size < config.mail_size:
|
||||||
bookformats.append({'format': 'Mobi',
|
if 'MOBI' in ele.format:
|
||||||
'convert': 0,
|
bookformats.append({'format': 'Mobi',
|
||||||
'text': _('Send %(format)s to Kindle', format='Mobi')})
|
'convert': 0,
|
||||||
if 'PDF' in ele.format:
|
'text': _('Send %(format)s to Kindle', format='Mobi')})
|
||||||
bookformats.append({'format': 'Pdf',
|
if 'PDF' in ele.format:
|
||||||
'convert': 0,
|
bookformats.append({'format': 'Pdf',
|
||||||
'text': _('Send %(format)s to Kindle', format='Pdf')})
|
'convert': 0,
|
||||||
if 'AZW' in ele.format:
|
'text': _('Send %(format)s to Kindle', format='Pdf')})
|
||||||
bookformats.append({'format': 'Azw',
|
if 'AZW' in ele.format:
|
||||||
'convert': 0,
|
bookformats.append({'format': 'Azw',
|
||||||
'text': _('Send %(format)s to Kindle', format='Azw')})
|
'convert': 0,
|
||||||
|
'text': _('Send %(format)s to Kindle', format='Azw')})
|
||||||
else:
|
else:
|
||||||
formats = list()
|
formats = list()
|
||||||
for ele in iter(entry.data):
|
for ele in iter(entry.data):
|
||||||
formats.append(ele.format)
|
if ele.uncompressed_size < config.mail_size:
|
||||||
|
formats.append(ele.format)
|
||||||
if 'MOBI' in formats:
|
if 'MOBI' in formats:
|
||||||
bookformats.append({'format': 'Mobi',
|
bookformats.append({'format': 'Mobi',
|
||||||
'convert': 0,
|
'convert': 0,
|
||||||
|
@ -173,14 +175,13 @@ def check_send_to_kindle(entry):
|
||||||
bookformats.append({'format': 'Pdf',
|
bookformats.append({'format': 'Pdf',
|
||||||
'convert': 0,
|
'convert': 0,
|
||||||
'text': _('Send %(format)s to Kindle', format='Pdf')})
|
'text': _('Send %(format)s to Kindle', format='Pdf')})
|
||||||
if config.config_ebookconverter >= 1:
|
if config.config_converterpath:
|
||||||
if 'EPUB' in formats and not 'MOBI' in formats:
|
if 'EPUB' in formats and not 'MOBI' in formats:
|
||||||
bookformats.append({'format': 'Mobi',
|
bookformats.append({'format': 'Mobi',
|
||||||
'convert':1,
|
'convert':1,
|
||||||
'text': _('Convert %(orig)s to %(format)s and send to Kindle',
|
'text': _('Convert %(orig)s to %(format)s and send to Kindle',
|
||||||
orig='Epub',
|
orig='Epub',
|
||||||
format='Mobi')})
|
format='Mobi')})
|
||||||
if config.config_ebookconverter == 2:
|
|
||||||
if 'AZW3' in formats and not 'MOBI' in formats:
|
if 'AZW3' in formats and not 'MOBI' in formats:
|
||||||
bookformats.append({'format': 'Mobi',
|
bookformats.append({'format': 'Mobi',
|
||||||
'convert': 2,
|
'convert': 2,
|
||||||
|
@ -211,7 +212,7 @@ def check_read_formats(entry):
|
||||||
# 3: If Pdf file is existing, it's directly send to kindle email
|
# 3: If Pdf file is existing, it's directly send to kindle email
|
||||||
def send_mail(book_id, book_format, convert, kindle_mail, calibrepath, user_id):
|
def send_mail(book_id, book_format, convert, kindle_mail, calibrepath, user_id):
|
||||||
"""Send email with attachments"""
|
"""Send email with attachments"""
|
||||||
book = db.session.query(db.Books).filter(db.Books.id == book_id).first()
|
book = calibre_db.get_book(book_id)
|
||||||
|
|
||||||
if convert == 1:
|
if convert == 1:
|
||||||
# returns None if success, otherwise errormessage
|
# returns None if success, otherwise errormessage
|
||||||
|
@ -317,13 +318,13 @@ def delete_book_file(book, calibrepath, book_format=None):
|
||||||
return True, None
|
return True, None
|
||||||
else:
|
else:
|
||||||
log.error("Deleting book %s failed, book path not valid: %s", book.id, book.path)
|
log.error("Deleting book %s failed, book path not valid: %s", book.id, book.path)
|
||||||
return False, _("Deleting book %(id)s failed, book path not valid: %(path)s",
|
return True, _("Deleting book %(id)s, book path not valid: %(path)s",
|
||||||
id=book.id,
|
id=book.id,
|
||||||
path=book.path)
|
path=book.path)
|
||||||
|
|
||||||
|
|
||||||
def update_dir_structure_file(book_id, calibrepath, first_author):
|
def update_dir_structure_file(book_id, calibrepath, first_author):
|
||||||
localbook = db.session.query(db.Books).filter(db.Books.id == book_id).first()
|
localbook = calibre_db.get_book(book_id)
|
||||||
path = os.path.join(calibrepath, localbook.path)
|
path = os.path.join(calibrepath, localbook.path)
|
||||||
|
|
||||||
authordir = localbook.path.split('/')[0]
|
authordir = localbook.path.split('/')[0]
|
||||||
|
@ -382,7 +383,7 @@ def update_dir_structure_file(book_id, calibrepath, first_author):
|
||||||
|
|
||||||
def update_dir_structure_gdrive(book_id, first_author):
|
def update_dir_structure_gdrive(book_id, first_author):
|
||||||
error = False
|
error = False
|
||||||
book = db.session.query(db.Books).filter(db.Books.id == book_id).first()
|
book = calibre_db.get_book(book_id)
|
||||||
path = book.path
|
path = book.path
|
||||||
|
|
||||||
authordir = book.path.split('/')[0]
|
authordir = book.path.split('/')[0]
|
||||||
|
@ -493,18 +494,17 @@ def get_cover_on_failure(use_generic_cover):
|
||||||
|
|
||||||
|
|
||||||
def get_book_cover(book_id):
|
def get_book_cover(book_id):
|
||||||
book = db.session.query(db.Books).filter(db.Books.id == book_id).filter(common_filters(allow_show_archived=True)).first()
|
book = calibre_db.get_filtered_book(book_id, allow_show_archived=True)
|
||||||
return get_book_cover_internal(book, use_generic_cover_on_failure=True)
|
return get_book_cover_internal(book, use_generic_cover_on_failure=True)
|
||||||
|
|
||||||
|
|
||||||
def get_book_cover_with_uuid(book_uuid,
|
def get_book_cover_with_uuid(book_uuid,
|
||||||
use_generic_cover_on_failure=True):
|
use_generic_cover_on_failure=True):
|
||||||
book = db.session.query(db.Books).filter(db.Books.uuid == book_uuid).first()
|
book = calibre_db.get_book_by_uuid(book_uuid)
|
||||||
return get_book_cover_internal(book, use_generic_cover_on_failure)
|
return get_book_cover_internal(book, use_generic_cover_on_failure)
|
||||||
|
|
||||||
|
|
||||||
def get_book_cover_internal(book,
|
def get_book_cover_internal(book, use_generic_cover_on_failure):
|
||||||
use_generic_cover_on_failure):
|
|
||||||
if book and book.has_cover:
|
if book and book.has_cover:
|
||||||
if config.config_use_google_drive:
|
if config.config_use_google_drive:
|
||||||
try:
|
try:
|
||||||
|
@ -594,7 +594,8 @@ def save_cover(img, book_path):
|
||||||
return save_cover_from_filestorage(os.path.join(config.config_calibre_dir, book_path), "cover.jpg", img)
|
return save_cover_from_filestorage(os.path.join(config.config_calibre_dir, book_path), "cover.jpg", img)
|
||||||
|
|
||||||
|
|
||||||
def do_download_file(book, book_format, data, headers):
|
|
||||||
|
def do_download_file(book, book_format, client, data, headers):
|
||||||
if config.config_use_google_drive:
|
if config.config_use_google_drive:
|
||||||
startTime = time.time()
|
startTime = time.time()
|
||||||
df = gd.getFileFromEbooksFolder(book.path, data.name + "." + book_format)
|
df = gd.getFileFromEbooksFolder(book.path, data.name + "." + book_format)
|
||||||
|
@ -608,6 +609,10 @@ def do_download_file(book, book_format, data, headers):
|
||||||
if not os.path.isfile(os.path.join(filename, data.name + "." + book_format)):
|
if not os.path.isfile(os.path.join(filename, data.name + "." + book_format)):
|
||||||
# ToDo: improve error handling
|
# ToDo: improve error handling
|
||||||
log.error('File not found: %s', os.path.join(filename, data.name + "." + book_format))
|
log.error('File not found: %s', os.path.join(filename, data.name + "." + book_format))
|
||||||
|
|
||||||
|
if client == "kobo" and book_format == "kepub":
|
||||||
|
headers["Content-Disposition"] = headers["Content-Disposition"].replace(".kepub", ".kepub.epub")
|
||||||
|
|
||||||
response = make_response(send_from_directory(filename, data.name + "." + book_format))
|
response = make_response(send_from_directory(filename, data.name + "." + book_format))
|
||||||
# ToDo Check headers parameter
|
# ToDo Check headers parameter
|
||||||
for element in headers:
|
for element in headers:
|
||||||
|
@ -629,11 +634,12 @@ def check_unrar(unrarLocation):
|
||||||
unrarLocation = unrarLocation.encode(sys.getfilesystemencoding())
|
unrarLocation = unrarLocation.encode(sys.getfilesystemencoding())
|
||||||
unrarLocation = [unrarLocation]
|
unrarLocation = [unrarLocation]
|
||||||
for lines in process_wait(unrarLocation):
|
for lines in process_wait(unrarLocation):
|
||||||
value = re.search('UNRAR (.*) freeware', lines)
|
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)
|
||||||
except OSError as err:
|
break
|
||||||
|
except (OSError, UnicodeDecodeError) as err:
|
||||||
log.exception(err)
|
log.exception(err)
|
||||||
return _('Error excecuting UnRar')
|
return _('Error excecuting UnRar')
|
||||||
|
|
||||||
|
@ -719,66 +725,12 @@ def render_task_status(tasklist):
|
||||||
return renderedtasklist
|
return renderedtasklist
|
||||||
|
|
||||||
|
|
||||||
# Language and content filters for displaying in the UI
|
|
||||||
def common_filters(allow_show_archived=False):
|
|
||||||
if not allow_show_archived:
|
|
||||||
archived_books = (
|
|
||||||
ub.session.query(ub.ArchivedBook)
|
|
||||||
.filter(ub.ArchivedBook.user_id == int(current_user.id))
|
|
||||||
.filter(ub.ArchivedBook.is_archived == True)
|
|
||||||
.all()
|
|
||||||
)
|
|
||||||
archived_book_ids = [archived_book.book_id for archived_book in archived_books]
|
|
||||||
archived_filter = db.Books.id.notin_(archived_book_ids)
|
|
||||||
else:
|
|
||||||
archived_filter = true()
|
|
||||||
|
|
||||||
if current_user.filter_language() != "all":
|
|
||||||
lang_filter = db.Books.languages.any(db.Languages.lang_code == current_user.filter_language())
|
|
||||||
else:
|
|
||||||
lang_filter = true()
|
|
||||||
negtags_list = current_user.list_denied_tags()
|
|
||||||
postags_list = current_user.list_allowed_tags()
|
|
||||||
neg_content_tags_filter = false() if negtags_list == [''] else db.Books.tags.any(db.Tags.name.in_(negtags_list))
|
|
||||||
pos_content_tags_filter = true() if postags_list == [''] else db.Books.tags.any(db.Tags.name.in_(postags_list))
|
|
||||||
if config.config_restricted_column:
|
|
||||||
pos_cc_list = current_user.allowed_column_value.split(',')
|
|
||||||
pos_content_cc_filter = true() if pos_cc_list == [''] else \
|
|
||||||
getattr(db.Books, 'custom_column_' + str(config.config_restricted_column)).\
|
|
||||||
any(db.cc_classes[config.config_restricted_column].value.in_(pos_cc_list))
|
|
||||||
neg_cc_list = current_user.denied_column_value.split(',')
|
|
||||||
neg_content_cc_filter = false() if neg_cc_list == [''] else \
|
|
||||||
getattr(db.Books, 'custom_column_' + str(config.config_restricted_column)).\
|
|
||||||
any(db.cc_classes[config.config_restricted_column].value.in_(neg_cc_list))
|
|
||||||
else:
|
|
||||||
pos_content_cc_filter = true()
|
|
||||||
neg_content_cc_filter = false()
|
|
||||||
return and_(lang_filter, pos_content_tags_filter, ~neg_content_tags_filter,
|
|
||||||
pos_content_cc_filter, ~neg_content_cc_filter, archived_filter)
|
|
||||||
|
|
||||||
|
|
||||||
def tags_filters():
|
def tags_filters():
|
||||||
negtags_list = current_user.list_denied_tags()
|
negtags_list = current_user.list_denied_tags()
|
||||||
postags_list = current_user.list_allowed_tags()
|
postags_list = current_user.list_allowed_tags()
|
||||||
neg_content_tags_filter = false() if negtags_list == [''] else db.Tags.name.in_(negtags_list)
|
neg_content_tags_filter = false() if negtags_list == [''] else db.Tags.name.in_(negtags_list)
|
||||||
pos_content_tags_filter = true() if postags_list == [''] else db.Tags.name.in_(postags_list)
|
pos_content_tags_filter = true() if postags_list == [''] else db.Tags.name.in_(postags_list)
|
||||||
return and_(pos_content_tags_filter, ~neg_content_tags_filter)
|
return and_(pos_content_tags_filter, ~neg_content_tags_filter)
|
||||||
# return ~(false()) if postags_list == [''] else db.Tags.in_(postags_list)
|
|
||||||
|
|
||||||
|
|
||||||
# Creates for all stored languages a translated speaking name in the array for the UI
|
|
||||||
def speaking_language(languages=None):
|
|
||||||
if not languages:
|
|
||||||
languages = db.session.query(db.Languages).join(db.books_languages_link).join(db.Books)\
|
|
||||||
.filter(common_filters())\
|
|
||||||
.group_by(text('books_languages_link.lang_code')).all()
|
|
||||||
for lang in languages:
|
|
||||||
try:
|
|
||||||
cur_l = LC.parse(lang.lang_code)
|
|
||||||
lang.name = cur_l.get_language_name(get_locale())
|
|
||||||
except UnknownLocaleError:
|
|
||||||
lang.name = _(isoLanguages.get(part3=lang.lang_code).name)
|
|
||||||
return languages
|
|
||||||
|
|
||||||
|
|
||||||
# checks if domain is in database (including wildcards)
|
# checks if domain is in database (including wildcards)
|
||||||
|
@ -795,93 +747,28 @@ def check_valid_domain(domain_text):
|
||||||
return not len(result)
|
return not len(result)
|
||||||
|
|
||||||
|
|
||||||
# Orders all Authors in the list according to authors sort
|
def get_cc_columns(filter_config_custom_read=False):
|
||||||
def order_authors(entry):
|
tmpcc = calibre_db.session.query(db.Custom_Columns)\
|
||||||
sort_authors = entry.author_sort.split('&')
|
.filter(db.Custom_Columns.datatype.notin_(db.cc_exceptions)).all()
|
||||||
authors_ordered = list()
|
cc = []
|
||||||
error = False
|
r = None
|
||||||
for auth in sort_authors:
|
|
||||||
# ToDo: How to handle not found authorname
|
|
||||||
result = db.session.query(db.Authors).filter(db.Authors.sort == auth.lstrip().strip()).first()
|
|
||||||
if not result:
|
|
||||||
error = True
|
|
||||||
break
|
|
||||||
authors_ordered.append(result)
|
|
||||||
if not error:
|
|
||||||
entry.authors = authors_ordered
|
|
||||||
return entry
|
|
||||||
|
|
||||||
|
|
||||||
# Fill indexpage with all requested data from database
|
|
||||||
def fill_indexpage(page, database, db_filter, order, *join):
|
|
||||||
return fill_indexpage_with_archived_books(page, database, db_filter, order, False, *join)
|
|
||||||
|
|
||||||
|
|
||||||
def fill_indexpage_with_archived_books(page, database, db_filter, order, allow_show_archived, *join):
|
|
||||||
if current_user.show_detail_random():
|
|
||||||
randm = db.session.query(db.Books).filter(common_filters(allow_show_archived))\
|
|
||||||
.order_by(func.random()).limit(config.config_random_books)
|
|
||||||
else:
|
|
||||||
randm = false()
|
|
||||||
off = int(int(config.config_books_per_page) * (page - 1))
|
|
||||||
query = db.session.query(database).join(*join, isouter=True).\
|
|
||||||
filter(db_filter).\
|
|
||||||
filter(common_filters(allow_show_archived))
|
|
||||||
pagination = Pagination(page, config.config_books_per_page,
|
|
||||||
len(query.all()))
|
|
||||||
entries = query.order_by(*order).offset(off).limit(config.config_books_per_page).all()
|
|
||||||
for book in entries:
|
|
||||||
book = order_authors(book)
|
|
||||||
return entries, randm, pagination
|
|
||||||
|
|
||||||
|
|
||||||
def get_typeahead(database, query, replace=('', ''), tag_filter=true()):
|
|
||||||
query = query or ''
|
|
||||||
db.session.connection().connection.connection.create_function("lower", 1, lcase)
|
|
||||||
entries = db.session.query(database).filter(tag_filter).\
|
|
||||||
filter(func.lower(database.name).ilike("%" + query + "%")).all()
|
|
||||||
json_dumps = json.dumps([dict(name=r.name.replace(*replace)) for r in entries])
|
|
||||||
return json_dumps
|
|
||||||
|
|
||||||
|
|
||||||
# read search results from calibre-database and return it (function is used for feed and simple search
|
|
||||||
def get_search_results(term):
|
|
||||||
db.session.connection().connection.connection.create_function("lower", 1, lcase)
|
|
||||||
q = list()
|
|
||||||
authorterms = re.split("[, ]+", term)
|
|
||||||
for authorterm in authorterms:
|
|
||||||
q.append(db.Books.authors.any(func.lower(db.Authors.name).ilike("%" + authorterm + "%")))
|
|
||||||
|
|
||||||
db.Books.authors.any(func.lower(db.Authors.name).ilike("%" + term + "%"))
|
|
||||||
|
|
||||||
return db.session.query(db.Books).filter(common_filters()).filter(
|
|
||||||
or_(db.Books.tags.any(func.lower(db.Tags.name).ilike("%" + term + "%")),
|
|
||||||
db.Books.series.any(func.lower(db.Series.name).ilike("%" + term + "%")),
|
|
||||||
db.Books.authors.any(and_(*q)),
|
|
||||||
db.Books.publishers.any(func.lower(db.Publishers.name).ilike("%" + term + "%")),
|
|
||||||
func.lower(db.Books.title).ilike("%" + term + "%")
|
|
||||||
)).order_by(db.Books.sort).all()
|
|
||||||
|
|
||||||
|
|
||||||
def get_cc_columns():
|
|
||||||
tmpcc = db.session.query(db.Custom_Columns).filter(db.Custom_Columns.datatype.notin_(db.cc_exceptions)).all()
|
|
||||||
if config.config_columns_to_ignore:
|
if config.config_columns_to_ignore:
|
||||||
cc = []
|
r = re.compile(config.config_columns_to_ignore)
|
||||||
for col in tmpcc:
|
|
||||||
r = re.compile(config.config_columns_to_ignore)
|
for col in tmpcc:
|
||||||
if not r.match(col.name):
|
if filter_config_custom_read and config.config_read_column and config.config_read_column == col.id:
|
||||||
cc.append(col)
|
continue
|
||||||
else:
|
if r and r.match(col.name):
|
||||||
cc = tmpcc
|
continue
|
||||||
|
cc.append(col)
|
||||||
|
|
||||||
return cc
|
return cc
|
||||||
|
|
||||||
|
def get_download_link(book_id, book_format, client):
|
||||||
def get_download_link(book_id, book_format):
|
|
||||||
book_format = book_format.split(".")[0]
|
book_format = book_format.split(".")[0]
|
||||||
book = db.session.query(db.Books).filter(db.Books.id == book_id).filter(common_filters()).first()
|
book = calibre_db.get_filtered_book(book_id)
|
||||||
if book:
|
if book:
|
||||||
data1 = db.session.query(db.Data).filter(db.Data.book == book.id)\
|
data1 = data = calibre_db.get_book_format(book.id, book_format.upper())
|
||||||
.filter(db.Data.format == book_format.upper()).first()
|
|
||||||
else:
|
else:
|
||||||
abort(404)
|
abort(404)
|
||||||
if data1:
|
if data1:
|
||||||
|
@ -896,28 +783,6 @@ def get_download_link(book_id, book_format):
|
||||||
headers["Content-Type"] = mimetypes.types_map.get('.' + book_format, "application/octet-stream")
|
headers["Content-Type"] = mimetypes.types_map.get('.' + book_format, "application/octet-stream")
|
||||||
headers["Content-Disposition"] = "attachment; filename=%s.%s; filename*=UTF-8''%s.%s" % (
|
headers["Content-Disposition"] = "attachment; filename=%s.%s; filename*=UTF-8''%s.%s" % (
|
||||||
quote(file_name.encode('utf-8')), book_format, quote(file_name.encode('utf-8')), book_format)
|
quote(file_name.encode('utf-8')), book_format, quote(file_name.encode('utf-8')), book_format)
|
||||||
return do_download_file(book, book_format, data1, headers)
|
return do_download_file(book, book_format, client, data1, headers)
|
||||||
else:
|
else:
|
||||||
abort(404)
|
abort(404)
|
||||||
|
|
||||||
|
|
||||||
def check_exists_book(authr, title):
|
|
||||||
db.session.connection().connection.connection.create_function("lower", 1, lcase)
|
|
||||||
q = list()
|
|
||||||
authorterms = re.split(r'\s*&\s*', authr)
|
|
||||||
for authorterm in authorterms:
|
|
||||||
q.append(db.Books.authors.any(func.lower(db.Authors.name).ilike("%" + authorterm + "%")))
|
|
||||||
|
|
||||||
return db.session.query(db.Books).filter(
|
|
||||||
and_(db.Books.authors.any(and_(*q)),
|
|
||||||
func.lower(db.Books.title).ilike("%" + title + "%")
|
|
||||||
)).first()
|
|
||||||
|
|
||||||
############### Database Helper functions
|
|
||||||
|
|
||||||
|
|
||||||
def lcase(s):
|
|
||||||
try:
|
|
||||||
return unidecode.unidecode(s.lower())
|
|
||||||
except Exception as e:
|
|
||||||
log.exception(e)
|
|
||||||
|
|
|
@ -57,12 +57,12 @@ def get_language_name(locale, lang_code):
|
||||||
|
|
||||||
def get_language_codes(locale, language_names, remainder=None):
|
def get_language_codes(locale, language_names, remainder=None):
|
||||||
language_names = set(x.strip().lower() for x in language_names if x)
|
language_names = set(x.strip().lower() for x in language_names if x)
|
||||||
|
languages = list()
|
||||||
for k, v in get_language_names(locale).items():
|
for k, v in get_language_names(locale).items():
|
||||||
v = v.lower()
|
v = v.lower()
|
||||||
if v in language_names:
|
if v in language_names:
|
||||||
|
languages.append(k)
|
||||||
language_names.remove(v)
|
language_names.remove(v)
|
||||||
yield k
|
|
||||||
|
|
||||||
if remainder is not None:
|
if remainder is not None:
|
||||||
remainder.extend(language_names)
|
remainder.extend(language_names)
|
||||||
|
return languages
|
||||||
|
|
|
@ -111,10 +111,3 @@ def timestamptodate(date, fmt=None):
|
||||||
@jinjia.app_template_filter('yesno')
|
@jinjia.app_template_filter('yesno')
|
||||||
def yesno(value, yes, no):
|
def yesno(value, yes, no):
|
||||||
return yes if value else no
|
return yes if value else no
|
||||||
|
|
||||||
|
|
||||||
'''@jinjia.app_template_filter('canread')
|
|
||||||
def canread(ext):
|
|
||||||
if isinstance(ext, db.Data):
|
|
||||||
ext = ext.format
|
|
||||||
return ext.lower() in EXTENSIONS_READER'''
|
|
||||||
|
|
20
cps/kobo.py
20
cps/kobo.py
|
@ -48,7 +48,7 @@ from sqlalchemy.sql.expression import and_, or_
|
||||||
from sqlalchemy.exc import StatementError
|
from sqlalchemy.exc import StatementError
|
||||||
import requests
|
import requests
|
||||||
|
|
||||||
from . import config, logger, kobo_auth, db, helper, shelf as shelf_lib, ub
|
from . import config, logger, kobo_auth, db, calibre_db, helper, shelf as shelf_lib, ub
|
||||||
from .services import SyncToken as SyncToken
|
from .services import SyncToken as SyncToken
|
||||||
from .web import download_required
|
from .web import download_required
|
||||||
from .kobo_auth import requires_kobo_auth
|
from .kobo_auth import requires_kobo_auth
|
||||||
|
@ -143,7 +143,7 @@ def HandleSyncRequest():
|
||||||
|
|
||||||
# We reload the book database so that the user get's a fresh view of the library
|
# We reload the book database so that the user get's a fresh view of the library
|
||||||
# in case of external changes (e.g: adding a book through Calibre).
|
# in case of external changes (e.g: adding a book through Calibre).
|
||||||
db.reconnect_db(config)
|
calibre_db.reconnect_db(config, ub.app_DB_path)
|
||||||
|
|
||||||
archived_books = (
|
archived_books = (
|
||||||
ub.session.query(ub.ArchivedBook)
|
ub.session.query(ub.ArchivedBook)
|
||||||
|
@ -170,7 +170,7 @@ def HandleSyncRequest():
|
||||||
# It looks like it's treating the db.Books.last_modified field as a string and may fail
|
# It looks like it's treating the db.Books.last_modified field as a string and may fail
|
||||||
# the comparison because of the +00:00 suffix.
|
# the comparison because of the +00:00 suffix.
|
||||||
changed_entries = (
|
changed_entries = (
|
||||||
db.session.query(db.Books)
|
calibre_db.session.query(db.Books)
|
||||||
.join(db.Data)
|
.join(db.Data)
|
||||||
.filter(or_(func.datetime(db.Books.last_modified) > sync_token.books_last_modified,
|
.filter(or_(func.datetime(db.Books.last_modified) > sync_token.books_last_modified,
|
||||||
db.Books.id.in_(recently_restored_or_archived_books)))
|
db.Books.id.in_(recently_restored_or_archived_books)))
|
||||||
|
@ -207,7 +207,7 @@ def HandleSyncRequest():
|
||||||
ub.KoboReadingState.user_id == current_user.id,
|
ub.KoboReadingState.user_id == current_user.id,
|
||||||
ub.KoboReadingState.book_id.notin_(reading_states_in_new_entitlements))))
|
ub.KoboReadingState.book_id.notin_(reading_states_in_new_entitlements))))
|
||||||
for kobo_reading_state in changed_reading_states.all():
|
for kobo_reading_state in changed_reading_states.all():
|
||||||
book = db.session.query(db.Books).filter(db.Books.id == kobo_reading_state.book_id).one_or_none()
|
book = calibre_db.session.query(db.Books).filter(db.Books.id == kobo_reading_state.book_id).one_or_none()
|
||||||
if book:
|
if book:
|
||||||
sync_results.append({
|
sync_results.append({
|
||||||
"ChangedReadingState": {
|
"ChangedReadingState": {
|
||||||
|
@ -256,7 +256,7 @@ def HandleMetadataRequest(book_uuid):
|
||||||
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 server port')
|
log.debug('Kobo: Received unproxied request, changed request port to server port')
|
||||||
log.info("Kobo library metadata request received for book %s" % book_uuid)
|
log.info("Kobo library metadata request received for book %s" % book_uuid)
|
||||||
book = db.session.query(db.Books).filter(db.Books.uuid == book_uuid).first()
|
book = calibre_db.get_book_by_uuid(book_uuid)
|
||||||
if not book or not book.data:
|
if not book or not book.data:
|
||||||
log.info(u"Book %s not found in database", book_uuid)
|
log.info(u"Book %s not found in database", book_uuid)
|
||||||
return redirect_or_proxy_request()
|
return redirect_or_proxy_request()
|
||||||
|
@ -474,7 +474,7 @@ def add_items_to_shelf(items, shelf):
|
||||||
items_unknown_to_calibre.append(item)
|
items_unknown_to_calibre.append(item)
|
||||||
continue
|
continue
|
||||||
|
|
||||||
book = db.session.query(db.Books).filter(db.Books.uuid == item["RevisionId"]).one_or_none()
|
book = calibre_db.get_book_by_uuid(item["RevisionId"])
|
||||||
if not book:
|
if not book:
|
||||||
items_unknown_to_calibre.append(item)
|
items_unknown_to_calibre.append(item)
|
||||||
continue
|
continue
|
||||||
|
@ -545,7 +545,7 @@ def HandleTagRemoveItem(tag_id):
|
||||||
items_unknown_to_calibre.append(item)
|
items_unknown_to_calibre.append(item)
|
||||||
continue
|
continue
|
||||||
|
|
||||||
book = db.session.query(db.Books).filter(db.Books.uuid == item["RevisionId"]).one_or_none()
|
book = calibre_db.get_book_by_uuid(item["RevisionId"])
|
||||||
if not book:
|
if not book:
|
||||||
items_unknown_to_calibre.append(item)
|
items_unknown_to_calibre.append(item)
|
||||||
continue
|
continue
|
||||||
|
@ -613,7 +613,7 @@ def create_kobo_tag(shelf):
|
||||||
"Type": "UserTag"
|
"Type": "UserTag"
|
||||||
}
|
}
|
||||||
for book_shelf in shelf.books:
|
for book_shelf in shelf.books:
|
||||||
book = db.session.query(db.Books).filter(db.Books.id == book_shelf.book_id).one_or_none()
|
book = calibre_db.get_book(book_shelf.book_id)
|
||||||
if not book:
|
if not book:
|
||||||
log.info(u"Book (id: %s) in BookShelf (id: %s) not found in book database", book_shelf.book_id, shelf.id)
|
log.info(u"Book (id: %s) in BookShelf (id: %s) not found in book database", book_shelf.book_id, shelf.id)
|
||||||
continue
|
continue
|
||||||
|
@ -629,7 +629,7 @@ def create_kobo_tag(shelf):
|
||||||
@kobo.route("/v1/library/<book_uuid>/state", methods=["GET", "PUT"])
|
@kobo.route("/v1/library/<book_uuid>/state", methods=["GET", "PUT"])
|
||||||
@login_required
|
@login_required
|
||||||
def HandleStateRequest(book_uuid):
|
def HandleStateRequest(book_uuid):
|
||||||
book = db.session.query(db.Books).filter(db.Books.uuid == book_uuid).first()
|
book = calibre_db.get_book_by_uuid(book_uuid)
|
||||||
if not book or not book.data:
|
if not book or not book.data:
|
||||||
log.info(u"Book %s not found in database", book_uuid)
|
log.info(u"Book %s not found in database", book_uuid)
|
||||||
return redirect_or_proxy_request()
|
return redirect_or_proxy_request()
|
||||||
|
@ -804,7 +804,7 @@ def TopLevelEndpoint():
|
||||||
@login_required
|
@login_required
|
||||||
def HandleBookDeletionRequest(book_uuid):
|
def HandleBookDeletionRequest(book_uuid):
|
||||||
log.info("Kobo book deletion request received for book %s" % book_uuid)
|
log.info("Kobo book deletion request received for book %s" % book_uuid)
|
||||||
book = db.session.query(db.Books).filter(db.Books.uuid == book_uuid).first()
|
book = calibre_db.get_book_by_uuid(book_uuid)
|
||||||
if not book:
|
if not book:
|
||||||
log.info(u"Book %s not found in database", book_uuid)
|
log.info(u"Book %s not found in database", book_uuid)
|
||||||
return redirect_or_proxy_request()
|
return redirect_or_proxy_request()
|
||||||
|
|
|
@ -82,7 +82,6 @@ def _absolute_log_file(log_file, default_log_file):
|
||||||
if not os.path.dirname(log_file):
|
if not os.path.dirname(log_file):
|
||||||
log_file = os.path.join(_CONFIG_DIR, log_file)
|
log_file = os.path.join(_CONFIG_DIR, log_file)
|
||||||
return os.path.abspath(log_file)
|
return os.path.abspath(log_file)
|
||||||
|
|
||||||
return default_log_file
|
return default_log_file
|
||||||
|
|
||||||
|
|
||||||
|
@ -115,7 +114,7 @@ def setup(log_file, log_level=None):
|
||||||
if previous_handler:
|
if previous_handler:
|
||||||
# if the log_file has not changed, don't create a new handler
|
# if the log_file has not changed, don't create a new handler
|
||||||
if getattr(previous_handler, 'baseFilename', None) == log_file:
|
if getattr(previous_handler, 'baseFilename', None) == log_file:
|
||||||
return
|
return "" if log_file == DEFAULT_LOG_FILE else log_file
|
||||||
logging.debug("logging to %s level %s", log_file, r.level)
|
logging.debug("logging to %s level %s", log_file, r.level)
|
||||||
|
|
||||||
if log_file == LOG_TO_STDERR or log_file == LOG_TO_STDOUT:
|
if log_file == LOG_TO_STDERR or log_file == LOG_TO_STDOUT:
|
||||||
|
@ -132,12 +131,14 @@ def setup(log_file, log_level=None):
|
||||||
if log_file == DEFAULT_LOG_FILE:
|
if log_file == DEFAULT_LOG_FILE:
|
||||||
raise
|
raise
|
||||||
file_handler = RotatingFileHandler(DEFAULT_LOG_FILE, maxBytes=50000, backupCount=2)
|
file_handler = RotatingFileHandler(DEFAULT_LOG_FILE, maxBytes=50000, backupCount=2)
|
||||||
|
log_file = ""
|
||||||
file_handler.setFormatter(FORMATTER)
|
file_handler.setFormatter(FORMATTER)
|
||||||
|
|
||||||
for h in r.handlers:
|
for h in r.handlers:
|
||||||
r.removeHandler(h)
|
r.removeHandler(h)
|
||||||
h.close()
|
h.close()
|
||||||
r.addHandler(file_handler)
|
r.addHandler(file_handler)
|
||||||
|
return "" if log_file == DEFAULT_LOG_FILE else log_file
|
||||||
|
|
||||||
|
|
||||||
def create_access_log(log_file, log_name, formatter):
|
def create_access_log(log_file, log_name, formatter):
|
||||||
|
@ -150,11 +151,18 @@ def create_access_log(log_file, log_name, formatter):
|
||||||
access_log = logging.getLogger(log_name)
|
access_log = logging.getLogger(log_name)
|
||||||
access_log.propagate = False
|
access_log.propagate = False
|
||||||
access_log.setLevel(logging.INFO)
|
access_log.setLevel(logging.INFO)
|
||||||
|
try:
|
||||||
|
file_handler = RotatingFileHandler(log_file, maxBytes=50000, backupCount=2)
|
||||||
|
except IOError:
|
||||||
|
if log_file == DEFAULT_ACCESS_LOG:
|
||||||
|
raise
|
||||||
|
file_handler = RotatingFileHandler(DEFAULT_ACCESS_LOG, maxBytes=50000, backupCount=2)
|
||||||
|
log_file = ""
|
||||||
|
|
||||||
file_handler = RotatingFileHandler(log_file, maxBytes=50000, backupCount=2)
|
|
||||||
file_handler.setFormatter(formatter)
|
file_handler.setFormatter(formatter)
|
||||||
access_log.addHandler(file_handler)
|
access_log.addHandler(file_handler)
|
||||||
return access_log
|
return access_log, \
|
||||||
|
"" if _absolute_log_file(log_file, DEFAULT_ACCESS_LOG) == DEFAULT_ACCESS_LOG else log_file
|
||||||
|
|
||||||
|
|
||||||
# Enable logging of smtp lib debug output
|
# Enable logging of smtp lib debug output
|
||||||
|
|
153
cps/opds.py
153
cps/opds.py
|
@ -30,10 +30,10 @@ from flask_login import current_user
|
||||||
from sqlalchemy.sql.expression import func, text, or_, and_
|
from sqlalchemy.sql.expression import func, text, or_, and_
|
||||||
from werkzeug.security import check_password_hash
|
from werkzeug.security import check_password_hash
|
||||||
|
|
||||||
from . import constants, logger, config, db, ub, services, get_locale, isoLanguages
|
from . import constants, logger, config, db, calibre_db, ub, services, get_locale, isoLanguages
|
||||||
from .helper import fill_indexpage, get_download_link, get_book_cover, speaking_language
|
from .helper import get_download_link, get_book_cover
|
||||||
from .pagination import Pagination
|
from .pagination import Pagination
|
||||||
from .web import common_filters, get_search_results, render_read_books, download_required
|
from .web import render_read_books, download_required
|
||||||
from flask_babel import gettext as _
|
from flask_babel import gettext as _
|
||||||
from babel import Locale as LC
|
from babel import Locale as LC
|
||||||
from babel.core import UnknownLocaleError
|
from babel.core import UnknownLocaleError
|
||||||
|
@ -100,15 +100,15 @@ def feed_normal_search():
|
||||||
@requires_basic_auth_if_no_ano
|
@requires_basic_auth_if_no_ano
|
||||||
def feed_new():
|
def feed_new():
|
||||||
off = request.args.get("offset") or 0
|
off = request.args.get("offset") or 0
|
||||||
entries, __, pagination = fill_indexpage((int(off) / (int(config.config_books_per_page)) + 1),
|
entries, __, pagination = calibre_db.fill_indexpage((int(off) / (int(config.config_books_per_page)) + 1),
|
||||||
db.Books, True, [db.Books.timestamp.desc()])
|
db.Books, True, [db.Books.timestamp.desc()])
|
||||||
return render_xml_template('feed.xml', entries=entries, pagination=pagination)
|
return render_xml_template('feed.xml', entries=entries, pagination=pagination)
|
||||||
|
|
||||||
|
|
||||||
@opds.route("/opds/discover")
|
@opds.route("/opds/discover")
|
||||||
@requires_basic_auth_if_no_ano
|
@requires_basic_auth_if_no_ano
|
||||||
def feed_discover():
|
def feed_discover():
|
||||||
entries = db.session.query(db.Books).filter(common_filters()).order_by(func.random())\
|
entries = calibre_db.session.query(db.Books).filter(calibre_db.common_filters()).order_by(func.random())\
|
||||||
.limit(config.config_books_per_page)
|
.limit(config.config_books_per_page)
|
||||||
pagination = Pagination(1, config.config_books_per_page, int(config.config_books_per_page))
|
pagination = Pagination(1, config.config_books_per_page, int(config.config_books_per_page))
|
||||||
return render_xml_template('feed.xml', entries=entries, pagination=pagination)
|
return render_xml_template('feed.xml', entries=entries, pagination=pagination)
|
||||||
|
@ -118,9 +118,9 @@ def feed_discover():
|
||||||
@requires_basic_auth_if_no_ano
|
@requires_basic_auth_if_no_ano
|
||||||
def feed_best_rated():
|
def feed_best_rated():
|
||||||
off = request.args.get("offset") or 0
|
off = request.args.get("offset") or 0
|
||||||
entries, __, pagination = fill_indexpage((int(off) / (int(config.config_books_per_page)) + 1),
|
entries, __, pagination = calibre_db.fill_indexpage((int(off) / (int(config.config_books_per_page)) + 1),
|
||||||
db.Books, db.Books.ratings.any(db.Ratings.rating > 9),
|
db.Books, db.Books.ratings.any(db.Ratings.rating > 9),
|
||||||
[db.Books.timestamp.desc()])
|
[db.Books.timestamp.desc()])
|
||||||
return render_xml_template('feed.xml', entries=entries, pagination=pagination)
|
return render_xml_template('feed.xml', entries=entries, pagination=pagination)
|
||||||
|
|
||||||
|
|
||||||
|
@ -133,16 +133,13 @@ def feed_hot():
|
||||||
hot_books = all_books.offset(off).limit(config.config_books_per_page)
|
hot_books = all_books.offset(off).limit(config.config_books_per_page)
|
||||||
entries = list()
|
entries = list()
|
||||||
for book in hot_books:
|
for book in hot_books:
|
||||||
downloadBook = db.session.query(db.Books).filter(db.Books.id == book.Downloads.book_id).first()
|
downloadBook = calibre_db.get_book(book.Downloads.book_id)
|
||||||
if downloadBook:
|
if downloadBook:
|
||||||
entries.append(
|
entries.append(
|
||||||
db.session.query(db.Books).filter(common_filters())
|
calibre_db.get_filtered_book(book.Downloads.book_id)
|
||||||
.filter(db.Books.id == book.Downloads.book_id).first()
|
|
||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
ub.delete_download(book.Downloads.book_id)
|
ub.delete_download(book.Downloads.book_id)
|
||||||
# ub.session.query(ub.Downloads).filter(book.Downloads.book_id == ub.Downloads.book_id).delete()
|
|
||||||
# ub.session.commit()
|
|
||||||
numBooks = entries.__len__()
|
numBooks = entries.__len__()
|
||||||
pagination = Pagination((int(off) / (int(config.config_books_per_page)) + 1),
|
pagination = Pagination((int(off) / (int(config.config_books_per_page)) + 1),
|
||||||
config.config_books_per_page, numBooks)
|
config.config_books_per_page, numBooks)
|
||||||
|
@ -153,11 +150,13 @@ def feed_hot():
|
||||||
@requires_basic_auth_if_no_ano
|
@requires_basic_auth_if_no_ano
|
||||||
def feed_authorindex():
|
def feed_authorindex():
|
||||||
off = request.args.get("offset") or 0
|
off = request.args.get("offset") or 0
|
||||||
entries = db.session.query(db.Authors).join(db.books_authors_link).join(db.Books).filter(common_filters())\
|
entries = calibre_db.session.query(db.Authors).join(db.books_authors_link).join(db.Books)\
|
||||||
.group_by(text('books_authors_link.author')).order_by(db.Authors.sort).limit(config.config_books_per_page)\
|
.filter(calibre_db.common_filters())\
|
||||||
|
.group_by(text('books_authors_link.author'))\
|
||||||
|
.order_by(db.Authors.sort).limit(config.config_books_per_page)\
|
||||||
.offset(off)
|
.offset(off)
|
||||||
pagination = Pagination((int(off) / (int(config.config_books_per_page)) + 1), config.config_books_per_page,
|
pagination = Pagination((int(off) / (int(config.config_books_per_page)) + 1), config.config_books_per_page,
|
||||||
len(db.session.query(db.Authors).all()))
|
len(calibre_db.session.query(db.Authors).all()))
|
||||||
return render_xml_template('feed.xml', listelements=entries, folder='opds.feed_author', pagination=pagination)
|
return render_xml_template('feed.xml', listelements=entries, folder='opds.feed_author', pagination=pagination)
|
||||||
|
|
||||||
|
|
||||||
|
@ -165,10 +164,10 @@ def feed_authorindex():
|
||||||
@requires_basic_auth_if_no_ano
|
@requires_basic_auth_if_no_ano
|
||||||
def feed_author(book_id):
|
def feed_author(book_id):
|
||||||
off = request.args.get("offset") or 0
|
off = request.args.get("offset") or 0
|
||||||
entries, __, pagination = fill_indexpage((int(off) / (int(config.config_books_per_page)) + 1),
|
entries, __, pagination = calibre_db.fill_indexpage((int(off) / (int(config.config_books_per_page)) + 1),
|
||||||
db.Books,
|
db.Books,
|
||||||
db.Books.authors.any(db.Authors.id == book_id),
|
db.Books.authors.any(db.Authors.id == book_id),
|
||||||
[db.Books.timestamp.desc()])
|
[db.Books.timestamp.desc()])
|
||||||
return render_xml_template('feed.xml', entries=entries, pagination=pagination)
|
return render_xml_template('feed.xml', entries=entries, pagination=pagination)
|
||||||
|
|
||||||
|
|
||||||
|
@ -176,11 +175,14 @@ def feed_author(book_id):
|
||||||
@requires_basic_auth_if_no_ano
|
@requires_basic_auth_if_no_ano
|
||||||
def feed_publisherindex():
|
def feed_publisherindex():
|
||||||
off = request.args.get("offset") or 0
|
off = request.args.get("offset") or 0
|
||||||
entries = db.session.query(db.Publishers).join(db.books_publishers_link).join(db.Books).filter(common_filters())\
|
entries = calibre_db.session.query(db.Publishers)\
|
||||||
.group_by(text('books_publishers_link.publisher')).order_by(db.Publishers.sort)\
|
.join(db.books_publishers_link)\
|
||||||
|
.join(db.Books).filter(calibre_db.common_filters())\
|
||||||
|
.group_by(text('books_publishers_link.publisher'))\
|
||||||
|
.order_by(db.Publishers.sort)\
|
||||||
.limit(config.config_books_per_page).offset(off)
|
.limit(config.config_books_per_page).offset(off)
|
||||||
pagination = Pagination((int(off) / (int(config.config_books_per_page)) + 1), config.config_books_per_page,
|
pagination = Pagination((int(off) / (int(config.config_books_per_page)) + 1), config.config_books_per_page,
|
||||||
len(db.session.query(db.Publishers).all()))
|
len(calibre_db.session.query(db.Publishers).all()))
|
||||||
return render_xml_template('feed.xml', listelements=entries, folder='opds.feed_publisher', pagination=pagination)
|
return render_xml_template('feed.xml', listelements=entries, folder='opds.feed_publisher', pagination=pagination)
|
||||||
|
|
||||||
|
|
||||||
|
@ -188,10 +190,10 @@ def feed_publisherindex():
|
||||||
@requires_basic_auth_if_no_ano
|
@requires_basic_auth_if_no_ano
|
||||||
def feed_publisher(book_id):
|
def feed_publisher(book_id):
|
||||||
off = request.args.get("offset") or 0
|
off = request.args.get("offset") or 0
|
||||||
entries, __, pagination = fill_indexpage((int(off) / (int(config.config_books_per_page)) + 1),
|
entries, __, pagination = calibre_db.fill_indexpage((int(off) / (int(config.config_books_per_page)) + 1),
|
||||||
db.Books,
|
db.Books,
|
||||||
db.Books.publishers.any(db.Publishers.id == book_id),
|
db.Books.publishers.any(db.Publishers.id == book_id),
|
||||||
[db.Books.timestamp.desc()])
|
[db.Books.timestamp.desc()])
|
||||||
return render_xml_template('feed.xml', entries=entries, pagination=pagination)
|
return render_xml_template('feed.xml', entries=entries, pagination=pagination)
|
||||||
|
|
||||||
|
|
||||||
|
@ -199,10 +201,16 @@ def feed_publisher(book_id):
|
||||||
@requires_basic_auth_if_no_ano
|
@requires_basic_auth_if_no_ano
|
||||||
def feed_categoryindex():
|
def feed_categoryindex():
|
||||||
off = request.args.get("offset") or 0
|
off = request.args.get("offset") or 0
|
||||||
entries = db.session.query(db.Tags).join(db.books_tags_link).join(db.Books).filter(common_filters())\
|
entries = calibre_db.session.query(db.Tags)\
|
||||||
.group_by(text('books_tags_link.tag')).order_by(db.Tags.name).offset(off).limit(config.config_books_per_page)
|
.join(db.books_tags_link)\
|
||||||
|
.join(db.Books)\
|
||||||
|
.filter(calibre_db.common_filters())\
|
||||||
|
.group_by(text('books_tags_link.tag'))\
|
||||||
|
.order_by(db.Tags.name)\
|
||||||
|
.offset(off)\
|
||||||
|
.limit(config.config_books_per_page)
|
||||||
pagination = Pagination((int(off) / (int(config.config_books_per_page)) + 1), config.config_books_per_page,
|
pagination = Pagination((int(off) / (int(config.config_books_per_page)) + 1), config.config_books_per_page,
|
||||||
len(db.session.query(db.Tags).all()))
|
len(calibre_db.session.query(db.Tags).all()))
|
||||||
return render_xml_template('feed.xml', listelements=entries, folder='opds.feed_category', pagination=pagination)
|
return render_xml_template('feed.xml', listelements=entries, folder='opds.feed_category', pagination=pagination)
|
||||||
|
|
||||||
|
|
||||||
|
@ -210,10 +218,10 @@ def feed_categoryindex():
|
||||||
@requires_basic_auth_if_no_ano
|
@requires_basic_auth_if_no_ano
|
||||||
def feed_category(book_id):
|
def feed_category(book_id):
|
||||||
off = request.args.get("offset") or 0
|
off = request.args.get("offset") or 0
|
||||||
entries, __, pagination = fill_indexpage((int(off) / (int(config.config_books_per_page)) + 1),
|
entries, __, pagination = calibre_db.fill_indexpage((int(off) / (int(config.config_books_per_page)) + 1),
|
||||||
db.Books,
|
db.Books,
|
||||||
db.Books.tags.any(db.Tags.id == book_id),
|
db.Books.tags.any(db.Tags.id == book_id),
|
||||||
[db.Books.timestamp.desc()])
|
[db.Books.timestamp.desc()])
|
||||||
return render_xml_template('feed.xml', entries=entries, pagination=pagination)
|
return render_xml_template('feed.xml', entries=entries, pagination=pagination)
|
||||||
|
|
||||||
|
|
||||||
|
@ -221,10 +229,15 @@ def feed_category(book_id):
|
||||||
@requires_basic_auth_if_no_ano
|
@requires_basic_auth_if_no_ano
|
||||||
def feed_seriesindex():
|
def feed_seriesindex():
|
||||||
off = request.args.get("offset") or 0
|
off = request.args.get("offset") or 0
|
||||||
entries = db.session.query(db.Series).join(db.books_series_link).join(db.Books).filter(common_filters())\
|
entries = calibre_db.session.query(db.Series)\
|
||||||
.group_by(text('books_series_link.series')).order_by(db.Series.sort).offset(off).all()
|
.join(db.books_series_link)\
|
||||||
|
.join(db.Books)\
|
||||||
|
.filter(calibre_db.common_filters())\
|
||||||
|
.group_by(text('books_series_link.series'))\
|
||||||
|
.order_by(db.Series.sort)\
|
||||||
|
.offset(off).all()
|
||||||
pagination = Pagination((int(off) / (int(config.config_books_per_page)) + 1), config.config_books_per_page,
|
pagination = Pagination((int(off) / (int(config.config_books_per_page)) + 1), config.config_books_per_page,
|
||||||
len(db.session.query(db.Series).all()))
|
len(calibre_db.session.query(db.Series).all()))
|
||||||
return render_xml_template('feed.xml', listelements=entries, folder='opds.feed_series', pagination=pagination)
|
return render_xml_template('feed.xml', listelements=entries, folder='opds.feed_series', pagination=pagination)
|
||||||
|
|
||||||
|
|
||||||
|
@ -232,10 +245,10 @@ def feed_seriesindex():
|
||||||
@requires_basic_auth_if_no_ano
|
@requires_basic_auth_if_no_ano
|
||||||
def feed_series(book_id):
|
def feed_series(book_id):
|
||||||
off = request.args.get("offset") or 0
|
off = request.args.get("offset") or 0
|
||||||
entries, __, pagination = fill_indexpage((int(off) / (int(config.config_books_per_page)) + 1),
|
entries, __, pagination = calibre_db.fill_indexpage((int(off) / (int(config.config_books_per_page)) + 1),
|
||||||
db.Books,
|
db.Books,
|
||||||
db.Books.series.any(db.Series.id == book_id),
|
db.Books.series.any(db.Series.id == book_id),
|
||||||
[db.Books.series_index])
|
[db.Books.series_index])
|
||||||
return render_xml_template('feed.xml', entries=entries, pagination=pagination)
|
return render_xml_template('feed.xml', entries=entries, pagination=pagination)
|
||||||
|
|
||||||
|
|
||||||
|
@ -243,10 +256,13 @@ def feed_series(book_id):
|
||||||
@requires_basic_auth_if_no_ano
|
@requires_basic_auth_if_no_ano
|
||||||
def feed_ratingindex():
|
def feed_ratingindex():
|
||||||
off = request.args.get("offset") or 0
|
off = request.args.get("offset") or 0
|
||||||
entries = db.session.query(db.Ratings, func.count('books_ratings_link.book').label('count'),
|
entries = calibre_db.session.query(db.Ratings, func.count('books_ratings_link.book').label('count'),
|
||||||
(db.Ratings.rating / 2).label('name')) \
|
(db.Ratings.rating / 2).label('name')) \
|
||||||
.join(db.books_ratings_link).join(db.Books).filter(common_filters()) \
|
.join(db.books_ratings_link)\
|
||||||
.group_by(text('books_ratings_link.rating')).order_by(db.Ratings.rating).all()
|
.join(db.Books)\
|
||||||
|
.filter(calibre_db.common_filters()) \
|
||||||
|
.group_by(text('books_ratings_link.rating'))\
|
||||||
|
.order_by(db.Ratings.rating).all()
|
||||||
|
|
||||||
pagination = Pagination((int(off) / (int(config.config_books_per_page)) + 1), config.config_books_per_page,
|
pagination = Pagination((int(off) / (int(config.config_books_per_page)) + 1), config.config_books_per_page,
|
||||||
len(entries))
|
len(entries))
|
||||||
|
@ -260,10 +276,10 @@ def feed_ratingindex():
|
||||||
@requires_basic_auth_if_no_ano
|
@requires_basic_auth_if_no_ano
|
||||||
def feed_ratings(book_id):
|
def feed_ratings(book_id):
|
||||||
off = request.args.get("offset") or 0
|
off = request.args.get("offset") or 0
|
||||||
entries, __, pagination = fill_indexpage((int(off) / (int(config.config_books_per_page)) + 1),
|
entries, __, pagination = calibre_db.fill_indexpage((int(off) / (int(config.config_books_per_page)) + 1),
|
||||||
db.Books,
|
db.Books,
|
||||||
db.Books.ratings.any(db.Ratings.id == book_id),
|
db.Books.ratings.any(db.Ratings.id == book_id),
|
||||||
[db.Books.timestamp.desc()])
|
[db.Books.timestamp.desc()])
|
||||||
return render_xml_template('feed.xml', entries=entries, pagination=pagination)
|
return render_xml_template('feed.xml', entries=entries, pagination=pagination)
|
||||||
|
|
||||||
|
|
||||||
|
@ -271,8 +287,10 @@ def feed_ratings(book_id):
|
||||||
@requires_basic_auth_if_no_ano
|
@requires_basic_auth_if_no_ano
|
||||||
def feed_formatindex():
|
def feed_formatindex():
|
||||||
off = request.args.get("offset") or 0
|
off = request.args.get("offset") or 0
|
||||||
entries = db.session.query(db.Data).join(db.Books).filter(common_filters()) \
|
entries = calibre_db.session.query(db.Data).join(db.Books)\
|
||||||
.group_by(db.Data.format).order_by(db.Data.format).all()
|
.filter(calibre_db.common_filters()) \
|
||||||
|
.group_by(db.Data.format)\
|
||||||
|
.order_by(db.Data.format).all()
|
||||||
pagination = Pagination((int(off) / (int(config.config_books_per_page)) + 1), config.config_books_per_page,
|
pagination = Pagination((int(off) / (int(config.config_books_per_page)) + 1), config.config_books_per_page,
|
||||||
len(entries))
|
len(entries))
|
||||||
|
|
||||||
|
@ -286,10 +304,10 @@ def feed_formatindex():
|
||||||
@requires_basic_auth_if_no_ano
|
@requires_basic_auth_if_no_ano
|
||||||
def feed_format(book_id):
|
def feed_format(book_id):
|
||||||
off = request.args.get("offset") or 0
|
off = request.args.get("offset") or 0
|
||||||
entries, __, pagination = fill_indexpage((int(off) / (int(config.config_books_per_page)) + 1),
|
entries, __, pagination = calibre_db.fill_indexpage((int(off) / (int(config.config_books_per_page)) + 1),
|
||||||
db.Books,
|
db.Books,
|
||||||
db.Books.data.any(db.Data.format == book_id.upper()),
|
db.Books.data.any(db.Data.format == book_id.upper()),
|
||||||
[db.Books.timestamp.desc()])
|
[db.Books.timestamp.desc()])
|
||||||
return render_xml_template('feed.xml', entries=entries, pagination=pagination)
|
return render_xml_template('feed.xml', entries=entries, pagination=pagination)
|
||||||
|
|
||||||
|
|
||||||
|
@ -299,13 +317,13 @@ def feed_format(book_id):
|
||||||
def feed_languagesindex():
|
def feed_languagesindex():
|
||||||
off = request.args.get("offset") or 0
|
off = request.args.get("offset") or 0
|
||||||
if current_user.filter_language() == u"all":
|
if current_user.filter_language() == u"all":
|
||||||
languages = speaking_language()
|
languages = calibre_db.speaking_language()
|
||||||
else:
|
else:
|
||||||
try:
|
try:
|
||||||
cur_l = LC.parse(current_user.filter_language())
|
cur_l = LC.parse(current_user.filter_language())
|
||||||
except UnknownLocaleError:
|
except UnknownLocaleError:
|
||||||
cur_l = None
|
cur_l = None
|
||||||
languages = db.session.query(db.Languages).filter(
|
languages = calibre_db.session.query(db.Languages).filter(
|
||||||
db.Languages.lang_code == current_user.filter_language()).all()
|
db.Languages.lang_code == current_user.filter_language()).all()
|
||||||
if cur_l:
|
if cur_l:
|
||||||
languages[0].name = cur_l.get_language_name(get_locale())
|
languages[0].name = cur_l.get_language_name(get_locale())
|
||||||
|
@ -320,10 +338,10 @@ def feed_languagesindex():
|
||||||
@requires_basic_auth_if_no_ano
|
@requires_basic_auth_if_no_ano
|
||||||
def feed_languages(book_id):
|
def feed_languages(book_id):
|
||||||
off = request.args.get("offset") or 0
|
off = request.args.get("offset") or 0
|
||||||
entries, __, pagination = fill_indexpage((int(off) / (int(config.config_books_per_page)) + 1),
|
entries, __, pagination = calibre_db.fill_indexpage((int(off) / (int(config.config_books_per_page)) + 1),
|
||||||
db.Books,
|
db.Books,
|
||||||
db.Books.languages.any(db.Languages.id == book_id),
|
db.Books.languages.any(db.Languages.id == book_id),
|
||||||
[db.Books.timestamp.desc()])
|
[db.Books.timestamp.desc()])
|
||||||
return render_xml_template('feed.xml', entries=entries, pagination=pagination)
|
return render_xml_template('feed.xml', entries=entries, pagination=pagination)
|
||||||
|
|
||||||
|
|
||||||
|
@ -356,7 +374,7 @@ def feed_shelf(book_id):
|
||||||
books_in_shelf = ub.session.query(ub.BookShelf).filter(ub.BookShelf.shelf == book_id).order_by(
|
books_in_shelf = ub.session.query(ub.BookShelf).filter(ub.BookShelf.shelf == book_id).order_by(
|
||||||
ub.BookShelf.order.asc()).all()
|
ub.BookShelf.order.asc()).all()
|
||||||
for book in books_in_shelf:
|
for book in books_in_shelf:
|
||||||
cur_book = db.session.query(db.Books).filter(db.Books.id == book.book_id).first()
|
cur_book = calibre_db.get_book(book.book_id)
|
||||||
result.append(cur_book)
|
result.append(cur_book)
|
||||||
pagination = Pagination((int(off) / (int(config.config_books_per_page)) + 1), config.config_books_per_page,
|
pagination = Pagination((int(off) / (int(config.config_books_per_page)) + 1), config.config_books_per_page,
|
||||||
len(result))
|
len(result))
|
||||||
|
@ -367,14 +385,18 @@ def feed_shelf(book_id):
|
||||||
@requires_basic_auth_if_no_ano
|
@requires_basic_auth_if_no_ano
|
||||||
@download_required
|
@download_required
|
||||||
def opds_download_link(book_id, book_format):
|
def opds_download_link(book_id, book_format):
|
||||||
return get_download_link(book_id, book_format.lower())
|
if "Kobo" in request.headers.get('User-Agent'):
|
||||||
|
client = "kobo"
|
||||||
|
else:
|
||||||
|
client = ""
|
||||||
|
return get_download_link(book_id, book_format.lower(), client)
|
||||||
|
|
||||||
|
|
||||||
@opds.route("/ajax/book/<string:uuid>/<library>")
|
@opds.route("/ajax/book/<string:uuid>/<library>")
|
||||||
@opds.route("/ajax/book/<string:uuid>", defaults={'library': ""})
|
@opds.route("/ajax/book/<string:uuid>", defaults={'library': ""})
|
||||||
@requires_basic_auth_if_no_ano
|
@requires_basic_auth_if_no_ano
|
||||||
def get_metadata_calibre_companion(uuid, library):
|
def get_metadata_calibre_companion(uuid, library):
|
||||||
entry = db.session.query(db.Books).filter(db.Books.uuid.like("%" + uuid + "%")).first()
|
entry = calibre_db.session.query(db.Books).filter(db.Books.uuid.like("%" + uuid + "%")).first()
|
||||||
if entry is not None:
|
if entry is not None:
|
||||||
js = render_template('json.txt', entry=entry)
|
js = render_template('json.txt', entry=entry)
|
||||||
response = make_response(js)
|
response = make_response(js)
|
||||||
|
@ -386,8 +408,7 @@ def get_metadata_calibre_companion(uuid, library):
|
||||||
|
|
||||||
def feed_search(term):
|
def feed_search(term):
|
||||||
if term:
|
if term:
|
||||||
term = term.strip().lower()
|
entries = calibre_db.get_search_results(term)
|
||||||
entries = get_search_results(term)
|
|
||||||
entriescount = len(entries) if len(entries) > 0 else 1
|
entriescount = len(entries) if len(entries) > 0 else 1
|
||||||
pagination = Pagination(1, entriescount, entriescount)
|
pagination = Pagination(1, entriescount, entriescount)
|
||||||
return render_xml_template('feed.xml', searchterm=term, entries=entries, pagination=pagination)
|
return render_xml_template('feed.xml', searchterm=term, entries=entries, pagination=pagination)
|
||||||
|
|
|
@ -72,7 +72,11 @@ class WebServer(object):
|
||||||
if config.config_access_log:
|
if config.config_access_log:
|
||||||
log_name = "gevent.access" if _GEVENT else "tornado.access"
|
log_name = "gevent.access" if _GEVENT else "tornado.access"
|
||||||
formatter = logger.ACCESS_FORMATTER_GEVENT if _GEVENT else logger.ACCESS_FORMATTER_TORNADO
|
formatter = logger.ACCESS_FORMATTER_GEVENT if _GEVENT else logger.ACCESS_FORMATTER_TORNADO
|
||||||
self.access_logger = logger.create_access_log(config.config_access_logfile, log_name, formatter)
|
self.access_logger, logfile = logger.create_access_log(config.config_access_logfile, log_name, formatter)
|
||||||
|
if logfile != config.config_access_logfile:
|
||||||
|
log.warning("Accesslog path %s not valid, falling back to default", config.config_access_logfile)
|
||||||
|
config.config_access_logfile = logfile
|
||||||
|
config.save()
|
||||||
else:
|
else:
|
||||||
if not _GEVENT:
|
if not _GEVENT:
|
||||||
logger.get('tornado.access').disabled = True
|
logger.get('tornado.access').disabled = True
|
||||||
|
@ -196,6 +200,9 @@ class WebServer(object):
|
||||||
def stop(self, restart=False):
|
def stop(self, restart=False):
|
||||||
from . import updater_thread
|
from . import updater_thread
|
||||||
updater_thread.stop()
|
updater_thread.stop()
|
||||||
|
from . import calibre_db
|
||||||
|
calibre_db.stop()
|
||||||
|
|
||||||
|
|
||||||
log.info("webserver stop (restart=%s)", restart)
|
log.info("webserver stop (restart=%s)", restart)
|
||||||
self.restart = restart
|
self.restart = restart
|
||||||
|
|
|
@ -20,7 +20,10 @@ from __future__ import division, print_function, unicode_literals
|
||||||
import time
|
import time
|
||||||
from functools import reduce
|
from functools import reduce
|
||||||
|
|
||||||
from goodreads.client import GoodreadsClient
|
try:
|
||||||
|
from goodreads.client import GoodreadsClient
|
||||||
|
except ImportError:
|
||||||
|
from betterreads.client import GoodreadsClient
|
||||||
|
|
||||||
try: import Levenshtein
|
try: import Levenshtein
|
||||||
except ImportError: Levenshtein = False
|
except ImportError: Levenshtein = False
|
||||||
|
@ -95,8 +98,12 @@ def get_other_books(author_info, library_books=None):
|
||||||
for book in author_info.books:
|
for book in author_info.books:
|
||||||
if book.isbn in identifiers:
|
if book.isbn in identifiers:
|
||||||
continue
|
continue
|
||||||
if book.gid["#text"] in identifiers:
|
if isinstance(book.gid, int):
|
||||||
continue
|
if book.gid in identifiers:
|
||||||
|
continue
|
||||||
|
else:
|
||||||
|
if book.gid["#text"] in identifiers:
|
||||||
|
continue
|
||||||
|
|
||||||
if Levenshtein and library_titles:
|
if Levenshtein and library_titles:
|
||||||
goodreads_title = book._book_dict['title_without_series']
|
goodreads_title = book._book_dict['title_without_series']
|
||||||
|
|
11
cps/shelf.py
11
cps/shelf.py
|
@ -28,9 +28,8 @@ from flask_babel import gettext as _
|
||||||
from flask_login import login_required, current_user
|
from flask_login import login_required, current_user
|
||||||
from sqlalchemy.sql.expression import func
|
from sqlalchemy.sql.expression import func
|
||||||
|
|
||||||
from . import logger, ub, searched_ids, db
|
from . import logger, ub, searched_ids, db, calibre_db
|
||||||
from .web import render_title_template
|
from .web import render_title_template
|
||||||
from .helper import common_filters
|
|
||||||
|
|
||||||
|
|
||||||
shelf = Blueprint('shelf', __name__)
|
shelf = Blueprint('shelf', __name__)
|
||||||
|
@ -320,11 +319,11 @@ def show_shelf(shelf_type, shelf_id):
|
||||||
books_in_shelf = ub.session.query(ub.BookShelf).filter(ub.BookShelf.shelf == shelf_id)\
|
books_in_shelf = ub.session.query(ub.BookShelf).filter(ub.BookShelf.shelf == shelf_id)\
|
||||||
.order_by(ub.BookShelf.order.asc()).all()
|
.order_by(ub.BookShelf.order.asc()).all()
|
||||||
for book in books_in_shelf:
|
for book in books_in_shelf:
|
||||||
cur_book = db.session.query(db.Books).filter(db.Books.id == book.book_id).filter(common_filters()).first()
|
cur_book = calibre_db.get_filtered_book(book.book_id)
|
||||||
if cur_book:
|
if cur_book:
|
||||||
result.append(cur_book)
|
result.append(cur_book)
|
||||||
else:
|
else:
|
||||||
cur_book = db.session.query(db.Books).filter(db.Books.id == book.book_id).first()
|
cur_book = calibre_db.get_book(book.book_id)
|
||||||
if not cur_book:
|
if not cur_book:
|
||||||
log.info('Not existing book %s in %s deleted', book.book_id, shelf)
|
log.info('Not existing book %s in %s deleted', book.book_id, shelf)
|
||||||
ub.session.query(ub.BookShelf).filter(ub.BookShelf.book_id == book.book_id).delete()
|
ub.session.query(ub.BookShelf).filter(ub.BookShelf.book_id == book.book_id).delete()
|
||||||
|
@ -356,7 +355,7 @@ def order_shelf(shelf_id):
|
||||||
books_in_shelf2 = ub.session.query(ub.BookShelf).filter(ub.BookShelf.shelf == shelf_id) \
|
books_in_shelf2 = ub.session.query(ub.BookShelf).filter(ub.BookShelf.shelf == shelf_id) \
|
||||||
.order_by(ub.BookShelf.order.asc()).all()
|
.order_by(ub.BookShelf.order.asc()).all()
|
||||||
for book in books_in_shelf2:
|
for book in books_in_shelf2:
|
||||||
cur_book = db.session.query(db.Books).filter(db.Books.id == book.book_id).filter(common_filters()).first()
|
cur_book = calibre_db.get_filtered_book(book.book_id)
|
||||||
if cur_book:
|
if cur_book:
|
||||||
result.append({'title': cur_book.title,
|
result.append({'title': cur_book.title,
|
||||||
'id': cur_book.id,
|
'id': cur_book.id,
|
||||||
|
@ -364,7 +363,7 @@ def order_shelf(shelf_id):
|
||||||
'series': cur_book.series,
|
'series': cur_book.series,
|
||||||
'series_index': cur_book.series_index})
|
'series_index': cur_book.series_index})
|
||||||
else:
|
else:
|
||||||
cur_book = db.session.query(db.Books).filter(db.Books.id == book.book_id).first()
|
cur_book = calibre_db.get_book(book.book_id)
|
||||||
result.append({'title': _('Hidden Book'),
|
result.append({'title': _('Hidden Book'),
|
||||||
'id': cur_book.id,
|
'id': cur_book.id,
|
||||||
'author': [],
|
'author': [],
|
||||||
|
|
17
cps/static/css/caliBlur_override.css
Normal file
17
cps/static/css/caliBlur_override.css
Normal file
|
@ -0,0 +1,17 @@
|
||||||
|
body.serieslist.grid-view div.container-fluid>div>div.col-sm-10:before{
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.cover .badge{
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
right: 0;
|
||||||
|
background-color: #cc7b19;
|
||||||
|
border-radius: 0;
|
||||||
|
padding: 0 8px;
|
||||||
|
box-shadow: 0 0 4px rgba(0,0,0,.6);
|
||||||
|
line-height: 24px;
|
||||||
|
}
|
||||||
|
.cover{
|
||||||
|
box-shadow: 0 0 4px rgba(0,0,0,.6);
|
||||||
|
}
|
|
@ -109,7 +109,7 @@ a { color: #45b29d; }
|
||||||
|
|
||||||
.container-fluid .book .cover img {
|
.container-fluid .book .cover img {
|
||||||
border: 1px solid #fff;
|
border: 1px solid #fff;
|
||||||
box-sizeing: border-box;
|
box-sizing: border-box;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
bottom: 0;
|
bottom: 0;
|
||||||
position: absolute;
|
position: absolute;
|
||||||
|
@ -165,7 +165,7 @@ span.glyphicon.glyphicon-tags {
|
||||||
|
|
||||||
.container-fluid .single .cover img {
|
.container-fluid .single .cover img {
|
||||||
border: 1px solid #fff;
|
border: 1px solid #fff;
|
||||||
box-sizeing: border-box;
|
box-sizing: border-box;
|
||||||
-webkit-box-shadow: 0 5px 8px -6px #777;
|
-webkit-box-shadow: 0 5px 8px -6px #777;
|
||||||
-moz-box-shadow: 0 5px 8px -6px #777;
|
-moz-box-shadow: 0 5px 8px -6px #777;
|
||||||
box-shadow: 0 5px 8px -6px #777;
|
box-shadow: 0 5px 8px -6px #777;
|
||||||
|
@ -174,6 +174,12 @@ span.glyphicon.glyphicon-tags {
|
||||||
.navbar-default .navbar-toggle .icon-bar {background-color: #000; }
|
.navbar-default .navbar-toggle .icon-bar {background-color: #000; }
|
||||||
.navbar-default .navbar-toggle {border-color: #000; }
|
.navbar-default .navbar-toggle {border-color: #000; }
|
||||||
.cover { margin-bottom: 10px; }
|
.cover { margin-bottom: 10px; }
|
||||||
|
.cover .badge{
|
||||||
|
position: absolute;
|
||||||
|
top: 2px;
|
||||||
|
left: 2px;
|
||||||
|
background-color: #777;
|
||||||
|
}
|
||||||
.cover-height { max-height: 100px;}
|
.cover-height { max-height: 100px;}
|
||||||
|
|
||||||
.col-sm-2 a .cover-small {
|
.col-sm-2 a .cover-small {
|
||||||
|
@ -207,7 +213,7 @@ span.glyphicon.glyphicon-tags {
|
||||||
.panel-body {background-color: #f5f5f5; }
|
.panel-body {background-color: #f5f5f5; }
|
||||||
.spinner {margin: 0 41%; }
|
.spinner {margin: 0 41%; }
|
||||||
.spinner2 {margin: 0 41%; }
|
.spinner2 {margin: 0 41%; }
|
||||||
|
.intend-form { margin-left:20px; }
|
||||||
table .bg-dark-danger {background-color: #d9534f; color: #fff; }
|
table .bg-dark-danger {background-color: #d9534f; color: #fff; }
|
||||||
table .bg-dark-danger a {color: #fff; }
|
table .bg-dark-danger a {color: #fff; }
|
||||||
table .bg-dark-danger:hover {background-color: #c9302c; }
|
table .bg-dark-danger:hover {background-color: #c9302c; }
|
||||||
|
@ -296,3 +302,4 @@ div.log {
|
||||||
white-space: nowrap;
|
white-space: nowrap;
|
||||||
padding: 0.5em;
|
padding: 0.5em;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
54
cps/static/js/filter_grid.js
Normal file
54
cps/static/js/filter_grid.js
Normal file
|
@ -0,0 +1,54 @@
|
||||||
|
/* This file is part of the Calibre-Web (https://github.com/janeczku/calibre-web)
|
||||||
|
* Copyright (C) 2018 OzzieIsaacs
|
||||||
|
*
|
||||||
|
* This program is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
var $list = $("#list").isotope({
|
||||||
|
itemSelector: ".book",
|
||||||
|
layoutMode: "fitRows",
|
||||||
|
getSortData: {
|
||||||
|
title: ".title",
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
$("#desc").click(function() {
|
||||||
|
$list.isotope({
|
||||||
|
sortBy: "name",
|
||||||
|
sortAscending: true
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
});
|
||||||
|
|
||||||
|
$("#asc").click(function() {
|
||||||
|
$list.isotope({
|
||||||
|
sortBy: "name",
|
||||||
|
sortAscending: false
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
});
|
||||||
|
|
||||||
|
$("#all").click(function() {
|
||||||
|
// go through all elements and make them visible
|
||||||
|
$list.isotope({ filter: function() {
|
||||||
|
return true;
|
||||||
|
} })
|
||||||
|
});
|
||||||
|
|
||||||
|
$(".char").click(function() {
|
||||||
|
var character = this.innerText;
|
||||||
|
$list.isotope({ filter: function() {
|
||||||
|
return this.attributes["data-id"].value.charAt(0).toUpperCase() == character;
|
||||||
|
} })
|
||||||
|
});
|
|
@ -323,4 +323,17 @@ $(function() {
|
||||||
$(".discover .row").isotope("layout");
|
$(".discover .row").isotope("layout");
|
||||||
});
|
});
|
||||||
|
|
||||||
|
$(".update-view").click(function(e) {
|
||||||
|
var target = $(this).data("target");
|
||||||
|
var view = $(this).data("view");
|
||||||
|
|
||||||
|
e.preventDefault();
|
||||||
|
e.stopPropagation();
|
||||||
|
var data = {};
|
||||||
|
data[target] = view;
|
||||||
|
console.debug("Updating view data: ", data);
|
||||||
|
$.post( "/ajax/view", data).done(function( ) {
|
||||||
|
location.reload();
|
||||||
|
});
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
|
@ -22,7 +22,7 @@ import os
|
||||||
import subprocess
|
import subprocess
|
||||||
|
|
||||||
|
|
||||||
def process_open(command, quotes=(), env=None, sout=subprocess.PIPE, serr=subprocess.PIPE):
|
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
|
||||||
# linux py3.x no encode and as list without quotes no empty element for parameters
|
# linux py3.x no encode and as list without quotes no empty element for parameters
|
||||||
# windows py2.7 encode as string with quotes empty element for parameters is okay
|
# windows py2.7 encode as string with quotes empty element for parameters is okay
|
||||||
|
@ -41,12 +41,13 @@ def process_open(command, quotes=(), env=None, sout=subprocess.PIPE, serr=subpro
|
||||||
else:
|
else:
|
||||||
exc_command = [x for x in command]
|
exc_command = [x for x in command]
|
||||||
|
|
||||||
return subprocess.Popen(exc_command, shell=False, stdout=sout, stderr=serr, universal_newlines=True, env=env)
|
return subprocess.Popen(exc_command, shell=False, stdout=sout, stderr=serr, universal_newlines=newlines, env=env)
|
||||||
|
|
||||||
|
|
||||||
def process_wait(command, serr=subprocess.PIPE):
|
def process_wait(command, serr=subprocess.PIPE):
|
||||||
# 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.
|
||||||
p = process_open(command, serr=serr)
|
newlines = os.name != 'nt'
|
||||||
|
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):
|
||||||
|
|
|
@ -13,11 +13,14 @@
|
||||||
<th>{{_('E-mail Address')}}</th>
|
<th>{{_('E-mail Address')}}</th>
|
||||||
<th>{{_('Send to Kindle E-mail Address')}}</th>
|
<th>{{_('Send to Kindle E-mail Address')}}</th>
|
||||||
<th>{{_('Downloads')}}</th>
|
<th>{{_('Downloads')}}</th>
|
||||||
<th class="hidden-xs">{{_('Admin')}}</th>
|
<th class="hidden-xs ">{{_('Admin')}}</th>
|
||||||
<th class="hidden-xs">{{_('Download')}}</th>
|
<th class="hidden-xs hidden-sm">{{_('Password')}}</th>
|
||||||
<th class="hidden-xs">{{_('View Books')}}</th>
|
<th class="hidden-xs hidden-sm">{{_('Upload')}}</th>
|
||||||
<th class="hidden-xs">{{_('Upload')}}</th>
|
<th class="hidden-xs hidden-sm">{{_('Download')}}</th>
|
||||||
<th class="hidden-xs">{{_('Edit')}}</th>
|
<th class="hidden-xs hidden-sm hidden-md">{{_('View Books')}}</th>
|
||||||
|
<th class="hidden-xs hidden-sm hidden-md">{{_('Edit')}}</th>
|
||||||
|
<th class="hidden-xs hidden-sm hidden-md">{{_('Delete')}}</th>
|
||||||
|
<th class="hidden-xs hidden-sm hidden-md">{{_('Public Shelf')}}</th>
|
||||||
</tr>
|
</tr>
|
||||||
{% for user in allUser %}
|
{% for user in allUser %}
|
||||||
{% if not user.role_anonymous() or config.config_anonbrowse %}
|
{% if not user.role_anonymous() or config.config_anonbrowse %}
|
||||||
|
@ -27,10 +30,13 @@
|
||||||
<td>{{user.kindle_mail}}</td>
|
<td>{{user.kindle_mail}}</td>
|
||||||
<td>{{user.downloads.count()}}</td>
|
<td>{{user.downloads.count()}}</td>
|
||||||
<td class="hidden-xs">{{ display_bool_setting(user.role_admin()) }}</td>
|
<td class="hidden-xs">{{ display_bool_setting(user.role_admin()) }}</td>
|
||||||
<td class="hidden-xs">{{ display_bool_setting(user.role_download()) }}</td>
|
<td class="hidden-xs hidden-sm">{{ display_bool_setting(user.role_passwd()) }}</td>
|
||||||
<td class="hidden-xs">{{ display_bool_setting(user.role_viewer()) }}</td>
|
<td class="hidden-xs hidden-sm">{{ display_bool_setting(user.role_upload()) }}</td>
|
||||||
<td class="hidden-xs">{{ display_bool_setting(user.role_upload()) }}</td>
|
<td class="hidden-xs hidden-sm">{{ display_bool_setting(user.role_download()) }}</td>
|
||||||
<td class="hidden-xs">{{ display_bool_setting(user.role_edit()) }}</td>
|
<td class="hidden-xs hidden-sm hidden-md">{{ display_bool_setting(user.role_viewer()) }}</td>
|
||||||
|
<td class="hidden-xs hidden-sm hidden-md">{{ display_bool_setting(user.role_edit()) }}</td>
|
||||||
|
<td class="hidden-xs hidden-sm hidden-md">{{ display_bool_setting(user.role_delete_books()) }}</td>
|
||||||
|
<td class="hidden-xs hidden-sm hidden-md">{{ display_bool_setting(user.role_edit_shelfs()) }}</td>
|
||||||
</tr>
|
</tr>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
|
|
|
@ -86,7 +86,7 @@
|
||||||
</div>
|
</div>
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<label for="series_index">{{_('Series ID')}}</label>
|
<label for="series_index">{{_('Series ID')}}</label>
|
||||||
<input type="number" step="0.01" min="0" class="form-control" name="series_index" id="series_index" value="{{book.series_index}}">
|
<input type="number" step="0.01" min="0" placeholder="1" class="form-control" name="series_index" id="series_index" value="{{book.series_index}}">
|
||||||
</div>
|
</div>
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<label for="rating">{{_('Rating')}}</label>
|
<label for="rating">{{_('Rating')}}</label>
|
||||||
|
|
59
cps/templates/book_table.html
Normal file
59
cps/templates/book_table.html
Normal file
|
@ -0,0 +1,59 @@
|
||||||
|
{% extends "layout.html" %}
|
||||||
|
{% block body %}
|
||||||
|
<h1 class="{{page}}">{{_(title)}}</h1>
|
||||||
|
|
||||||
|
<div class="filterheader hidden-xs hidden-sm">
|
||||||
|
{% if entries.__len__() %}
|
||||||
|
{% if data == 'author' %}
|
||||||
|
<button id="sort_name" class="btn btn-primary"><b>B,A <-> A B</b></button>
|
||||||
|
{% endif %}
|
||||||
|
{% endif %}
|
||||||
|
<button id="desc" class="btn btn-primary"><span class="glyphicon glyphicon-sort-by-alphabet"></span></button>
|
||||||
|
<button id="asc" class="btn btn-primary"><span class="glyphicon glyphicon-sort-by-alphabet-alt"></span></button>
|
||||||
|
{% if charlist|length %}
|
||||||
|
<button id="all" class="btn btn-primary">{{_('All')}}</button>
|
||||||
|
{% endif %}
|
||||||
|
<div class="btn-group character" role="group">
|
||||||
|
{% for char in charlist%}
|
||||||
|
<button class="btn btn-primary char">{{char.char}}</button>
|
||||||
|
{% endfor %}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{% if title == "Series" %}
|
||||||
|
<button class="update-view btn btn-primary" href="#" data-target="series_view" data-view="grid">Grid</button>
|
||||||
|
{% endif %}
|
||||||
|
</div>
|
||||||
|
<div class="container">
|
||||||
|
<div id="list" class="col-xs-12 col-sm-6">
|
||||||
|
{% for entry in entries %}
|
||||||
|
{% if loop.index0 == (loop.length/2+loop.length%2)|int and loop.length > 20 %}
|
||||||
|
</div>
|
||||||
|
<div id="second" class="col-xs-12 col-sm-6">
|
||||||
|
{% endif %}
|
||||||
|
<div class="row" {% if entry[0].sort %}data-name="{{entry[0].name}}"{% endif %} data-id="{% if entry[0].sort %}{{entry[0].sort}}{% else %}{% if entry.name %}{{entry.name}}{% else %}{{entry[0].name}}{% endif %}{% endif %}">
|
||||||
|
<div class="col-xs-2 col-sm-2 col-md-1" align="left"><span class="badge">{{entry.count}}</span></div>
|
||||||
|
<div class="col-xs-10 col-sm-10 col-md-11"><a id="list_{{loop.index0}}" href="{% if entry.format %}{{url_for('web.books_list', data=data, sort='new', book_id=entry.format )}}{% else %}{{url_for('web.books_list', data=data, sort='new', book_id=entry[0].id )}}{% endif %}">
|
||||||
|
{% if entry.name %}
|
||||||
|
<div class="rating">
|
||||||
|
{% for number in range(entry.name) %}
|
||||||
|
<span class="glyphicon glyphicon-star good"></span>
|
||||||
|
{% if loop.last and loop.index < 5 %}
|
||||||
|
{% for numer in range(5 - loop.index) %}
|
||||||
|
<span class="glyphicon glyphicon-star"></span>
|
||||||
|
{% endfor %}
|
||||||
|
{% endif %}
|
||||||
|
{% endfor %}
|
||||||
|
</div>
|
||||||
|
{% else %}
|
||||||
|
{% if entry.format %}
|
||||||
|
{{entry.format}}
|
||||||
|
{% else %}
|
||||||
|
{{entry[0].name}}{% endif %}{% endif %}</a></div>
|
||||||
|
</div>
|
||||||
|
{% endfor %}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{% endblock %}
|
||||||
|
{% block js %}
|
||||||
|
<script src="{{ url_for('static', filename='js/filter_list.js') }}"></script>
|
||||||
|
{% endblock %}
|
|
@ -3,7 +3,7 @@
|
||||||
<div class="discover">
|
<div class="discover">
|
||||||
<h2>{{title}}</h2>
|
<h2>{{title}}</h2>
|
||||||
<form role="form" method="POST" autocomplete="off">
|
<form role="form" method="POST" autocomplete="off">
|
||||||
<div class="panel-group">
|
<div class="panel-group col-md-10 col-lg-6">
|
||||||
<div class="panel panel-default">
|
<div class="panel panel-default">
|
||||||
<div class="panel-heading">
|
<div class="panel-heading">
|
||||||
<h4 class="panel-title">
|
<h4 class="panel-title">
|
||||||
|
@ -15,10 +15,13 @@
|
||||||
</div>
|
</div>
|
||||||
<div id="collapseOne" class="panel-collapse collapse in">
|
<div id="collapseOne" class="panel-collapse collapse in">
|
||||||
<div class="panel-body">
|
<div class="panel-body">
|
||||||
<div class="form-group required">
|
<div class="form-group required input-group">
|
||||||
<label for="config_calibre_dir">{{_('Location of Calibre Database')}}</label>
|
<label for="config_calibre_dir" class="sr-only">{{_('Location of Calibre Database')}}</label>
|
||||||
<input type="text" class="form-control" name="config_calibre_dir" id="config_calibre_dir" value="{% if config.config_calibre_dir != None %}{{ config.config_calibre_dir }}{% endif %}" autocomplete="off">
|
<input type="text" class="form-control" id="config_calibre_dir" name="config_calibre_dir" value="{% if config.config_calibre_dir != None %}{{ config.config_calibre_dir }}{% endif %}" autocomplete="off">
|
||||||
</div>
|
<span class="input-group-btn">
|
||||||
|
<button type="button" id="library_path" class="btn btn-default"><span class="glyphicon glyphicon-folder-open"></span></button>
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
{% if feature_support['gdrive'] %}
|
{% if feature_support['gdrive'] %}
|
||||||
<div class="form-group required">
|
<div class="form-group required">
|
||||||
<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 %} >
|
<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 %} >
|
||||||
|
@ -87,21 +90,25 @@
|
||||||
<label for="config_port">{{_('Server Port')}}</label>
|
<label for="config_port">{{_('Server Port')}}</label>
|
||||||
<input type="number" min="1" max="65535" class="form-control" name="config_port" id="config_port" value="{% if config.config_port != None %}{{ config.config_port }}{% endif %}" autocomplete="off" required>
|
<input type="number" min="1" max="65535" class="form-control" name="config_port" id="config_port" value="{% if config.config_port != None %}{{ config.config_port }}{% endif %}" autocomplete="off" required>
|
||||||
</div>
|
</div>
|
||||||
<div class="form-group">
|
<label for="config_certfile">{{_('SSL certfile location (leave it empty for non-SSL Servers)')}}</label>
|
||||||
<label for="config_certfile">{{_('SSL certfile location (leave it empty for non-SSL Servers)')}}</label>
|
<div class="form-group input-group">
|
||||||
<input type="text" class="form-control" name="config_certfile" id="config_certfile" value="{% if config.config_certfile != None %}{{ config.config_certfile }}{% endif %}" autocomplete="off">
|
<input type="text" class="form-control" id="config_certfile" name="config_certfile" value="{% if config.config_certfile != None %}{{ config.config_certfile }}{% endif %}" autocomplete="off">
|
||||||
|
<span class="input-group-btn">
|
||||||
|
<button type="button" id="certfile_path" class="btn btn-default"><span class="glyphicon glyphicon-folder-open"></span></button>
|
||||||
|
</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="form-group">
|
<label for="config_calibre_dir" >{{_('SSL Keyfile location (leave it empty for non-SSL Servers)')}}</label>
|
||||||
<label for="config_keyfile">{{_('SSL Keyfile location (leave it empty for non-SSL Servers)')}}</label>
|
<div class="form-group input-group">
|
||||||
<input type="text" class="form-control" name="config_keyfile" id="config_keyfile" value="{% if config.config_keyfile != None %}{{ config.config_keyfile }}{% endif %}" autocomplete="off">
|
<input type="text" class="form-control" id="config_keyfile" name="config_keyfile" value="{% if config.config_keyfile != None %}{{ config.config_keyfile }}{% endif %}" autocomplete="off">
|
||||||
|
<span class="input-group-btn">
|
||||||
|
<button type="button" id="keyfile_path" class="btn btn-default"><span class="glyphicon glyphicon-folder-open"></span></button>
|
||||||
|
</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<label for="config_updatechannel">{{_('Update Channel')}}</label>
|
<label for="config_updatechannel">{{_('Update Channel')}}</label>
|
||||||
<select name="config_updatechannel" id="config_updatechannel" class="form-control">
|
<select name="config_updatechannel" id="config_updatechannel" class="form-control">
|
||||||
<option value="0" {% if config.config_updatechannel == 0 %}selected{% endif %}>{{_('Stable')}}</option>
|
<option value="0" {% if config.config_updatechannel == 0 %}selected{% endif %}>{{_('Stable')}}</option>
|
||||||
<!--option value="1" {% if config.config_updatechannel == 1 %}selected{% endif %}>{{_('Stable (Automatic)')}}</option-->
|
<option value="2" {% if config.config_updatechannel == 2 %}selected{% endif %}>{{_('Nightly')}}</option>
|
||||||
<option value="2" {% if config.config_updatechannel == 2 %}selected{% endif %}>{{_('Nightly')}}</option>
|
|
||||||
<!--option-- value="3" {% if config.config_updatechannel == 3 %}selected{% endif %}>{{_('Nightly (Automatic)')}}</option-->
|
|
||||||
</select>
|
</select>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -154,17 +161,29 @@
|
||||||
<div id="collapsefive" class="panel-collapse collapse">
|
<div id="collapsefive" class="panel-collapse collapse">
|
||||||
<div class="panel-body">
|
<div class="panel-body">
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<input type="checkbox" id="config_uploading" name="config_uploading" {% if config.config_uploading %}checked{% endif %}>
|
<input type="checkbox" id="config_uploading" data-control="upload_settings" name="config_uploading" {% if config.config_uploading %}checked{% endif %}>
|
||||||
<label for="config_uploading">{{_('Enable Uploads')}}</label>
|
<label for="config_uploading">{{_('Enable Uploads')}}</label>
|
||||||
</div>
|
</div>
|
||||||
|
<div data-related="upload_settings">
|
||||||
|
<div class="form-group">
|
||||||
|
<label for="config_upload_formats">{{_('Allowed Upload Fileformats')}}</label>
|
||||||
|
<input type="text" class="form-control" name="config_upload_formats" id="config_upload_formats" value="{% if config.config_upload_formats != None %}{{ config.config_upload_formats }}{% endif %}" autocomplete="off">
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<input type="checkbox" id="config_anonbrowse" name="config_anonbrowse" {% if config.config_anonbrowse %}checked{% endif %}>
|
<input type="checkbox" id="config_anonbrowse" name="config_anonbrowse" {% if config.config_anonbrowse %}checked{% endif %}>
|
||||||
<label for="config_anonbrowse">{{_('Enable Anonymous Browsing')}}</label>
|
<label for="config_anonbrowse">{{_('Enable Anonymous Browsing')}}</label>
|
||||||
</div>
|
</div>
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<input type="checkbox" id="config_public_reg" name="config_public_reg" {% if config.config_public_reg %}checked{% endif %}>
|
<input type="checkbox" id="config_public_reg" data-control="register_settings" name="config_public_reg" {% if config.config_public_reg %}checked{% endif %}>
|
||||||
<label for="config_public_reg">{{_('Enable Public Registration')}}</label>
|
<label for="config_public_reg">{{_('Enable Public Registration')}}</label>
|
||||||
</div>
|
</div>
|
||||||
|
<div data-related="register_settings">
|
||||||
|
<div class="form-group intend-form">
|
||||||
|
<input type="checkbox" id="config_register_email" name="config_register_email" {% if config.config_register_email %}checked{% endif %}>
|
||||||
|
<label for="config_register_email">{{_('Use E-Mail as Username')}}</label>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<input type="checkbox" id="config_remote_login" name="config_remote_login" {% if config.config_remote_login %}checked{% endif %}>
|
<input type="checkbox" id="config_remote_login" name="config_remote_login" {% if config.config_remote_login %}checked{% endif %}>
|
||||||
<label for="config_remote_login">{{_('Enable Magic Link Remote Login')}}</label>
|
<label for="config_remote_login">{{_('Enable Magic Link Remote Login')}}</label>
|
||||||
|
@ -326,30 +345,33 @@
|
||||||
</div>
|
</div>
|
||||||
<div id="collapseeight" class="panel-collapse collapse">
|
<div id="collapseeight" class="panel-collapse collapse">
|
||||||
<div class="panel-body">
|
<div class="panel-body">
|
||||||
<div class="form-group">
|
<label for="config_converterpath">{{_('Path to Calibre E-Book Converter')}}</label>
|
||||||
<div><input type="radio" name="config_ebookconverter" id="converter0" value="0" {% if config.config_ebookconverter == 0 %}checked{% endif %}>
|
<div class="form-group input-group">
|
||||||
<label for="converter0">{{_('No Converter')}}</label></div>
|
<input type="text" class="form-control" id="config_converterpath" name="config_converterpath" value="{% if config.config_converterpath != None %}{{ config.config_converterpath }}{% endif %}" autocomplete="off">
|
||||||
<div><input type="radio" name="config_ebookconverter" id="converter1" value="1" {% if config.config_ebookconverter == 1 %}checked{% endif %}>
|
<span class="input-group-btn">
|
||||||
<label for="converter1">{{_('Use Kindlegen')}}</label></div>
|
<button type="button" id="converter_path" class="btn btn-default"><span class="glyphicon glyphicon-folder-open"></span></button>
|
||||||
<div><input type="radio" name="config_ebookconverter" id="converter2" value="2" {% if config.config_ebookconverter == 2 %}checked{% endif %}>
|
</span>
|
||||||
<label for="converter2">{{_('Use calibre\'s ebook converter')}}</label></div>
|
</div>
|
||||||
|
<div class="form-group">
|
||||||
|
<label for="config_calibre">{{_('Calibre E-Book Converter Settings')}}</label>
|
||||||
|
<input type="text" class="form-control" id="config_calibre" name="config_calibre" value="{% if config.config_calibre != None %}{{ config.config_calibre }}{% endif %}" autocomplete="off">
|
||||||
|
</div>
|
||||||
|
<label for="config_kepubifypath">{{_('Path to Kepubify E-Book Converter')}}</label>
|
||||||
|
<div class="form-group input-group">
|
||||||
|
<input type="text" class="form-control" id="config_kepubifypath" name="config_kepubifypath" value="{% if config.config_kepubifypath != None %}{{ config.config_kepubifypath }}{% endif %}" autocomplete="off">
|
||||||
|
<span class="input-group-btn">
|
||||||
|
<button type="button" id="kepubify_path" class="btn btn-default"><span class="glyphicon glyphicon-folder-open"></span></button>
|
||||||
|
</span>
|
||||||
</div>
|
</div>
|
||||||
<div data-related="calibre">
|
{% if feature_support['rar'] %}
|
||||||
<div class="form-group">
|
<label for="config_rarfile_location">{{_('Location of Unrar binary')}}</label>
|
||||||
<label for="config_calibre">{{_('E-Book converter settings')}}</label>
|
<div class="form-group input-group">
|
||||||
<input type="text" class="form-control" id="config_calibre" name="config_calibre" value="{% if config.config_calibre != None %}{{ config.config_calibre }}{% endif %}" autocomplete="off">
|
<input type="text" class="form-control" id="config_rarfile_location" name="config_rarfile_location" value="{% if config.config_rarfile_location != None %}{{ config.config_rarfile_location }}{% endif %}" autocomplete="off">
|
||||||
</div>
|
<span class="input-group-btn">
|
||||||
<div class="form-group">
|
<button type="button" id="unrar_path" class="btn btn-default"><span class="glyphicon glyphicon-folder-open"></span></button>
|
||||||
<label for="config_calibre">{{_('Path to convertertool')}}</label>
|
</span>
|
||||||
<input type="text" class="form-control" id="config_converterpath" name="config_converterpath" value="{% if config.config_converterpath != None %}{{ config.config_converterpath }}{% endif %}" autocomplete="off">
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
{% if feature_support['rar'] %}
|
{% endif %}
|
||||||
<div class="form-group">
|
|
||||||
<label for="config_rarfile_location">{{_('Location of Unrar binary')}}</label>
|
|
||||||
<input type="text" class="form-control" name="config_rarfile_location" id="config_rarfile_location" value="{% if config.config_rarfile_location != None %}{{ config.config_rarfile_location }}{% endif %}" autocomplete="off">
|
|
||||||
</div>
|
|
||||||
{% endif %}
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -6,7 +6,7 @@
|
||||||
{% block body %}
|
{% block body %}
|
||||||
<div class="discover">
|
<div class="discover">
|
||||||
<h2>{{title}}</h2>
|
<h2>{{title}}</h2>
|
||||||
<form role="form" method="POST" autocomplete="off">
|
<form role="form" method="POST" autocomplete="off" class="col-md-10 col-lg-6">
|
||||||
<div class="panel-group">
|
<div class="panel-group">
|
||||||
<div class="panel panel-default">
|
<div class="panel panel-default">
|
||||||
<div class="panel-heading">
|
<div class="panel-heading">
|
||||||
|
|
|
@ -202,6 +202,7 @@
|
||||||
</label>
|
</label>
|
||||||
</form>
|
</form>
|
||||||
</p>
|
</p>
|
||||||
|
{% if g.user.check_visibility(32768) %}
|
||||||
<p>
|
<p>
|
||||||
<form id="archived_form" action="{{ url_for('web.toggle_archived', book_id=entry.id)}}" method="POST">
|
<form id="archived_form" action="{{ url_for('web.toggle_archived', book_id=entry.id)}}" method="POST">
|
||||||
<label class="block-label">
|
<label class="block-label">
|
||||||
|
@ -210,6 +211,7 @@
|
||||||
</label>
|
</label>
|
||||||
</form>
|
</form>
|
||||||
</p>
|
</p>
|
||||||
|
{% endif %}
|
||||||
</div>
|
</div>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|
||||||
|
|
|
@ -6,14 +6,14 @@
|
||||||
{% block body %}
|
{% block body %}
|
||||||
<div class="discover">
|
<div class="discover">
|
||||||
<h1>{{title}}</h1>
|
<h1>{{title}}</h1>
|
||||||
<form role="form" method="POST">
|
<form role="form" class="col-md-10 col-lg-6" method="POST">
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<label for="mail_server">{{_('SMTP Hostname')}}</label>
|
<label for="mail_server">{{_('SMTP Hostname')}}</label>
|
||||||
<input type="text" class="form-control" name="mail_server" id="mail_server" value="{{content.mail_server}}">
|
<input type="text" class="form-control" name="mail_server" id="mail_server" value="{{content.mail_server}}">
|
||||||
</div>
|
</div>
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<label for="mail_port">{{_('SMTP Port')}}</label>
|
<label for="mail_port">{{_('SMTP Port')}}</label>
|
||||||
<input type="text" class="form-control" name="mail_port" id="mail_port" value="{{content.mail_port}}">
|
<input type="number" min="1" max="65535" step="1" class="form-control" name="mail_port" id="mail_port" value="{% if content.mail_port != None %}{{ content.mail_port }}{% endif %}" autocomplete="off">
|
||||||
</div>
|
</div>
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<label for="mail_use_ssl">{{_('Encryption')}}</label>
|
<label for="mail_use_ssl">{{_('Encryption')}}</label>
|
||||||
|
@ -35,11 +35,19 @@
|
||||||
<label for="mail_from">{{_('From E-mail')}}</label>
|
<label for="mail_from">{{_('From E-mail')}}</label>
|
||||||
<input type="text" class="form-control" name="mail_from" id="mail_from" value="{{content.mail_from}}">
|
<input type="text" class="form-control" name="mail_from" id="mail_from" value="{{content.mail_from}}">
|
||||||
</div>
|
</div>
|
||||||
|
<label for="mail_size">{{_('Attachment Size Limit')}}</label>
|
||||||
|
<div class="form-group input-group">
|
||||||
|
<input type="number" min="1" max="600" step="1" class="form-control" name="mail_size" id="mail_size" value="{% if content.mail_size != None %}{{ (content.mail_size / 1024 / 1024)|int }}{% endif %}">
|
||||||
|
<span class="input-group-btn">
|
||||||
|
<button type="button" id="attachement_size" class="btn btn-default" disabled>MB</button>
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
<button type="submit" name="submit" value="submit" class="btn btn-default">{{_('Save')}}</button>
|
<button type="submit" name="submit" value="submit" class="btn btn-default">{{_('Save')}}</button>
|
||||||
<button type="submit" name="test" value="test" class="btn btn-default">{{_('Save and Send Test E-mail')}}</button>
|
<button type="submit" name="test" value="test" class="btn btn-default">{{_('Save and Send Test E-mail')}}</button>
|
||||||
<a href="{{ url_for('admin.admin') }}" id="back" class="btn btn-default">{{_('Cancel')}}</a>
|
<a href="{{ url_for('admin.admin') }}" id="back" class="btn btn-default">{{_('Cancel')}}</a>
|
||||||
</form>
|
</form>
|
||||||
{% if g.allow_registration %}
|
{% if g.allow_registration %}
|
||||||
|
<div class="col-md-10 col-lg-6">
|
||||||
<h2>{{_('Allowed Domains (Whitelist)')}}</h2>
|
<h2>{{_('Allowed Domains (Whitelist)')}}</h2>
|
||||||
<form id="domain_add_allow" action="{{ url_for('admin.add_domain',allow=1)}}" method="POST">
|
<form id="domain_add_allow" action="{{ url_for('admin.add_domain',allow=1)}}" method="POST">
|
||||||
<div class="form-group required">
|
<div class="form-group required">
|
||||||
|
@ -74,6 +82,7 @@
|
||||||
</div>
|
</div>
|
||||||
<button id="domain_deny_submit" class="btn btn-default">{{_('Add')}}</button>
|
<button id="domain_deny_submit" class="btn btn-default">{{_('Add')}}</button>
|
||||||
</form>
|
</form>
|
||||||
|
</div>
|
||||||
|
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</div>
|
</div>
|
||||||
|
|
49
cps/templates/grid.html
Normal file
49
cps/templates/grid.html
Normal file
|
@ -0,0 +1,49 @@
|
||||||
|
{% extends "layout.html" %}
|
||||||
|
{% block body %}
|
||||||
|
<h1 class="{{page}}">{{_(title)}}</h1>
|
||||||
|
|
||||||
|
<div class="filterheader hidden-xs hidden-sm">
|
||||||
|
{% if entries.__len__() %}
|
||||||
|
{% if entries[0][0].sort %}
|
||||||
|
<button id="sort_name" class="btn btn-primary"><b>B,A <-> A B</b></button>
|
||||||
|
{% endif %}
|
||||||
|
{% endif %}
|
||||||
|
<button id="desc" class="btn btn-primary"><span class="glyphicon glyphicon-sort-by-alphabet"></span></button>
|
||||||
|
<button id="asc" class="btn btn-primary"><span class="glyphicon glyphicon-sort-by-alphabet-alt"></span></button>
|
||||||
|
{% if charlist|length %}
|
||||||
|
<button id="all" class="btn btn-primary">{{_('All')}}</button>
|
||||||
|
{% endif %}
|
||||||
|
<div class="btn-group character" role="group">
|
||||||
|
{% for char in charlist%}
|
||||||
|
<button class="btn btn-primary char">{{char.char}}</button>
|
||||||
|
{% endfor %}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<button class="update-view btn btn-primary" href="#" data-target="series_view" data-view="list">List</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{% if entries[0] %}
|
||||||
|
<div id="list" class="row">
|
||||||
|
{% for entry in entries %}
|
||||||
|
<div class="col-sm-3 col-lg-2 col-xs-6 book sortable" {% if entry[0].sort %}data-name="{{entry[0].series[0].name}}"{% endif %} data-id="{% if entry[0].series[0].name %}{{entry[0].series[0].name}}{% endif %}">
|
||||||
|
<div class="cover">
|
||||||
|
<a href="{{url_for('web.books_list', data=data, sort='new', book_id=entry[0].series[0].id )}}">
|
||||||
|
<img src="{{ url_for('web.get_cover', book_id=entry[0].id) }}" alt="{{ entry[0].name }}"/>
|
||||||
|
<span class="badge">{{entry.count}}</span>
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
<div class="meta">
|
||||||
|
<a href="{{url_for('web.books_list', data=data, sort='new', book_id=entry[0].series[0].id )}}">
|
||||||
|
<p class="title">{{entry[0].series[0].name|shortentitle}}</p>
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{% endfor %}
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
|
||||||
|
{% endblock %}
|
||||||
|
{% block js %}
|
||||||
|
<script src="{{ url_for('static', filename='js/filter_grid.js') }}"></script>
|
||||||
|
{% endblock %}
|
|
@ -17,6 +17,7 @@
|
||||||
<link href="{{ url_for('static', filename='css/upload.css') }}" rel="stylesheet" media="screen">
|
<link href="{{ url_for('static', filename='css/upload.css') }}" rel="stylesheet" media="screen">
|
||||||
{% if g.current_theme == 1 %}
|
{% if g.current_theme == 1 %}
|
||||||
<link href="{{ url_for('static', filename='css/caliBlur.min.css') }}" rel="stylesheet" media="screen">
|
<link href="{{ url_for('static', filename='css/caliBlur.min.css') }}" rel="stylesheet" media="screen">
|
||||||
|
<link href="{{ url_for('static', filename='css/caliBlur_override.css') }}" rel="stylesheet" media="screen">
|
||||||
{% endif %}
|
{% endif %}
|
||||||
<!-- HTML5 Shim and Respond.js IE8 support of HTML5 elements and media queries -->
|
<!-- HTML5 Shim and Respond.js IE8 support of HTML5 elements and media queries -->
|
||||||
<!-- WARNING: Respond.js doesn't work if you view the page via file:// -->
|
<!-- WARNING: Respond.js doesn't work if you view the page via file:// -->
|
||||||
|
@ -25,7 +26,7 @@
|
||||||
<script src="https://oss.maxcdn.com/libs/respond.js/1.3.0/respond.min.js"></script>
|
<script src="https://oss.maxcdn.com/libs/respond.js/1.3.0/respond.min.js"></script>
|
||||||
<![endif]-->
|
<![endif]-->
|
||||||
</head>
|
</head>
|
||||||
<body class="{{ page }}" data-text="{{_('Home')}}" data-textback="{{_('Back')}}">
|
<body class="{{ page }} {{ bodyClass }}" data-text="{{_('Home')}}" data-textback="{{_('Back')}}">
|
||||||
<!-- Static navbar -->
|
<!-- Static navbar -->
|
||||||
<div class="navbar navbar-default navbar-static-top" role="navigation">
|
<div class="navbar navbar-default navbar-static-top" role="navigation">
|
||||||
<div class="container-fluid">
|
<div class="container-fluid">
|
||||||
|
|
|
@ -18,6 +18,10 @@
|
||||||
<button class="btn btn-primary char">{{char.char}}</button>
|
<button class="btn btn-primary char">{{char.char}}</button>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
{% if data == "series" %}
|
||||||
|
<button class="update-view btn btn-primary" href="#" data-target="series_view" data-view="grid">Grid</button>
|
||||||
|
{% endif %}
|
||||||
</div>
|
</div>
|
||||||
<div class="container">
|
<div class="container">
|
||||||
<div id="list" class="col-xs-12 col-sm-6">
|
<div id="list" class="col-xs-12 col-sm-6">
|
||||||
|
|
|
@ -3,10 +3,12 @@
|
||||||
<div class="well col-sm-6 col-sm-offset-2">
|
<div class="well col-sm-6 col-sm-offset-2">
|
||||||
<h2 style="margin-top: 0">{{_('Register New Account')}}</h2>
|
<h2 style="margin-top: 0">{{_('Register New Account')}}</h2>
|
||||||
<form method="POST" role="form">
|
<form method="POST" role="form">
|
||||||
|
{% if not config.config_register_email %}
|
||||||
<div class="form-group required">
|
<div class="form-group required">
|
||||||
<label for="nickname">{{_('Username')}}</label>
|
<label for="nickname">{{_('Username')}}</label>
|
||||||
<input type="text" class="form-control" id="nickname" name="nickname" placeholder="{{_('Choose a username')}}" required>
|
<input type="text" class="form-control" id="nickname" name="nickname" placeholder="{{_('Choose a username')}}" required>
|
||||||
</div>
|
</div>
|
||||||
|
{% endif %}
|
||||||
<div class="form-group required">
|
<div class="form-group required">
|
||||||
<label for="email">{{_('E-mail Address')}}</label>
|
<label for="email">{{_('E-mail Address')}}</label>
|
||||||
<input type="email" class="form-control" id="email" name="email" placeholder="{{_('Your email address')}}" required>
|
<input type="email" class="form-control" id="email" name="email" placeholder="{{_('Your email address')}}" required>
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
{% extends "layout.html" %}
|
{% extends "layout.html" %}
|
||||||
{% block body %}
|
{% block body %}
|
||||||
<div class="col-sm-8">
|
<div class="col-md-10 col-lg-6">
|
||||||
<form role="form" id="search" action="{{ url_for('web.advanced_search') }}" method="GET">
|
<form role="form" id="search" action="{{ url_for('web.advanced_search') }}" method="GET">
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<label for="book_title">{{_('Book Title')}}</label>
|
<label for="book_title">{{_('Book Title')}}</label>
|
||||||
|
|
|
@ -3,6 +3,7 @@
|
||||||
<div class="discover">
|
<div class="discover">
|
||||||
<h1>{{title}}</h1>
|
<h1>{{title}}</h1>
|
||||||
<form role="form" method="POST" autocomplete="off">
|
<form role="form" method="POST" autocomplete="off">
|
||||||
|
<div class="col-md-10 col-lg-8">
|
||||||
{% if new_user or ( g.user and content.nickname != "Guest" and g.user.role_admin() ) %}
|
{% if new_user or ( g.user and content.nickname != "Guest" and g.user.role_admin() ) %}
|
||||||
<div class="form-group required">
|
<div class="form-group required">
|
||||||
<label for="nickname">{{_('Username')}}</label>
|
<label for="nickname">{{_('Username')}}</label>
|
||||||
|
@ -65,6 +66,7 @@
|
||||||
<div class="btn btn-danger" id="config_delete_kobo_token" data-toggle="modal" data-target="#modalDeleteToken" data-remote="false" {% if not content.remote_auth_token.first() %} style="display: none;" {% endif %}>{{_('Delete')}}</div>
|
<div class="btn btn-danger" id="config_delete_kobo_token" data-toggle="modal" data-target="#modalDeleteToken" data-remote="false" {% if not content.remote_auth_token.first() %} style="display: none;" {% endif %}>{{_('Delete')}}</div>
|
||||||
</div>
|
</div>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
</div>
|
||||||
<div class="col-sm-6">
|
<div class="col-sm-6">
|
||||||
{% for element in sidebar %}
|
{% for element in sidebar %}
|
||||||
{% if element['config_show'] %}
|
{% if element['config_show'] %}
|
||||||
|
|
Binary file not shown.
File diff suppressed because it is too large
Load Diff
Binary file not shown.
File diff suppressed because it is too large
Load Diff
Binary file not shown.
File diff suppressed because it is too large
Load Diff
Binary file not shown.
File diff suppressed because it is too large
Load Diff
Binary file not shown.
File diff suppressed because it is too large
Load Diff
Binary file not shown.
File diff suppressed because it is too large
Load Diff
Binary file not shown.
File diff suppressed because it is too large
Load Diff
Binary file not shown.
File diff suppressed because it is too large
Load Diff
Binary file not shown.
File diff suppressed because it is too large
Load Diff
Binary file not shown.
File diff suppressed because it is too large
Load Diff
Binary file not shown.
File diff suppressed because it is too large
Load Diff
Binary file not shown.
File diff suppressed because it is too large
Load Diff
Binary file not shown.
File diff suppressed because it is too large
Load Diff
Binary file not shown.
File diff suppressed because it is too large
Load Diff
Binary file not shown.
File diff suppressed because it is too large
Load Diff
Binary file not shown.
File diff suppressed because it is too large
Load Diff
26
cps/ub.py
26
cps/ub.py
|
@ -49,6 +49,7 @@ from . import constants
|
||||||
|
|
||||||
|
|
||||||
session = None
|
session = None
|
||||||
|
app_DB_path = None
|
||||||
Base = declarative_base()
|
Base = declarative_base()
|
||||||
|
|
||||||
|
|
||||||
|
@ -107,6 +108,11 @@ def get_sidebar_config(kwargs=None):
|
||||||
{"glyph": "glyphicon-trash", "text": _('Archived Books'), "link": 'web.books_list', "id": "archived",
|
{"glyph": "glyphicon-trash", "text": _('Archived Books'), "link": 'web.books_list', "id": "archived",
|
||||||
"visibility": constants.SIDEBAR_ARCHIVED, 'public': (not g.user.is_anonymous), "page": "archived",
|
"visibility": constants.SIDEBAR_ARCHIVED, 'public': (not g.user.is_anonymous), "page": "archived",
|
||||||
"show_text": _('Show archived books'), "config_show": content})
|
"show_text": _('Show archived books'), "config_show": content})
|
||||||
|
'''sidebar.append(
|
||||||
|
{"glyph": "glyphicon-th-list", "text": _('Books List'), "link": 'web.books_list', "id": "list",
|
||||||
|
"visibility": constants.SIDEBAR_LIST, 'public': (not g.user.is_anonymous), "page": "list",
|
||||||
|
"show_text": _('Show Books List'), "config_show": content})'''
|
||||||
|
|
||||||
return sidebar
|
return sidebar
|
||||||
|
|
||||||
|
|
||||||
|
@ -211,6 +217,7 @@ class User(UserBase, Base):
|
||||||
denied_column_value = Column(String, default="")
|
denied_column_value = Column(String, default="")
|
||||||
allowed_column_value = Column(String, default="")
|
allowed_column_value = Column(String, default="")
|
||||||
remote_auth_token = relationship('RemoteAuthToken', backref='user', lazy='dynamic')
|
remote_auth_token = relationship('RemoteAuthToken', backref='user', lazy='dynamic')
|
||||||
|
series_view = Column(String(10), default="list")
|
||||||
|
|
||||||
|
|
||||||
if oauth_support:
|
if oauth_support:
|
||||||
|
@ -251,6 +258,7 @@ class Anonymous(AnonymousUserMixin, UserBase):
|
||||||
self.allowed_tags = data.allowed_tags
|
self.allowed_tags = data.allowed_tags
|
||||||
self.denied_column_value = data.denied_column_value
|
self.denied_column_value = data.denied_column_value
|
||||||
self.allowed_column_value = data.allowed_column_value
|
self.allowed_column_value = data.allowed_column_value
|
||||||
|
self.series_view = data.series_view
|
||||||
|
|
||||||
def role_admin(self):
|
def role_admin(self):
|
||||||
return False
|
return False
|
||||||
|
@ -447,7 +455,7 @@ class RemoteAuthToken(Base):
|
||||||
|
|
||||||
|
|
||||||
# Migrate database to current version, has to be updated after every database change. Currently migration from
|
# Migrate database to current version, has to be updated after every database change. Currently migration from
|
||||||
# everywhere to curent should work. Migration is done by checking if relevant coloums are existing, and than adding
|
# everywhere to current should work. Migration is done by checking if relevant columns are existing, and than adding
|
||||||
# rows with SQL commands
|
# rows with SQL commands
|
||||||
def migrate_Database(session):
|
def migrate_Database(session):
|
||||||
engine = session.bind
|
engine = session.bind
|
||||||
|
@ -557,6 +565,12 @@ def migrate_Database(session):
|
||||||
conn.execute("ALTER TABLE user ADD column `denied_column_value` DEFAULT ''")
|
conn.execute("ALTER TABLE user ADD column `denied_column_value` DEFAULT ''")
|
||||||
conn.execute("ALTER TABLE user ADD column `allowed_column_value` DEFAULT ''")
|
conn.execute("ALTER TABLE user ADD column `allowed_column_value` DEFAULT ''")
|
||||||
session.commit()
|
session.commit()
|
||||||
|
try:
|
||||||
|
session.query(exists().where(User.series_view)).scalar()
|
||||||
|
except exc.OperationalError:
|
||||||
|
conn = engine.connect()
|
||||||
|
conn.execute("ALTER TABLE user ADD column `series_view` VARCHAR(10) DEFAULT 'list'")
|
||||||
|
|
||||||
if session.query(User).filter(User.role.op('&')(constants.ROLE_ANONYMOUS) == constants.ROLE_ANONYMOUS).first() \
|
if session.query(User).filter(User.role.op('&')(constants.ROLE_ANONYMOUS) == constants.ROLE_ANONYMOUS).first() \
|
||||||
is None:
|
is None:
|
||||||
create_anonymous_user(session)
|
create_anonymous_user(session)
|
||||||
|
@ -568,7 +582,7 @@ def migrate_Database(session):
|
||||||
# Create new table user_id and copy contents of table user into it
|
# Create new table user_id and copy contents of table user into it
|
||||||
conn = engine.connect()
|
conn = engine.connect()
|
||||||
conn.execute("CREATE TABLE user_id (id INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT,"
|
conn.execute("CREATE TABLE user_id (id INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT,"
|
||||||
" nickname VARCHAR(64),"
|
"nickname VARCHAR(64),"
|
||||||
"email VARCHAR(120),"
|
"email VARCHAR(120),"
|
||||||
"role SMALLINT,"
|
"role SMALLINT,"
|
||||||
"password VARCHAR,"
|
"password VARCHAR,"
|
||||||
|
@ -576,10 +590,11 @@ def migrate_Database(session):
|
||||||
"locale VARCHAR(2),"
|
"locale VARCHAR(2),"
|
||||||
"sidebar_view INTEGER,"
|
"sidebar_view INTEGER,"
|
||||||
"default_language VARCHAR(3),"
|
"default_language VARCHAR(3),"
|
||||||
|
"series_view VARCHAR(10),"
|
||||||
"UNIQUE (nickname),"
|
"UNIQUE (nickname),"
|
||||||
"UNIQUE (email))")
|
"UNIQUE (email))")
|
||||||
conn.execute("INSERT INTO user_id(id, nickname, email, role, password, kindle_mail,locale,"
|
conn.execute("INSERT INTO user_id(id, nickname, email, role, password, kindle_mail,locale,"
|
||||||
"sidebar_view, default_language) "
|
"sidebar_view, default_language, series_view) "
|
||||||
"SELECT id, nickname, email, role, password, kindle_mail, locale,"
|
"SELECT id, nickname, email, role, password, kindle_mail, locale,"
|
||||||
"sidebar_view, default_language FROM user")
|
"sidebar_view, default_language FROM user")
|
||||||
# delete old user table and rename new user_id table to user:
|
# delete old user table and rename new user_id table to user:
|
||||||
|
@ -616,8 +631,7 @@ def delete_download(book_id):
|
||||||
session.query(Downloads).filter(book_id == Downloads.book_id).delete()
|
session.query(Downloads).filter(book_id == Downloads.book_id).delete()
|
||||||
session.commit()
|
session.commit()
|
||||||
|
|
||||||
|
# Generate user Guest (translated text), as anonymous user, no rights
|
||||||
# Generate user Guest (translated text), as anoymous user, no rights
|
|
||||||
def create_anonymous_user(session):
|
def create_anonymous_user(session):
|
||||||
user = User()
|
user = User()
|
||||||
user.nickname = "Guest"
|
user.nickname = "Guest"
|
||||||
|
@ -651,7 +665,9 @@ def create_admin_user(session):
|
||||||
def init_db(app_db_path):
|
def init_db(app_db_path):
|
||||||
# Open session for database connection
|
# Open session for database connection
|
||||||
global session
|
global session
|
||||||
|
global app_DB_path
|
||||||
|
|
||||||
|
app_DB_path = app_db_path
|
||||||
engine = create_engine(u'sqlite:///{0}'.format(app_db_path), echo=False)
|
engine = create_engine(u'sqlite:///{0}'.format(app_db_path), echo=False)
|
||||||
|
|
||||||
Session = sessionmaker()
|
Session = sessionmaker()
|
||||||
|
|
|
@ -40,7 +40,7 @@ try:
|
||||||
from wand.exceptions import PolicyError
|
from wand.exceptions import PolicyError
|
||||||
use_generic_pdf_cover = False
|
use_generic_pdf_cover = False
|
||||||
except (ImportError, RuntimeError) as e:
|
except (ImportError, RuntimeError) as e:
|
||||||
log.debug('cannot import Image, generating pdf covers for pdf uploads will not work: %s', e)
|
log.debug('Cannot import Image, generating pdf covers for pdf uploads will not work: %s', e)
|
||||||
use_generic_pdf_cover = True
|
use_generic_pdf_cover = True
|
||||||
|
|
||||||
try:
|
try:
|
||||||
|
@ -48,21 +48,21 @@ try:
|
||||||
from PyPDF2 import __version__ as PyPdfVersion
|
from PyPDF2 import __version__ as PyPdfVersion
|
||||||
use_pdf_meta = True
|
use_pdf_meta = True
|
||||||
except ImportError as e:
|
except ImportError as e:
|
||||||
log.debug('cannot import PyPDF2, extracting pdf metadata will not work: %s', e)
|
log.debug('Cannot import PyPDF2, extracting pdf metadata will not work: %s', e)
|
||||||
use_pdf_meta = False
|
use_pdf_meta = False
|
||||||
|
|
||||||
try:
|
try:
|
||||||
from . import epub
|
from . import epub
|
||||||
use_epub_meta = True
|
use_epub_meta = True
|
||||||
except ImportError as e:
|
except ImportError as e:
|
||||||
log.debug('cannot import epub, extracting epub metadata will not work: %s', e)
|
log.debug('Cannot import epub, extracting epub metadata will not work: %s', e)
|
||||||
use_epub_meta = False
|
use_epub_meta = False
|
||||||
|
|
||||||
try:
|
try:
|
||||||
from . import fb2
|
from . import fb2
|
||||||
use_fb2_meta = True
|
use_fb2_meta = True
|
||||||
except ImportError as e:
|
except ImportError as e:
|
||||||
log.debug('cannot import fb2, extracting fb2 metadata will not work: %s', e)
|
log.debug('Cannot import fb2, extracting fb2 metadata will not work: %s', e)
|
||||||
use_fb2_meta = False
|
use_fb2_meta = False
|
||||||
|
|
||||||
try:
|
try:
|
||||||
|
@ -70,20 +70,17 @@ try:
|
||||||
from PIL import __version__ as PILversion
|
from PIL import __version__ as PILversion
|
||||||
use_PIL = True
|
use_PIL = True
|
||||||
except ImportError as e:
|
except ImportError as e:
|
||||||
log.debug('cannot import Pillow, using png and webp images as cover will not work: %s', e)
|
log.debug('Cannot import Pillow, using png and webp images as cover will not work: %s', e)
|
||||||
use_PIL = False
|
use_PIL = False
|
||||||
|
|
||||||
__author__ = 'lemmsh'
|
|
||||||
|
|
||||||
|
|
||||||
def process(tmp_file_path, original_file_name, original_file_extension, rarExecutable):
|
def process(tmp_file_path, original_file_name, original_file_extension, rarExecutable):
|
||||||
"""Get the metadata for tmp_file_path."""
|
|
||||||
meta = None
|
meta = None
|
||||||
extension_upper = original_file_extension.upper()
|
extension_upper = original_file_extension.upper()
|
||||||
try:
|
try:
|
||||||
if ".PDF" == extension_upper:
|
if ".PDF" == extension_upper:
|
||||||
meta = pdf_meta(tmp_file_path, original_file_name, original_file_extension)
|
meta = pdf_meta(tmp_file_path, original_file_name, original_file_extension)
|
||||||
elif ".EPUB" == extension_upper and use_epub_meta is True:
|
elif extension_upper in [".KEPUB", ".EPUB"] and use_epub_meta is True:
|
||||||
meta = epub.get_epub_info(tmp_file_path, original_file_name, original_file_extension)
|
meta = epub.get_epub_info(tmp_file_path, original_file_name, original_file_extension)
|
||||||
elif ".FB2" == extension_upper and use_fb2_meta is True:
|
elif ".FB2" == extension_upper and use_fb2_meta is True:
|
||||||
meta = fb2.get_fb2_info(tmp_file_path, original_file_extension)
|
meta = fb2.get_fb2_info(tmp_file_path, original_file_extension)
|
||||||
|
@ -182,7 +179,7 @@ def get_versions():
|
||||||
else:
|
else:
|
||||||
PILVersion = u'not installed'
|
PILVersion = u'not installed'
|
||||||
if comic.use_comic_meta:
|
if comic.use_comic_meta:
|
||||||
ComicVersion = u'installed'
|
ComicVersion = comic.comic_version or u'installed'
|
||||||
else:
|
else:
|
||||||
ComicVersion = u'not installed'
|
ComicVersion = u'not installed'
|
||||||
return {'Image Magick': IVersion,
|
return {'Image Magick': IVersion,
|
||||||
|
|
370
cps/web.py
370
cps/web.py
|
@ -37,9 +37,9 @@ from flask import Blueprint
|
||||||
from flask import render_template, request, redirect, send_from_directory, make_response, g, flash, abort, url_for
|
from flask import render_template, request, redirect, send_from_directory, make_response, g, flash, abort, url_for
|
||||||
from flask_babel import gettext as _
|
from flask_babel import gettext as _
|
||||||
from flask_login import login_user, logout_user, login_required, current_user
|
from flask_login import login_user, logout_user, login_required, current_user
|
||||||
from sqlalchemy.exc import IntegrityError
|
from sqlalchemy.exc import IntegrityError, InvalidRequestError, OperationalError
|
||||||
from sqlalchemy.sql.expression import text, func, true, false, not_, and_, or_
|
from sqlalchemy.sql.expression import text, func, true, false, not_, and_, or_
|
||||||
from werkzeug.exceptions import default_exceptions
|
from werkzeug.exceptions import default_exceptions, InternalServerError
|
||||||
from sqlalchemy.sql.functions import coalesce
|
from sqlalchemy.sql.functions import coalesce
|
||||||
try:
|
try:
|
||||||
from werkzeug.exceptions import FailedDependency
|
from werkzeug.exceptions import FailedDependency
|
||||||
|
@ -48,13 +48,13 @@ except ImportError:
|
||||||
from werkzeug.datastructures import Headers
|
from werkzeug.datastructures import Headers
|
||||||
from werkzeug.security import generate_password_hash, check_password_hash
|
from werkzeug.security import generate_password_hash, check_password_hash
|
||||||
|
|
||||||
from . import constants, logger, isoLanguages, services, worker
|
from . import constants, logger, isoLanguages, services, worker, cli
|
||||||
from . import searched_ids, lm, babel, db, ub, config, get_locale, app
|
from . import searched_ids, lm, babel, db, ub, config, get_locale, app
|
||||||
|
from . import calibre_db
|
||||||
from .gdriveutils import getFileFromEbooksFolder, do_gdrive_download
|
from .gdriveutils import getFileFromEbooksFolder, do_gdrive_download
|
||||||
from .helper import common_filters, get_search_results, fill_indexpage, fill_indexpage_with_archived_books, \
|
from .helper import check_valid_domain, render_task_status, json_serial, \
|
||||||
speaking_language, check_valid_domain, order_authors, get_typeahead, render_task_status, json_serial, \
|
|
||||||
get_cc_columns, get_book_cover, get_download_link, send_mail, generate_random_password, \
|
get_cc_columns, get_book_cover, get_download_link, send_mail, generate_random_password, \
|
||||||
send_registration_mail, check_send_to_kindle, check_read_formats, lcase, tags_filters, reset_password
|
send_registration_mail, check_send_to_kindle, check_read_formats, tags_filters, reset_password
|
||||||
from .pagination import Pagination
|
from .pagination import Pagination
|
||||||
from .redirect import redirect_back
|
from .redirect import redirect_back
|
||||||
|
|
||||||
|
@ -124,6 +124,21 @@ if feature_support['ldap']:
|
||||||
log.debug('LDAP server not accessible while trying to login to opds feed')
|
log.debug('LDAP server not accessible while trying to login to opds feed')
|
||||||
return error_http(FailedDependency())
|
return error_http(FailedDependency())
|
||||||
|
|
||||||
|
# @app.errorhandler(InvalidRequestError)
|
||||||
|
#@app.errorhandler(OperationalError)
|
||||||
|
#def handle_db_exception(e):
|
||||||
|
# db.session.rollback()
|
||||||
|
# log.error('Database request error: %s',e)
|
||||||
|
# return internal_error(InternalServerError(e))
|
||||||
|
|
||||||
|
@app.after_request
|
||||||
|
def add_security_headers(resp):
|
||||||
|
# resp.headers['Content-Security-Policy']= "script-src 'self' https://www.googleapis.com https://api.douban.com https://comicvine.gamespot.com;"
|
||||||
|
resp.headers['X-Content-Type-Options'] = 'nosniff'
|
||||||
|
resp.headers['X-Frame-Options'] = 'SAMEORIGIN'
|
||||||
|
resp.headers['X-XSS-Protection'] = '1; mode=block'
|
||||||
|
# resp.headers['Strict-Transport-Security'] = 'max-age=31536000; includeSubDomains'
|
||||||
|
return resp
|
||||||
|
|
||||||
web = Blueprint('web', __name__)
|
web = Blueprint('web', __name__)
|
||||||
log = logger.create()
|
log = logger.create()
|
||||||
|
@ -423,21 +438,24 @@ def toggle_read(book_id):
|
||||||
ub.session.commit()
|
ub.session.commit()
|
||||||
else:
|
else:
|
||||||
try:
|
try:
|
||||||
db.update_title_sort(config)
|
calibre_db.update_title_sort(config)
|
||||||
book = db.session.query(db.Books).filter(db.Books.id == book_id).filter(common_filters()).first()
|
book = calibre_db.get_filtered_book(book_id)
|
||||||
read_status = getattr(book, 'custom_column_' + str(config.config_read_column))
|
read_status = getattr(book, 'custom_column_' + str(config.config_read_column))
|
||||||
if len(read_status):
|
if len(read_status):
|
||||||
read_status[0].value = not read_status[0].value
|
read_status[0].value = not read_status[0].value
|
||||||
db.session.commit()
|
calibre_db.session.commit()
|
||||||
else:
|
else:
|
||||||
cc_class = db.cc_classes[config.config_read_column]
|
cc_class = db.cc_classes[config.config_read_column]
|
||||||
new_cc = cc_class(value=1, book=book_id)
|
new_cc = cc_class(value=1, book=book_id)
|
||||||
db.session.add(new_cc)
|
calibre_db.session.add(new_cc)
|
||||||
db.session.commit()
|
calibre_db.session.commit()
|
||||||
except KeyError:
|
except KeyError:
|
||||||
log.error(u"Custom Column No.%d is not exisiting in calibre database", config.config_read_column)
|
log.error(u"Custom Column No.%d is not exisiting in calibre database", config.config_read_column)
|
||||||
return ""
|
except OperationalError as e:
|
||||||
|
calibre_db.session.rollback()
|
||||||
|
log.error(u"Read status could not set: %e", e)
|
||||||
|
|
||||||
|
return ""
|
||||||
|
|
||||||
@web.route("/ajax/togglearchived/<int:book_id>", methods=['POST'])
|
@web.route("/ajax/togglearchived/<int:book_id>", methods=['POST'])
|
||||||
@login_required
|
@login_required
|
||||||
|
@ -455,11 +473,30 @@ def toggle_archived(book_id):
|
||||||
return ""
|
return ""
|
||||||
|
|
||||||
|
|
||||||
|
@web.route("/ajax/view", methods=["POST"])
|
||||||
|
@login_required
|
||||||
|
def update_view():
|
||||||
|
to_save = request.form.to_dict()
|
||||||
|
allowed_view = ['grid', 'list']
|
||||||
|
if "series_view" in to_save and to_save["series_view"] in allowed_view:
|
||||||
|
current_user.series_view = to_save["series_view"]
|
||||||
|
else:
|
||||||
|
log.error("Invalid request received: %r %r", request, to_save)
|
||||||
|
return "Invalid request", 400
|
||||||
|
|
||||||
|
try:
|
||||||
|
ub.session.commit()
|
||||||
|
except InvalidRequestError:
|
||||||
|
log.error("Invalid request received: %r ", request, )
|
||||||
|
return "Invalid request", 400
|
||||||
|
return "", 200
|
||||||
|
|
||||||
|
|
||||||
'''
|
'''
|
||||||
@web.route("/ajax/getcomic/<int:book_id>/<book_format>/<int:page>")
|
@web.route("/ajax/getcomic/<int:book_id>/<book_format>/<int:page>")
|
||||||
@login_required
|
@login_required
|
||||||
def get_comic_book(book_id, book_format, page):
|
def get_comic_book(book_id, book_format, page):
|
||||||
book = db.session.query(db.Books).filter(db.Books.id == book_id).first()
|
book = calibre_db.get_book(book_id)
|
||||||
if not book:
|
if not book:
|
||||||
return "", 204
|
return "", 204
|
||||||
else:
|
else:
|
||||||
|
@ -513,25 +550,25 @@ def get_comic_book(book_id, book_format, page):
|
||||||
@web.route("/get_authors_json", methods=['GET'])
|
@web.route("/get_authors_json", methods=['GET'])
|
||||||
@login_required_if_no_ano
|
@login_required_if_no_ano
|
||||||
def get_authors_json():
|
def get_authors_json():
|
||||||
return get_typeahead(db.Authors, request.args.get('q'), ('|', ','))
|
return calibre_db.get_typeahead(db.Authors, request.args.get('q'), ('|', ','))
|
||||||
|
|
||||||
|
|
||||||
@web.route("/get_publishers_json", methods=['GET'])
|
@web.route("/get_publishers_json", methods=['GET'])
|
||||||
@login_required_if_no_ano
|
@login_required_if_no_ano
|
||||||
def get_publishers_json():
|
def get_publishers_json():
|
||||||
return get_typeahead(db.Publishers, request.args.get('q'), ('|', ','))
|
return calibre_db.get_typeahead(db.Publishers, request.args.get('q'), ('|', ','))
|
||||||
|
|
||||||
|
|
||||||
@web.route("/get_tags_json", methods=['GET'])
|
@web.route("/get_tags_json", methods=['GET'])
|
||||||
@login_required_if_no_ano
|
@login_required_if_no_ano
|
||||||
def get_tags_json():
|
def get_tags_json():
|
||||||
return get_typeahead(db.Tags, request.args.get('q'), tag_filter=tags_filters())
|
return calibre_db.get_typeahead(db.Tags, request.args.get('q'), tag_filter=tags_filters())
|
||||||
|
|
||||||
|
|
||||||
@web.route("/get_series_json", methods=['GET'])
|
@web.route("/get_series_json", methods=['GET'])
|
||||||
@login_required_if_no_ano
|
@login_required_if_no_ano
|
||||||
def get_series_json():
|
def get_series_json():
|
||||||
return get_typeahead(db.Series, request.args.get('q'))
|
return calibre_db.get_typeahead(db.Series, request.args.get('q'))
|
||||||
|
|
||||||
|
|
||||||
@web.route("/get_languages_json", methods=['GET'])
|
@web.route("/get_languages_json", methods=['GET'])
|
||||||
|
@ -552,8 +589,8 @@ def get_languages_json():
|
||||||
@login_required_if_no_ano
|
@login_required_if_no_ano
|
||||||
def get_matching_tags():
|
def get_matching_tags():
|
||||||
tag_dict = {'tags': []}
|
tag_dict = {'tags': []}
|
||||||
q = db.session.query(db.Books)
|
q = calibre_db.session.query(db.Books)
|
||||||
db.session.connection().connection.connection.create_function("lower", 1, lcase)
|
calibre_db.session.connection().connection.connection.create_function("lower", 1, db.lcase)
|
||||||
author_input = request.args.get('author_name') or ''
|
author_input = request.args.get('author_name') or ''
|
||||||
title_input = request.args.get('book_title') or ''
|
title_input = request.args.get('book_title') or ''
|
||||||
include_tag_inputs = request.args.getlist('include_tag') or ''
|
include_tag_inputs = request.args.getlist('include_tag') or ''
|
||||||
|
@ -583,7 +620,7 @@ def get_matching_tags():
|
||||||
@web.route('/page/<int:page>')
|
@web.route('/page/<int:page>')
|
||||||
@login_required_if_no_ano
|
@login_required_if_no_ano
|
||||||
def index(page):
|
def index(page):
|
||||||
entries, random, pagination = fill_indexpage(page, db.Books, True, [db.Books.timestamp.desc()])
|
entries, random, pagination = calibre_db.fill_indexpage(page, db.Books, True, [db.Books.timestamp.desc()])
|
||||||
return render_title_template('index.html', random=random, entries=entries, pagination=pagination,
|
return render_title_template('index.html', random=random, entries=entries, pagination=pagination,
|
||||||
title=_(u"Recently Added Books"), page="root")
|
title=_(u"Recently Added Books"), page="root")
|
||||||
|
|
||||||
|
@ -610,15 +647,17 @@ def books_list(data, sort, book_id, page):
|
||||||
|
|
||||||
if data == "rated":
|
if data == "rated":
|
||||||
if current_user.check_visibility(constants.SIDEBAR_BEST_RATED):
|
if current_user.check_visibility(constants.SIDEBAR_BEST_RATED):
|
||||||
entries, random, pagination = fill_indexpage(page, db.Books, db.Books.ratings.any(db.Ratings.rating > 9),
|
entries, random, pagination = calibre_db.fill_indexpage(page,
|
||||||
order)
|
db.Books,
|
||||||
|
db.Books.ratings.any(db.Ratings.rating > 9),
|
||||||
|
order)
|
||||||
return render_title_template('index.html', random=random, entries=entries, pagination=pagination,
|
return render_title_template('index.html', random=random, entries=entries, pagination=pagination,
|
||||||
id=book_id, title=_(u"Top Rated Books"), page="rated")
|
id=book_id, title=_(u"Top Rated Books"), page="rated")
|
||||||
else:
|
else:
|
||||||
abort(404)
|
abort(404)
|
||||||
elif data == "discover":
|
elif data == "discover":
|
||||||
if current_user.check_visibility(constants.SIDEBAR_RANDOM):
|
if current_user.check_visibility(constants.SIDEBAR_RANDOM):
|
||||||
entries, __, pagination = fill_indexpage(page, db.Books, True, [func.randomblob(2)])
|
entries, __, pagination = calibre_db.fill_indexpage(page, db.Books, True, [func.randomblob(2)])
|
||||||
pagination = Pagination(1, config.config_books_per_page, config.config_books_per_page)
|
pagination = Pagination(1, config.config_books_per_page, config.config_books_per_page)
|
||||||
return render_title_template('discover.html', entries=entries, pagination=pagination, id=book_id,
|
return render_title_template('discover.html', entries=entries, pagination=pagination, id=book_id,
|
||||||
title=_(u"Discover (Random Books)"), page="discover")
|
title=_(u"Discover (Random Books)"), page="discover")
|
||||||
|
@ -647,7 +686,7 @@ def books_list(data, sort, book_id, page):
|
||||||
elif data == "archived":
|
elif data == "archived":
|
||||||
return render_archived_books(page, order)
|
return render_archived_books(page, order)
|
||||||
else:
|
else:
|
||||||
entries, random, pagination = fill_indexpage(page, db.Books, True, order)
|
entries, random, pagination = calibre_db.fill_indexpage(page, db.Books, True, order)
|
||||||
return render_title_template('index.html', random=random, entries=entries, pagination=pagination,
|
return render_title_template('index.html', random=random, entries=entries, pagination=pagination,
|
||||||
title=_(u"Books"), page="newest")
|
title=_(u"Books"), page="newest")
|
||||||
|
|
||||||
|
@ -655,7 +694,7 @@ def books_list(data, sort, book_id, page):
|
||||||
def render_hot_books(page):
|
def render_hot_books(page):
|
||||||
if current_user.check_visibility(constants.SIDEBAR_HOT):
|
if current_user.check_visibility(constants.SIDEBAR_HOT):
|
||||||
if current_user.show_detail_random():
|
if current_user.show_detail_random():
|
||||||
random = db.session.query(db.Books).filter(common_filters()) \
|
random = calibre_db.session.query(db.Books).filter(calibre_db.common_filters()) \
|
||||||
.order_by(func.random()).limit(config.config_random_books)
|
.order_by(func.random()).limit(config.config_random_books)
|
||||||
else:
|
else:
|
||||||
random = false()
|
random = false()
|
||||||
|
@ -665,7 +704,7 @@ def render_hot_books(page):
|
||||||
hot_books = all_books.offset(off).limit(config.config_books_per_page)
|
hot_books = all_books.offset(off).limit(config.config_books_per_page)
|
||||||
entries = list()
|
entries = list()
|
||||||
for book in hot_books:
|
for book in hot_books:
|
||||||
downloadBook = db.session.query(db.Books).filter(common_filters()).filter(
|
downloadBook = calibre_db.session.query(db.Books).filter(calibre_db.common_filters()).filter(
|
||||||
db.Books.id == book.Downloads.book_id).first()
|
db.Books.id == book.Downloads.book_id).first()
|
||||||
if downloadBook:
|
if downloadBook:
|
||||||
entries.append(downloadBook)
|
entries.append(downloadBook)
|
||||||
|
@ -682,15 +721,18 @@ def render_hot_books(page):
|
||||||
|
|
||||||
|
|
||||||
def render_author_books(page, author_id, order):
|
def render_author_books(page, author_id, order):
|
||||||
entries, __, pagination = fill_indexpage(page, db.Books, db.Books.authors.any(db.Authors.id == author_id),
|
entries, __, pagination = calibre_db.fill_indexpage(page,
|
||||||
[order[0], db.Series.name, db.Books.series_index],
|
db.Books,
|
||||||
db.books_series_link, db.Series)
|
db.Books.authors.any(db.Authors.id == author_id),
|
||||||
|
[order[0], db.Series.name, db.Books.series_index],
|
||||||
|
db.books_series_link,
|
||||||
|
db.Series)
|
||||||
if entries is None or not len(entries):
|
if entries is None or not len(entries):
|
||||||
flash(_(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")
|
category="error")
|
||||||
return redirect(url_for("web.index"))
|
return redirect(url_for("web.index"))
|
||||||
|
|
||||||
author = db.session.query(db.Authors).get(author_id)
|
author = calibre_db.session.query(db.Authors).get(author_id)
|
||||||
author_name = author.name.replace('|', ',')
|
author_name = author.name.replace('|', ',')
|
||||||
|
|
||||||
author_info = None
|
author_info = None
|
||||||
|
@ -705,12 +747,14 @@ def render_author_books(page, author_id, order):
|
||||||
|
|
||||||
|
|
||||||
def render_publisher_books(page, book_id, order):
|
def render_publisher_books(page, book_id, order):
|
||||||
publisher = db.session.query(db.Publishers).filter(db.Publishers.id == book_id).first()
|
publisher = calibre_db.session.query(db.Publishers).filter(db.Publishers.id == book_id).first()
|
||||||
if publisher:
|
if publisher:
|
||||||
entries, random, pagination = fill_indexpage(page, db.Books,
|
entries, random, pagination = calibre_db.fill_indexpage(page,
|
||||||
db.Books.publishers.any(db.Publishers.id == book_id),
|
db.Books,
|
||||||
[db.Series.name, order[0], db.Books.series_index],
|
db.Books.publishers.any(db.Publishers.id == book_id),
|
||||||
db.books_series_link, db.Series)
|
[db.Series.name, order[0], db.Books.series_index],
|
||||||
|
db.books_series_link,
|
||||||
|
db.Series)
|
||||||
return render_title_template('index.html', random=random, entries=entries, pagination=pagination, id=book_id,
|
return render_title_template('index.html', random=random, entries=entries, pagination=pagination, id=book_id,
|
||||||
title=_(u"Publisher: %(name)s", name=publisher.name), page="publisher")
|
title=_(u"Publisher: %(name)s", name=publisher.name), page="publisher")
|
||||||
else:
|
else:
|
||||||
|
@ -718,10 +762,12 @@ def render_publisher_books(page, book_id, order):
|
||||||
|
|
||||||
|
|
||||||
def render_series_books(page, book_id, order):
|
def render_series_books(page, book_id, order):
|
||||||
name = db.session.query(db.Series).filter(db.Series.id == book_id).first()
|
name = calibre_db.session.query(db.Series).filter(db.Series.id == book_id).first()
|
||||||
if name:
|
if name:
|
||||||
entries, random, pagination = fill_indexpage(page, db.Books, db.Books.series.any(db.Series.id == book_id),
|
entries, random, pagination = calibre_db.fill_indexpage(page,
|
||||||
[db.Books.series_index, order[0]])
|
db.Books,
|
||||||
|
db.Books.series.any(db.Series.id == book_id),
|
||||||
|
[db.Books.series_index, order[0]])
|
||||||
return render_title_template('index.html', random=random, pagination=pagination, entries=entries, id=book_id,
|
return render_title_template('index.html', random=random, pagination=pagination, entries=entries, id=book_id,
|
||||||
title=_(u"Series: %(serie)s", serie=name.name), page="series")
|
title=_(u"Series: %(serie)s", serie=name.name), page="series")
|
||||||
else:
|
else:
|
||||||
|
@ -729,9 +775,11 @@ def render_series_books(page, book_id, order):
|
||||||
|
|
||||||
|
|
||||||
def render_ratings_books(page, book_id, order):
|
def render_ratings_books(page, book_id, order):
|
||||||
name = db.session.query(db.Ratings).filter(db.Ratings.id == book_id).first()
|
name = calibre_db.session.query(db.Ratings).filter(db.Ratings.id == book_id).first()
|
||||||
entries, random, pagination = fill_indexpage(page, db.Books, db.Books.ratings.any(db.Ratings.id == book_id),
|
entries, random, pagination = calibre_db.fill_indexpage(page,
|
||||||
[db.Books.timestamp.desc(), order[0]])
|
db.Books,
|
||||||
|
db.Books.ratings.any(db.Ratings.id == book_id),
|
||||||
|
[db.Books.timestamp.desc(), order[0]])
|
||||||
if name and name.rating <= 10:
|
if name and name.rating <= 10:
|
||||||
return render_title_template('index.html', random=random, pagination=pagination, entries=entries, id=book_id,
|
return render_title_template('index.html', random=random, pagination=pagination, entries=entries, id=book_id,
|
||||||
title=_(u"Rating: %(rating)s stars", rating=int(name.rating / 2)), page="ratings")
|
title=_(u"Rating: %(rating)s stars", rating=int(name.rating / 2)), page="ratings")
|
||||||
|
@ -740,11 +788,12 @@ def render_ratings_books(page, book_id, order):
|
||||||
|
|
||||||
|
|
||||||
def render_formats_books(page, book_id, order):
|
def render_formats_books(page, book_id, order):
|
||||||
name = db.session.query(db.Data).filter(db.Data.format == book_id.upper()).first()
|
name = calibre_db.session.query(db.Data).filter(db.Data.format == book_id.upper()).first()
|
||||||
if name:
|
if name:
|
||||||
entries, random, pagination = fill_indexpage(page, db.Books,
|
entries, random, pagination = calibre_db.fill_indexpage(page,
|
||||||
db.Books.data.any(db.Data.format == book_id.upper()),
|
db.Books,
|
||||||
[db.Books.timestamp.desc(), order[0]])
|
db.Books.data.any(db.Data.format == book_id.upper()),
|
||||||
|
[db.Books.timestamp.desc(), order[0]])
|
||||||
return render_title_template('index.html', random=random, pagination=pagination, entries=entries, id=book_id,
|
return render_title_template('index.html', random=random, pagination=pagination, entries=entries, id=book_id,
|
||||||
title=_(u"File format: %(format)s", format=name.format), page="formats")
|
title=_(u"File format: %(format)s", format=name.format), page="formats")
|
||||||
else:
|
else:
|
||||||
|
@ -752,11 +801,13 @@ def render_formats_books(page, book_id, order):
|
||||||
|
|
||||||
|
|
||||||
def render_category_books(page, book_id, order):
|
def render_category_books(page, book_id, order):
|
||||||
name = db.session.query(db.Tags).filter(db.Tags.id == book_id).first()
|
name = calibre_db.session.query(db.Tags).filter(db.Tags.id == book_id).first()
|
||||||
if name:
|
if name:
|
||||||
entries, random, pagination = fill_indexpage(page, db.Books, db.Books.tags.any(db.Tags.id == book_id),
|
entries, random, pagination = calibre_db.fill_indexpage(page,
|
||||||
[order[0], db.Series.name, db.Books.series_index],
|
db.Books,
|
||||||
db.books_series_link, db.Series)
|
db.Books.tags.any(db.Tags.id == book_id),
|
||||||
|
[order[0], db.Series.name, db.Books.series_index],
|
||||||
|
db.books_series_link, db.Series)
|
||||||
return render_title_template('index.html', random=random, entries=entries, pagination=pagination, id=book_id,
|
return render_title_template('index.html', random=random, entries=entries, pagination=pagination, id=book_id,
|
||||||
title=_(u"Category: %(name)s", name=name.name), page="category")
|
title=_(u"Category: %(name)s", name=name.name), page="category")
|
||||||
else:
|
else:
|
||||||
|
@ -772,21 +823,29 @@ def render_language_books(page, name, order):
|
||||||
lang_name = _(isoLanguages.get(part3=name).name)
|
lang_name = _(isoLanguages.get(part3=name).name)
|
||||||
except KeyError:
|
except KeyError:
|
||||||
abort(404)
|
abort(404)
|
||||||
entries, random, pagination = fill_indexpage(page, db.Books, db.Books.languages.any(db.Languages.lang_code == name),
|
entries, random, pagination = calibre_db.fill_indexpage(page,
|
||||||
[db.Books.timestamp.desc(), order[0]])
|
db.Books,
|
||||||
|
db.Books.languages.any(db.Languages.lang_code == name),
|
||||||
|
[db.Books.timestamp.desc(), order[0]])
|
||||||
return render_title_template('index.html', random=random, entries=entries, pagination=pagination, id=name,
|
return render_title_template('index.html', random=random, entries=entries, pagination=pagination, id=name,
|
||||||
title=_(u"Language: %(name)s", name=lang_name), page="language")
|
title=_(u"Language: %(name)s", name=lang_name), page="language")
|
||||||
|
|
||||||
|
|
||||||
|
'''@web.route("/table")
|
||||||
|
@login_required_if_no_ano
|
||||||
|
def books_table():
|
||||||
|
return render_title_template('index.html', random=random, entries=entries, pagination=pagination, id=name,
|
||||||
|
title=_(u"Language: %(name)s", name=lang_name), page="language")'''
|
||||||
|
|
||||||
@web.route("/author")
|
@web.route("/author")
|
||||||
@login_required_if_no_ano
|
@login_required_if_no_ano
|
||||||
def author_list():
|
def author_list():
|
||||||
if current_user.check_visibility(constants.SIDEBAR_AUTHOR):
|
if current_user.check_visibility(constants.SIDEBAR_AUTHOR):
|
||||||
entries = db.session.query(db.Authors, func.count('books_authors_link.book').label('count')) \
|
entries = calibre_db.session.query(db.Authors, func.count('books_authors_link.book').label('count')) \
|
||||||
.join(db.books_authors_link).join(db.Books).filter(common_filters()) \
|
.join(db.books_authors_link).join(db.Books).filter(calibre_db.common_filters()) \
|
||||||
.group_by(text('books_authors_link.author')).order_by(db.Authors.sort).all()
|
.group_by(text('books_authors_link.author')).order_by(db.Authors.sort).all()
|
||||||
charlist = db.session.query(func.upper(func.substr(db.Authors.sort, 1, 1)).label('char')) \
|
charlist = calibre_db.session.query(func.upper(func.substr(db.Authors.sort, 1, 1)).label('char')) \
|
||||||
.join(db.books_authors_link).join(db.Books).filter(common_filters()) \
|
.join(db.books_authors_link).join(db.Books).filter(calibre_db.common_filters()) \
|
||||||
.group_by(func.upper(func.substr(db.Authors.sort, 1, 1))).all()
|
.group_by(func.upper(func.substr(db.Authors.sort, 1, 1))).all()
|
||||||
for entry in entries:
|
for entry in entries:
|
||||||
entry.Authors.name = entry.Authors.name.replace('|', ',')
|
entry.Authors.name = entry.Authors.name.replace('|', ',')
|
||||||
|
@ -800,11 +859,11 @@ def author_list():
|
||||||
@login_required_if_no_ano
|
@login_required_if_no_ano
|
||||||
def publisher_list():
|
def publisher_list():
|
||||||
if current_user.check_visibility(constants.SIDEBAR_PUBLISHER):
|
if current_user.check_visibility(constants.SIDEBAR_PUBLISHER):
|
||||||
entries = db.session.query(db.Publishers, func.count('books_publishers_link.book').label('count')) \
|
entries = calibre_db.session.query(db.Publishers, func.count('books_publishers_link.book').label('count')) \
|
||||||
.join(db.books_publishers_link).join(db.Books).filter(common_filters()) \
|
.join(db.books_publishers_link).join(db.Books).filter(calibre_db.common_filters()) \
|
||||||
.group_by(text('books_publishers_link.publisher')).order_by(db.Publishers.name).all()
|
.group_by(text('books_publishers_link.publisher')).order_by(db.Publishers.name).all()
|
||||||
charlist = db.session.query(func.upper(func.substr(db.Publishers.name, 1, 1)).label('char')) \
|
charlist = calibre_db.session.query(func.upper(func.substr(db.Publishers.name, 1, 1)).label('char')) \
|
||||||
.join(db.books_publishers_link).join(db.Books).filter(common_filters()) \
|
.join(db.books_publishers_link).join(db.Books).filter(calibre_db.common_filters()) \
|
||||||
.group_by(func.upper(func.substr(db.Publishers.name, 1, 1))).all()
|
.group_by(func.upper(func.substr(db.Publishers.name, 1, 1))).all()
|
||||||
return render_title_template('list.html', entries=entries, folder='web.books_list', charlist=charlist,
|
return render_title_template('list.html', entries=entries, folder='web.books_list', charlist=charlist,
|
||||||
title=_(u"Publishers"), page="publisherlist", data="publisher")
|
title=_(u"Publishers"), page="publisherlist", data="publisher")
|
||||||
|
@ -816,14 +875,25 @@ def publisher_list():
|
||||||
@login_required_if_no_ano
|
@login_required_if_no_ano
|
||||||
def series_list():
|
def series_list():
|
||||||
if current_user.check_visibility(constants.SIDEBAR_SERIES):
|
if current_user.check_visibility(constants.SIDEBAR_SERIES):
|
||||||
entries = db.session.query(db.Series, func.count('books_series_link.book').label('count')) \
|
if current_user.series_view == 'list':
|
||||||
.join(db.books_series_link).join(db.Books).filter(common_filters()) \
|
entries = calibre_db.session.query(db.Series, func.count('books_series_link.book').label('count')) \
|
||||||
.group_by(text('books_series_link.series')).order_by(db.Series.sort).all()
|
.join(db.books_series_link).join(db.Books).filter(calibre_db.common_filters()) \
|
||||||
charlist = db.session.query(func.upper(func.substr(db.Series.sort, 1, 1)).label('char')) \
|
.group_by(text('books_series_link.series')).order_by(db.Series.sort).all()
|
||||||
.join(db.books_series_link).join(db.Books).filter(common_filters()) \
|
charlist = calibre_db.session.query(func.upper(func.substr(db.Series.sort, 1, 1)).label('char')) \
|
||||||
.group_by(func.upper(func.substr(db.Series.sort, 1, 1))).all()
|
.join(db.books_series_link).join(db.Books).filter(calibre_db.common_filters()) \
|
||||||
return render_title_template('list.html', entries=entries, folder='web.books_list', charlist=charlist,
|
.group_by(func.upper(func.substr(db.Series.sort, 1, 1))).all()
|
||||||
title=_(u"Series"), page="serieslist", data="series")
|
return render_title_template('list.html', entries=entries, folder='web.books_list', charlist=charlist,
|
||||||
|
title=_(u"Series"), page="serieslist", data="series")
|
||||||
|
else:
|
||||||
|
entries = calibre_db.session.query(db.Books, func.count('books_series_link').label('count')) \
|
||||||
|
.join(db.books_series_link).join(db.Series).filter(calibre_db.common_filters()) \
|
||||||
|
.group_by(text('books_series_link.series')).order_by(db.Series.sort).all()
|
||||||
|
charlist = calibre_db.session.query(func.upper(func.substr(db.Series.sort, 1, 1)).label('char')) \
|
||||||
|
.join(db.books_series_link).join(db.Books).filter(calibre_db.common_filters()) \
|
||||||
|
.group_by(func.upper(func.substr(db.Series.sort, 1, 1))).all()
|
||||||
|
|
||||||
|
return render_title_template('grid.html', entries=entries, folder='web.books_list', charlist=charlist,
|
||||||
|
title=_(u"Series"), page="serieslist", data="series", bodyClass="grid-view")
|
||||||
else:
|
else:
|
||||||
abort(404)
|
abort(404)
|
||||||
|
|
||||||
|
@ -832,9 +902,9 @@ def series_list():
|
||||||
@login_required_if_no_ano
|
@login_required_if_no_ano
|
||||||
def ratings_list():
|
def ratings_list():
|
||||||
if current_user.check_visibility(constants.SIDEBAR_RATING):
|
if current_user.check_visibility(constants.SIDEBAR_RATING):
|
||||||
entries = db.session.query(db.Ratings, func.count('books_ratings_link.book').label('count'),
|
entries = calibre_db.session.query(db.Ratings, func.count('books_ratings_link.book').label('count'),
|
||||||
(db.Ratings.rating / 2).label('name')) \
|
(db.Ratings.rating / 2).label('name')) \
|
||||||
.join(db.books_ratings_link).join(db.Books).filter(common_filters()) \
|
.join(db.books_ratings_link).join(db.Books).filter(calibre_db.common_filters()) \
|
||||||
.group_by(text('books_ratings_link.rating')).order_by(db.Ratings.rating).all()
|
.group_by(text('books_ratings_link.rating')).order_by(db.Ratings.rating).all()
|
||||||
return render_title_template('list.html', entries=entries, folder='web.books_list', charlist=list(),
|
return render_title_template('list.html', entries=entries, folder='web.books_list', charlist=list(),
|
||||||
title=_(u"Ratings list"), page="ratingslist", data="ratings")
|
title=_(u"Ratings list"), page="ratingslist", data="ratings")
|
||||||
|
@ -846,8 +916,10 @@ def ratings_list():
|
||||||
@login_required_if_no_ano
|
@login_required_if_no_ano
|
||||||
def formats_list():
|
def formats_list():
|
||||||
if current_user.check_visibility(constants.SIDEBAR_FORMAT):
|
if current_user.check_visibility(constants.SIDEBAR_FORMAT):
|
||||||
entries = db.session.query(db.Data, func.count('data.book').label('count'), db.Data.format.label('format')) \
|
entries = calibre_db.session.query(db.Data,
|
||||||
.join(db.Books).filter(common_filters()) \
|
func.count('data.book').label('count'),
|
||||||
|
db.Data.format.label('format')) \
|
||||||
|
.join(db.Books).filter(calibre_db.common_filters()) \
|
||||||
.group_by(db.Data.format).order_by(db.Data.format).all()
|
.group_by(db.Data.format).order_by(db.Data.format).all()
|
||||||
return render_title_template('list.html', entries=entries, folder='web.books_list', charlist=list(),
|
return render_title_template('list.html', entries=entries, folder='web.books_list', charlist=list(),
|
||||||
title=_(u"File formats list"), page="formatslist", data="formats")
|
title=_(u"File formats list"), page="formatslist", data="formats")
|
||||||
|
@ -861,20 +933,20 @@ def language_overview():
|
||||||
if current_user.check_visibility(constants.SIDEBAR_LANGUAGE):
|
if current_user.check_visibility(constants.SIDEBAR_LANGUAGE):
|
||||||
charlist = list()
|
charlist = list()
|
||||||
if current_user.filter_language() == u"all":
|
if current_user.filter_language() == u"all":
|
||||||
languages = speaking_language()
|
languages = calibre_db.speaking_language()
|
||||||
# ToDo: generate first character list for languages
|
# ToDo: generate first character list for languages
|
||||||
else:
|
else:
|
||||||
try:
|
try:
|
||||||
cur_l = LC.parse(current_user.filter_language())
|
cur_l = LC.parse(current_user.filter_language())
|
||||||
except UnknownLocaleError:
|
except UnknownLocaleError:
|
||||||
cur_l = None
|
cur_l = None
|
||||||
languages = db.session.query(db.Languages).filter(
|
languages = calibre_db.session.query(db.Languages).filter(
|
||||||
db.Languages.lang_code == current_user.filter_language()).all()
|
db.Languages.lang_code == current_user.filter_language()).all()
|
||||||
if cur_l:
|
if cur_l:
|
||||||
languages[0].name = cur_l.get_language_name(get_locale())
|
languages[0].name = cur_l.get_language_name(get_locale())
|
||||||
else:
|
else:
|
||||||
languages[0].name = _(isoLanguages.get(part3=languages[0].lang_code).name)
|
languages[0].name = _(isoLanguages.get(part3=languages[0].lang_code).name)
|
||||||
lang_counter = db.session.query(db.books_languages_link,
|
lang_counter = calibre_db.session.query(db.books_languages_link,
|
||||||
func.count('books_languages_link.book').label('bookcount')).group_by(
|
func.count('books_languages_link.book').label('bookcount')).group_by(
|
||||||
text('books_languages_link.lang_code')).all()
|
text('books_languages_link.lang_code')).all()
|
||||||
return render_title_template('languages.html', languages=languages, lang_counter=lang_counter,
|
return render_title_template('languages.html', languages=languages, lang_counter=lang_counter,
|
||||||
|
@ -888,11 +960,11 @@ def language_overview():
|
||||||
@login_required_if_no_ano
|
@login_required_if_no_ano
|
||||||
def category_list():
|
def category_list():
|
||||||
if current_user.check_visibility(constants.SIDEBAR_CATEGORY):
|
if current_user.check_visibility(constants.SIDEBAR_CATEGORY):
|
||||||
entries = db.session.query(db.Tags, func.count('books_tags_link.book').label('count')) \
|
entries = calibre_db.session.query(db.Tags, func.count('books_tags_link.book').label('count')) \
|
||||||
.join(db.books_tags_link).join(db.Books).order_by(db.Tags.name).filter(common_filters()) \
|
.join(db.books_tags_link).join(db.Books).order_by(db.Tags.name).filter(calibre_db.common_filters()) \
|
||||||
.group_by(text('books_tags_link.tag')).all()
|
.group_by(text('books_tags_link.tag')).all()
|
||||||
charlist = db.session.query(func.upper(func.substr(db.Tags.name, 1, 1)).label('char')) \
|
charlist = calibre_db.session.query(func.upper(func.substr(db.Tags.name, 1, 1)).label('char')) \
|
||||||
.join(db.books_tags_link).join(db.Books).filter(common_filters()) \
|
.join(db.books_tags_link).join(db.Books).filter(calibre_db.common_filters()) \
|
||||||
.group_by(func.upper(func.substr(db.Tags.name, 1, 1))).all()
|
.group_by(func.upper(func.substr(db.Tags.name, 1, 1))).all()
|
||||||
return render_title_template('list.html', entries=entries, folder='web.books_list', charlist=charlist,
|
return render_title_template('list.html', entries=entries, folder='web.books_list', charlist=charlist,
|
||||||
title=_(u"Categories"), page="catlist", data="category")
|
title=_(u"Categories"), page="catlist", data="category")
|
||||||
|
@ -912,21 +984,21 @@ def get_tasks_status():
|
||||||
return render_title_template('tasks.html', entries=answer, title=_(u"Tasks"), page="tasks")
|
return render_title_template('tasks.html', entries=answer, title=_(u"Tasks"), page="tasks")
|
||||||
|
|
||||||
|
|
||||||
# ################################### Search functions ################################################################
|
|
||||||
|
|
||||||
@app.route("/reconnect")
|
@app.route("/reconnect")
|
||||||
def reconnect():
|
def reconnect():
|
||||||
db.reconnect_db(config)
|
db.reconnect_db(config, ub.app_DB_path)
|
||||||
return json.dumps({})
|
return json.dumps({})
|
||||||
|
|
||||||
|
|
||||||
|
# ################################### Search functions ################################################################
|
||||||
|
|
||||||
|
|
||||||
@web.route("/search", methods=["GET"])
|
@web.route("/search", methods=["GET"])
|
||||||
@login_required_if_no_ano
|
@login_required_if_no_ano
|
||||||
def search():
|
def search():
|
||||||
term = request.args.get("query")
|
term = request.args.get("query")
|
||||||
if term:
|
if term:
|
||||||
term.strip().lower()
|
entries = calibre_db.get_search_results(term)
|
||||||
entries = get_search_results(term)
|
|
||||||
ids = list()
|
ids = list()
|
||||||
for element in entries:
|
for element in entries:
|
||||||
ids.append(element.id)
|
ids.append(element.id)
|
||||||
|
@ -948,9 +1020,9 @@ def search():
|
||||||
@login_required_if_no_ano
|
@login_required_if_no_ano
|
||||||
def advanced_search():
|
def advanced_search():
|
||||||
# Build custom columns names
|
# Build custom columns names
|
||||||
cc = get_cc_columns()
|
cc = get_cc_columns(filter_config_custom_read=True)
|
||||||
db.session.connection().connection.connection.create_function("lower", 1, lcase)
|
calibre_db.session.connection().connection.connection.create_function("lower", 1, db.lcase)
|
||||||
q = db.session.query(db.Books).filter(common_filters()).order_by(db.Books.sort)
|
q = calibre_db.session.query(db.Books).filter(calibre_db.common_filters()).order_by(db.Books.sort)
|
||||||
|
|
||||||
include_tag_inputs = request.args.getlist('include_tag')
|
include_tag_inputs = request.args.getlist('include_tag')
|
||||||
exclude_tag_inputs = request.args.getlist('exclude_tag')
|
exclude_tag_inputs = request.args.getlist('exclude_tag')
|
||||||
|
@ -1003,13 +1075,13 @@ def advanced_search():
|
||||||
format='medium', locale=get_locale())])
|
format='medium', locale=get_locale())])
|
||||||
except ValueError:
|
except ValueError:
|
||||||
pub_start = u""
|
pub_start = u""
|
||||||
tag_names = db.session.query(db.Tags).filter(db.Tags.id.in_(include_tag_inputs)).all()
|
tag_names = calibre_db.session.query(db.Tags).filter(db.Tags.id.in_(include_tag_inputs)).all()
|
||||||
searchterm.extend(tag.name for tag in tag_names)
|
searchterm.extend(tag.name for tag in tag_names)
|
||||||
serie_names = db.session.query(db.Series).filter(db.Series.id.in_(include_series_inputs)).all()
|
serie_names = calibre_db.session.query(db.Series).filter(db.Series.id.in_(include_series_inputs)).all()
|
||||||
searchterm.extend(serie.name for serie in serie_names)
|
searchterm.extend(serie.name for serie in serie_names)
|
||||||
language_names = db.session.query(db.Languages).filter(db.Languages.id.in_(include_languages_inputs)).all()
|
language_names = calibre_db.session.query(db.Languages).filter(db.Languages.id.in_(include_languages_inputs)).all()
|
||||||
if language_names:
|
if language_names:
|
||||||
language_names = speaking_language(language_names)
|
language_names = calibre_db.speaking_language(language_names)
|
||||||
searchterm.extend(language.name for language in language_names)
|
searchterm.extend(language.name for language in language_names)
|
||||||
if rating_high:
|
if rating_high:
|
||||||
searchterm.extend([_(u"Rating <= %(rating)s", rating=rating_high)])
|
searchterm.extend([_(u"Rating <= %(rating)s", rating=rating_high)])
|
||||||
|
@ -1064,13 +1136,16 @@ def advanced_search():
|
||||||
# search custom culumns
|
# search custom culumns
|
||||||
for c in cc:
|
for c in cc:
|
||||||
custom_query = request.args.get('custom_column_' + str(c.id))
|
custom_query = request.args.get('custom_column_' + str(c.id))
|
||||||
if custom_query:
|
if custom_query != '' and custom_query is not None:
|
||||||
if c.datatype == 'bool':
|
if c.datatype == 'bool':
|
||||||
q = q.filter(getattr(db.Books, 'custom_column_' + str(c.id)).any(
|
q = q.filter(getattr(db.Books, 'custom_column_' + str(c.id)).any(
|
||||||
db.cc_classes[c.id].value == (custom_query == "True")))
|
db.cc_classes[c.id].value == (custom_query == "True")))
|
||||||
elif c.datatype == 'int':
|
elif c.datatype == 'int' or c.datatype == 'float':
|
||||||
q = q.filter(getattr(db.Books, 'custom_column_' + str(c.id)).any(
|
q = q.filter(getattr(db.Books, 'custom_column_' + str(c.id)).any(
|
||||||
db.cc_classes[c.id].value == custom_query))
|
db.cc_classes[c.id].value == custom_query))
|
||||||
|
elif c.datatype == 'rating':
|
||||||
|
q = q.filter(getattr(db.Books, 'custom_column_' + str(c.id)).any(
|
||||||
|
db.cc_classes[c.id].value == int(custom_query) * 2))
|
||||||
else:
|
else:
|
||||||
q = q.filter(getattr(db.Books, 'custom_column_' + str(c.id)).any(
|
q = q.filter(getattr(db.Books, 'custom_column_' + str(c.id)).any(
|
||||||
func.lower(db.cc_classes[c.id].value).ilike("%" + custom_query + "%")))
|
func.lower(db.cc_classes[c.id].value).ilike("%" + custom_query + "%")))
|
||||||
|
@ -1082,15 +1157,26 @@ def advanced_search():
|
||||||
return render_title_template('search.html', adv_searchterm=searchterm,
|
return render_title_template('search.html', adv_searchterm=searchterm,
|
||||||
entries=q, title=_(u"search"), page="search")
|
entries=q, title=_(u"search"), page="search")
|
||||||
# prepare data for search-form
|
# prepare data for search-form
|
||||||
tags = db.session.query(db.Tags).join(db.books_tags_link).join(db.Books).filter(common_filters()) \
|
tags = calibre_db.session.query(db.Tags)\
|
||||||
.group_by(text('books_tags_link.tag')).order_by(db.Tags.name).all()
|
.join(db.books_tags_link)\
|
||||||
series = db.session.query(db.Series).join(db.books_series_link).join(db.Books).filter(common_filters()) \
|
.join(db.Books)\
|
||||||
.group_by(text('books_series_link.series')).order_by(db.Series.name).filter(common_filters()).all()
|
.filter(calibre_db.common_filters()) \
|
||||||
extensions = db.session.query(db.Data).join(db.Books).filter(common_filters()) \
|
.group_by(text('books_tags_link.tag'))\
|
||||||
.group_by(db.Data.format).order_by(db.Data.format).all()
|
.order_by(db.Tags.name).all()
|
||||||
|
series = calibre_db.session.query(db.Series)\
|
||||||
|
.join(db.books_series_link)\
|
||||||
|
.join(db.Books)\
|
||||||
|
.filter(calibre_db.common_filters()) \
|
||||||
|
.group_by(text('books_series_link.series'))\
|
||||||
|
.order_by(db.Series.name)\
|
||||||
|
.filter(calibre_db.common_filters()).all()
|
||||||
|
extensions = calibre_db.session.query(db.Data)\
|
||||||
|
.join(db.Books)\
|
||||||
|
.filter(calibre_db.common_filters()) \
|
||||||
|
.group_by(db.Data.format)\
|
||||||
|
.order_by(db.Data.format).all()
|
||||||
if current_user.filter_language() == u"all":
|
if current_user.filter_language() == u"all":
|
||||||
languages = speaking_language()
|
languages = calibre_db.speaking_language()
|
||||||
else:
|
else:
|
||||||
languages = None
|
languages = None
|
||||||
return render_title_template('search_form.html', tags=tags, languages=languages, extensions=extensions,
|
return render_title_template('search_form.html', tags=tags, languages=languages, extensions=extensions,
|
||||||
|
@ -1100,30 +1186,35 @@ def advanced_search():
|
||||||
def render_read_books(page, are_read, as_xml=False, order=None, *args, **kwargs):
|
def render_read_books(page, are_read, as_xml=False, order=None, *args, **kwargs):
|
||||||
order = order or []
|
order = order or []
|
||||||
if not config.config_read_column:
|
if not config.config_read_column:
|
||||||
readBooks = ub.session.query(ub.ReadBook).filter(ub.ReadBook.user_id == int(current_user.id))\
|
|
||||||
.filter(ub.ReadBook.read_status == ub.ReadBook.STATUS_FINISHED).all()
|
|
||||||
readBookIds = [x.book_id for x in readBooks]
|
|
||||||
if are_read:
|
if are_read:
|
||||||
db_filter = db.Books.id.in_(readBookIds)
|
db_filter = and_(ub.ReadBook.user_id == int(current_user.id),
|
||||||
|
ub.ReadBook.read_status == ub.ReadBook.STATUS_FINISHED)
|
||||||
else:
|
else:
|
||||||
db_filter = ~db.Books.id.in_(readBookIds)
|
db_filter = coalesce(ub.ReadBook.read_status, 0) != ub.ReadBook.STATUS_FINISHED
|
||||||
entries, random, pagination = fill_indexpage(page, db.Books, db_filter, order)
|
entries, random, pagination = calibre_db.fill_indexpage(page,
|
||||||
|
db.Books,
|
||||||
|
db_filter,
|
||||||
|
order,
|
||||||
|
ub.ReadBook, db.Books.id==ub.ReadBook.book_id)
|
||||||
else:
|
else:
|
||||||
try:
|
try:
|
||||||
if are_read:
|
if are_read:
|
||||||
db_filter = db.cc_classes[config.config_read_column].value == True
|
db_filter = db.cc_classes[config.config_read_column].value == True
|
||||||
else:
|
else:
|
||||||
db_filter = coalesce(db.cc_classes[config.config_read_column].value, False) != True
|
db_filter = coalesce(db.cc_classes[config.config_read_column].value, False) != True
|
||||||
# book_count = db.session.query(func.count(db.Books.id)).filter(common_filters()).filter(db_filter).scalar()
|
entries, random, pagination = calibre_db.fill_indexpage(page,
|
||||||
entries, random, pagination = fill_indexpage(page, db.Books,
|
db.Books,
|
||||||
db_filter,
|
db_filter,
|
||||||
order,
|
order,
|
||||||
db.cc_classes[config.config_read_column])
|
db.cc_classes[config.config_read_column])
|
||||||
except KeyError:
|
except KeyError:
|
||||||
log.error("Custom Column No.%d is not existing in calibre database", config.config_read_column)
|
log.error("Custom Column No.%d is not existing in calibre database", config.config_read_column)
|
||||||
book_count = 0
|
if not as_xml:
|
||||||
|
flash(_("Custom Column No.%(column)d is not existing in calibre database",
|
||||||
|
column=config.config_read_column),
|
||||||
|
category="error")
|
||||||
|
return redirect(url_for("web.index"))
|
||||||
|
# ToDo: Handle error Case for opds
|
||||||
if as_xml:
|
if as_xml:
|
||||||
return entries, pagination
|
return entries, pagination
|
||||||
else:
|
else:
|
||||||
|
@ -1149,8 +1240,11 @@ def render_archived_books(page, order):
|
||||||
|
|
||||||
archived_filter = db.Books.id.in_(archived_book_ids)
|
archived_filter = db.Books.id.in_(archived_book_ids)
|
||||||
|
|
||||||
entries, random, pagination = fill_indexpage_with_archived_books(page, db.Books, archived_filter, order,
|
entries, random, pagination = calibre_db.fill_indexpage_with_archived_books(page,
|
||||||
allow_show_archived=True)
|
db.Books,
|
||||||
|
archived_filter,
|
||||||
|
order,
|
||||||
|
allow_show_archived=True)
|
||||||
|
|
||||||
name = _(u'Archived Books') + ' (' + str(len(archived_book_ids)) + ')'
|
name = _(u'Archived Books') + ' (' + str(len(archived_book_ids)) + ')'
|
||||||
pagename = "archived"
|
pagename = "archived"
|
||||||
|
@ -1172,9 +1266,8 @@ def get_cover(book_id):
|
||||||
@viewer_required
|
@viewer_required
|
||||||
def serve_book(book_id, book_format, anyname):
|
def serve_book(book_id, book_format, anyname):
|
||||||
book_format = book_format.split(".")[0]
|
book_format = book_format.split(".")[0]
|
||||||
book = db.session.query(db.Books).filter(db.Books.id == book_id).first()
|
book = calibre_db.get_book(book_id)
|
||||||
data = db.session.query(db.Data).filter(db.Data.book == book.id).filter(db.Data.format == book_format.upper()) \
|
data = calibre_db.get_book_format(book.id, book_format.upper())
|
||||||
.first()
|
|
||||||
log.info('Serving book: %s', data.name)
|
log.info('Serving book: %s', data.name)
|
||||||
if config.config_use_google_drive:
|
if config.config_use_google_drive:
|
||||||
headers = Headers()
|
headers = Headers()
|
||||||
|
@ -1190,7 +1283,12 @@ def serve_book(book_id, book_format, anyname):
|
||||||
@login_required_if_no_ano
|
@login_required_if_no_ano
|
||||||
@download_required
|
@download_required
|
||||||
def download_link(book_id, book_format, anyname):
|
def download_link(book_id, book_format, anyname):
|
||||||
return get_download_link(book_id, book_format.lower())
|
if "Kobo" in request.headers.get('User-Agent'):
|
||||||
|
client = "kobo"
|
||||||
|
else:
|
||||||
|
client=""
|
||||||
|
|
||||||
|
return get_download_link(book_id, book_format, client)
|
||||||
|
|
||||||
|
|
||||||
@web.route('/send/<int:book_id>/<book_format>/<int:convert>')
|
@web.route('/send/<int:book_id>/<book_format>/<int:convert>')
|
||||||
|
@ -1231,30 +1329,33 @@ def register():
|
||||||
|
|
||||||
if request.method == "POST":
|
if request.method == "POST":
|
||||||
to_save = request.form.to_dict()
|
to_save = request.form.to_dict()
|
||||||
if not to_save["nickname"] or not to_save["email"]:
|
if config.config_register_email:
|
||||||
|
nickname = to_save["email"]
|
||||||
|
else:
|
||||||
|
nickname = to_save["nickname"]
|
||||||
|
if not nickname or not to_save["email"]:
|
||||||
flash(_(u"Please fill out all fields!"), category="error")
|
flash(_(u"Please fill out all fields!"), category="error")
|
||||||
return render_title_template('register.html', title=_(u"register"), page="register")
|
return render_title_template('register.html', title=_(u"register"), page="register")
|
||||||
|
|
||||||
existing_user = ub.session.query(ub.User).filter(func.lower(ub.User.nickname) == to_save["nickname"]
|
|
||||||
|
existing_user = ub.session.query(ub.User).filter(func.lower(ub.User.nickname) == nickname
|
||||||
.lower()).first()
|
.lower()).first()
|
||||||
existing_email = ub.session.query(ub.User).filter(ub.User.email == to_save["email"].lower()).first()
|
existing_email = ub.session.query(ub.User).filter(ub.User.email == to_save["email"].lower()).first()
|
||||||
if not existing_user and not existing_email:
|
if not existing_user and not existing_email:
|
||||||
content = ub.User()
|
content = ub.User()
|
||||||
# content.password = generate_password_hash(to_save["password"])
|
|
||||||
if check_valid_domain(to_save["email"]):
|
if check_valid_domain(to_save["email"]):
|
||||||
content.nickname = to_save["nickname"]
|
content.nickname = nickname
|
||||||
content.email = to_save["email"]
|
content.email = to_save["email"]
|
||||||
password = generate_random_password()
|
password = generate_random_password()
|
||||||
content.password = generate_password_hash(password)
|
content.password = generate_password_hash(password)
|
||||||
content.role = config.config_default_role
|
content.role = config.config_default_role
|
||||||
content.sidebar_view = config.config_default_show
|
content.sidebar_view = config.config_default_show
|
||||||
# content.mature_content = bool(config.config_default_show & constants.MATURE_CONTENT)
|
|
||||||
try:
|
try:
|
||||||
ub.session.add(content)
|
ub.session.add(content)
|
||||||
ub.session.commit()
|
ub.session.commit()
|
||||||
if feature_support['oauth']:
|
if feature_support['oauth']:
|
||||||
register_user_with_oauth(content)
|
register_user_with_oauth(content)
|
||||||
send_registration_mail(to_save["email"], to_save["nickname"], password)
|
send_registration_mail(to_save["email"], nickname, password)
|
||||||
except Exception:
|
except Exception:
|
||||||
ub.session.rollback()
|
ub.session.rollback()
|
||||||
flash(_(u"An unknown error occurred. Please try again later."), category="error")
|
flash(_(u"An unknown error occurred. Please try again later."), category="error")
|
||||||
|
@ -1448,7 +1549,7 @@ def token_verified():
|
||||||
@login_required
|
@login_required
|
||||||
def profile():
|
def profile():
|
||||||
downloads = list()
|
downloads = list()
|
||||||
languages = speaking_language()
|
languages = calibre_db.speaking_language()
|
||||||
translations = babel.list_translations() + [LC('en')]
|
translations = babel.list_translations() + [LC('en')]
|
||||||
kobo_support = feature_support['kobo'] and config.config_kobo_sync
|
kobo_support = feature_support['kobo'] and config.config_kobo_sync
|
||||||
if feature_support['oauth']:
|
if feature_support['oauth']:
|
||||||
|
@ -1457,9 +1558,9 @@ def profile():
|
||||||
oauth_status = None
|
oauth_status = None
|
||||||
|
|
||||||
for book in current_user.downloads:
|
for book in current_user.downloads:
|
||||||
downloadBook = db.session.query(db.Books).filter(db.Books.id == book.book_id).first()
|
downloadBook = calibre_db.get_book(book.book_id)
|
||||||
if downloadBook:
|
if downloadBook:
|
||||||
downloads.append(db.session.query(db.Books).filter(db.Books.id == book.book_id).first())
|
downloads.append(downloadBook)
|
||||||
else:
|
else:
|
||||||
ub.delete_download(book.book_id)
|
ub.delete_download(book.book_id)
|
||||||
if request.method == "POST":
|
if request.method == "POST":
|
||||||
|
@ -1538,7 +1639,7 @@ def profile():
|
||||||
@login_required_if_no_ano
|
@login_required_if_no_ano
|
||||||
@viewer_required
|
@viewer_required
|
||||||
def read_book(book_id, book_format):
|
def read_book(book_id, book_format):
|
||||||
book = db.session.query(db.Books).filter(db.Books.id == book_id).filter(common_filters()).first()
|
book = calibre_db.get_filtered_book(book_id)
|
||||||
if not book:
|
if not book:
|
||||||
flash(_(u"Error opening eBook. File does not exist or file is not accessible:"), category="error")
|
flash(_(u"Error opening eBook. File does not exist or file is not accessible:"), category="error")
|
||||||
log.debug(u"Error opening eBook. File does not exist or file is not accessible:")
|
log.debug(u"Error opening eBook. File does not exist or file is not accessible:")
|
||||||
|
@ -1562,7 +1663,7 @@ def read_book(book_id, book_format):
|
||||||
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 = db.session.query(db.Books).filter(db.Books.id == book_id).filter(common_filters()).first()
|
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)
|
title=_(u"Read a Book"), entry=entries, bookmark=bookmark)
|
||||||
|
@ -1588,8 +1689,7 @@ def read_book(book_id, book_format):
|
||||||
@web.route("/book/<int:book_id>")
|
@web.route("/book/<int:book_id>")
|
||||||
@login_required_if_no_ano
|
@login_required_if_no_ano
|
||||||
def show_book(book_id):
|
def show_book(book_id):
|
||||||
entries = db.session.query(db.Books).filter(and_(db.Books.id == book_id,
|
entries = calibre_db.get_filtered_book(book_id, allow_show_archived=True)
|
||||||
common_filters(allow_show_archived=True))).first()
|
|
||||||
if entries:
|
if entries:
|
||||||
for index in range(0, len(entries.languages)):
|
for index in range(0, len(entries.languages)):
|
||||||
try:
|
try:
|
||||||
|
@ -1598,7 +1698,7 @@ def show_book(book_id):
|
||||||
except UnknownLocaleError:
|
except UnknownLocaleError:
|
||||||
entries.languages[index].language_name = _(
|
entries.languages[index].language_name = _(
|
||||||
isoLanguages.get(part3=entries.languages[index].lang_code).name)
|
isoLanguages.get(part3=entries.languages[index].lang_code).name)
|
||||||
cc = get_cc_columns()
|
cc = get_cc_columns(filter_config_custom_read=True)
|
||||||
book_in_shelfs = []
|
book_in_shelfs = []
|
||||||
shelfs = ub.session.query(ub.BookShelf).filter(ub.BookShelf.book_id == book_id).all()
|
shelfs = ub.session.query(ub.BookShelf).filter(ub.BookShelf.book_id == book_id).all()
|
||||||
for entry in shelfs:
|
for entry in shelfs:
|
||||||
|
@ -1629,7 +1729,7 @@ def show_book(book_id):
|
||||||
|
|
||||||
entries.tags = sort(entries.tags, key=lambda tag: tag.name)
|
entries.tags = sort(entries.tags, key=lambda tag: tag.name)
|
||||||
|
|
||||||
entries = order_authors(entries)
|
entries = calibre_db.order_authors(entries)
|
||||||
|
|
||||||
kindle_list = check_send_to_kindle(entries)
|
kindle_list = check_send_to_kindle(entries)
|
||||||
reader_list = check_read_formats(entries)
|
reader_list = check_read_formats(entries)
|
||||||
|
|
216
cps/worker.py
216
cps/worker.py
|
@ -24,6 +24,12 @@ import smtplib
|
||||||
import socket
|
import socket
|
||||||
import time
|
import time
|
||||||
import threading
|
import threading
|
||||||
|
try:
|
||||||
|
import queue
|
||||||
|
except ImportError:
|
||||||
|
import Queue as queue
|
||||||
|
from glob import glob
|
||||||
|
from shutil import copyfile
|
||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
|
|
||||||
try:
|
try:
|
||||||
|
@ -43,9 +49,10 @@ from email.utils import make_msgid
|
||||||
from email.generator import Generator
|
from email.generator import Generator
|
||||||
from flask_babel import gettext as _
|
from flask_babel import gettext as _
|
||||||
|
|
||||||
from . import logger, config, db, gdriveutils
|
from . import calibre_db, db
|
||||||
|
from . import logger, config
|
||||||
from .subproc_wrapper import process_open
|
from .subproc_wrapper import process_open
|
||||||
|
from . import gdriveutils
|
||||||
|
|
||||||
log = logger.create()
|
log = logger.create()
|
||||||
|
|
||||||
|
@ -187,6 +194,8 @@ class WorkerThread(threading.Thread):
|
||||||
self.UIqueue = list()
|
self.UIqueue = list()
|
||||||
self.asyncSMTP = None
|
self.asyncSMTP = None
|
||||||
self.id = 0
|
self.id = 0
|
||||||
|
self.db_queue = queue.Queue()
|
||||||
|
calibre_db.add_queue(self.db_queue)
|
||||||
self.doLock = threading.Lock()
|
self.doLock = threading.Lock()
|
||||||
|
|
||||||
# Main thread loop starting the different tasks
|
# Main thread loop starting the different tasks
|
||||||
|
@ -273,7 +282,7 @@ class WorkerThread(threading.Thread):
|
||||||
index = self.current
|
index = self.current
|
||||||
self.doLock.release()
|
self.doLock.release()
|
||||||
file_path = self.queue[index]['file_path']
|
file_path = self.queue[index]['file_path']
|
||||||
bookid = self.queue[index]['bookid']
|
book_id = self.queue[index]['bookid']
|
||||||
format_old_ext = u'.' + self.queue[index]['settings']['old_book_format'].lower()
|
format_old_ext = u'.' + self.queue[index]['settings']['old_book_format'].lower()
|
||||||
format_new_ext = u'.' + self.queue[index]['settings']['new_book_format'].lower()
|
format_new_ext = u'.' + self.queue[index]['settings']['new_book_format'].lower()
|
||||||
|
|
||||||
|
@ -281,95 +290,51 @@ class WorkerThread(threading.Thread):
|
||||||
# if it does - mark the conversion task as complete and return a success
|
# if it does - mark the conversion task as complete and return a success
|
||||||
# this will allow send to kindle workflow to continue to work
|
# this will allow send to kindle workflow to continue to work
|
||||||
if os.path.isfile(file_path + format_new_ext):
|
if os.path.isfile(file_path + format_new_ext):
|
||||||
log.info("Book id %d already converted to %s", bookid, format_new_ext)
|
log.info("Book id %d already converted to %s", book_id, format_new_ext)
|
||||||
cur_book = db.session.query(db.Books).filter(db.Books.id == bookid).first()
|
cur_book = calibre_db.get_book(book_id)
|
||||||
self.queue[index]['path'] = file_path
|
self.queue[index]['path'] = file_path
|
||||||
self.queue[index]['title'] = cur_book.title
|
self.queue[index]['title'] = cur_book.title
|
||||||
self._handleSuccess()
|
self._handleSuccess()
|
||||||
return file_path + format_new_ext
|
return file_path + format_new_ext
|
||||||
else:
|
else:
|
||||||
log.info("Book id %d - target format of %s does not exist. Moving forward with convert.", bookid, format_new_ext)
|
log.info("Book id %d - target format of %s does not exist. Moving forward with convert.",
|
||||||
|
book_id,
|
||||||
|
format_new_ext)
|
||||||
|
|
||||||
# check if converter-executable is existing
|
if config.config_kepubifypath and format_old_ext == '.epub' and format_new_ext == '.kepub':
|
||||||
if not os.path.exists(config.config_converterpath):
|
check, error_message = self._convert_kepubify(file_path,
|
||||||
# ToDo Text is not translated
|
format_old_ext,
|
||||||
self._handleError(u"Convertertool %s not found" % config.config_converterpath)
|
format_new_ext,
|
||||||
return
|
index)
|
||||||
|
|
||||||
try:
|
|
||||||
# check which converter to use kindlegen is "1"
|
|
||||||
if format_old_ext == '.epub' and format_new_ext == '.mobi':
|
|
||||||
if config.config_ebookconverter == 1:
|
|
||||||
command = [config.config_converterpath, file_path + u'.epub']
|
|
||||||
quotes = [1]
|
|
||||||
if config.config_ebookconverter == 2:
|
|
||||||
# Linux py2.7 encode as list without quotes no empty element for parameters
|
|
||||||
# linux py3.x no encode and as list without quotes no empty element for parameters
|
|
||||||
# windows py2.7 encode as string with quotes empty element for parameters is okay
|
|
||||||
# windows py 3.x no encode and as string with quotes empty element for parameters is okay
|
|
||||||
# separate handling for windows and linux
|
|
||||||
quotes = [1,2]
|
|
||||||
command = [config.config_converterpath, (file_path + format_old_ext),
|
|
||||||
(file_path + format_new_ext)]
|
|
||||||
quotes_index = 3
|
|
||||||
if config.config_calibre:
|
|
||||||
parameters = config.config_calibre.split(" ")
|
|
||||||
for param in parameters:
|
|
||||||
command.append(param)
|
|
||||||
quotes.append(quotes_index)
|
|
||||||
quotes_index += 1
|
|
||||||
p = process_open(command, quotes)
|
|
||||||
# p = subprocess.Popen(command, stdout=subprocess.PIPE, universal_newlines=True)
|
|
||||||
except OSError as e:
|
|
||||||
self._handleError(_(u"Ebook-converter failed: %(error)s", error=e))
|
|
||||||
return
|
|
||||||
|
|
||||||
if config.config_ebookconverter == 1:
|
|
||||||
nextline = p.communicate()[0]
|
|
||||||
# Format of error message (kindlegen translates its output texts):
|
|
||||||
# Error(prcgen):E23006: Language not recognized in metadata.The dc:Language field is mandatory.Aborting.
|
|
||||||
conv_error = re.search(r".*\(.*\):(E\d+):\s(.*)", nextline, re.MULTILINE)
|
|
||||||
# If error occoures, store error message for logfile
|
|
||||||
if conv_error:
|
|
||||||
error_message = _(u"Kindlegen failed with Error %(error)s. Message: %(message)s",
|
|
||||||
error=conv_error.group(1), message=conv_error.group(2).strip())
|
|
||||||
log.debug("convert_kindlegen: %s", nextline)
|
|
||||||
else:
|
else:
|
||||||
while p.poll() is None:
|
# check if calibre converter-executable is existing
|
||||||
nextline = p.stdout.readline()
|
if not os.path.exists(config.config_converterpath):
|
||||||
if os.name == 'nt' and sys.version_info < (3, 0):
|
# ToDo Text is not translated
|
||||||
nextline = nextline.decode('windows-1252')
|
self._handleError(_(u"Calibre ebook-convert %(tool)s not found", tool=config.config_converterpath))
|
||||||
elif os.name == 'posix' and sys.version_info < (3, 0):
|
return
|
||||||
nextline = nextline.decode('utf-8')
|
check, error_message = self._convert_calibre(file_path, format_old_ext, format_new_ext, index)
|
||||||
log.debug(nextline.strip('\r\n'))
|
|
||||||
# parse progress string from calibre-converter
|
|
||||||
progress = re.search(r"(\d+)%\s.*", nextline)
|
|
||||||
if progress:
|
|
||||||
self.UIqueue[index]['progress'] = progress.group(1) + ' %'
|
|
||||||
|
|
||||||
# process returncode
|
if check == 0:
|
||||||
check = p.returncode
|
cur_book = calibre_db.get_book(book_id)
|
||||||
calibre_traceback = p.stderr.readlines()
|
|
||||||
for ele in calibre_traceback:
|
|
||||||
if sys.version_info < (3, 0):
|
|
||||||
ele = ele.decode('utf-8')
|
|
||||||
log.debug(ele.strip('\n'))
|
|
||||||
if not ele.startswith('Traceback') and not ele.startswith(' File'):
|
|
||||||
error_message = "Calibre failed with error: %s" % ele.strip('\n')
|
|
||||||
|
|
||||||
# kindlegen returncodes
|
|
||||||
# 0 = Info(prcgen):I1036: Mobi file built successfully
|
|
||||||
# 1 = Info(prcgen):I1037: Mobi file built with WARNINGS!
|
|
||||||
# 2 = Info(prcgen):I1038: MOBI file could not be generated because of errors!
|
|
||||||
if (check < 2 and config.config_ebookconverter == 1) or \
|
|
||||||
(check == 0 and config.config_ebookconverter == 2):
|
|
||||||
cur_book = db.session.query(db.Books).filter(db.Books.id == bookid).first()
|
|
||||||
if os.path.isfile(file_path + format_new_ext):
|
if os.path.isfile(file_path + format_new_ext):
|
||||||
|
# self.db_queue.join()
|
||||||
new_format = db.Data(name=cur_book.data[0].name,
|
new_format = db.Data(name=cur_book.data[0].name,
|
||||||
book_format=self.queue[index]['settings']['new_book_format'].upper(),
|
book_format=self.queue[index]['settings']['new_book_format'].upper(),
|
||||||
book=bookid, uncompressed_size=os.path.getsize(file_path + format_new_ext))
|
book=book_id, uncompressed_size=os.path.getsize(file_path + format_new_ext))
|
||||||
cur_book.data.append(new_format)
|
task = {'task':'add_format','id': book_id, 'format': new_format}
|
||||||
db.session.commit()
|
self.db_queue.put(task)
|
||||||
|
# To Do how to handle error?
|
||||||
|
|
||||||
|
'''cur_book.data.append(new_format)
|
||||||
|
try:
|
||||||
|
# db.session.merge(cur_book)
|
||||||
|
calibre_db.session.commit()
|
||||||
|
except OperationalError as e:
|
||||||
|
calibre_db.session.rollback()
|
||||||
|
log.error("Database error: %s", e)
|
||||||
|
self._handleError(_(u"Database error: %(error)s.", error=e))
|
||||||
|
return'''
|
||||||
|
|
||||||
self.queue[index]['path'] = cur_book.path
|
self.queue[index]['path'] = cur_book.path
|
||||||
self.queue[index]['title'] = cur_book.title
|
self.queue[index]['title'] = cur_book.title
|
||||||
if config.config_use_google_drive:
|
if config.config_use_google_drive:
|
||||||
|
@ -385,6 +350,87 @@ class WorkerThread(threading.Thread):
|
||||||
return
|
return
|
||||||
|
|
||||||
|
|
||||||
|
def _convert_calibre(self, file_path, format_old_ext, format_new_ext, index):
|
||||||
|
try:
|
||||||
|
# Linux py2.7 encode as list without quotes no empty element for parameters
|
||||||
|
# linux py3.x no encode and as list without quotes no empty element for parameters
|
||||||
|
# windows py2.7 encode as string with quotes empty element for parameters is okay
|
||||||
|
# windows py 3.x no encode and as string with quotes empty element for parameters is okay
|
||||||
|
# separate handling for windows and linux
|
||||||
|
quotes = [1, 2]
|
||||||
|
command = [config.config_converterpath, (file_path + format_old_ext),
|
||||||
|
(file_path + format_new_ext)]
|
||||||
|
quotes_index = 3
|
||||||
|
if config.config_calibre:
|
||||||
|
parameters = config.config_calibre.split(" ")
|
||||||
|
for param in parameters:
|
||||||
|
command.append(param)
|
||||||
|
quotes.append(quotes_index)
|
||||||
|
quotes_index += 1
|
||||||
|
|
||||||
|
p = process_open(command, quotes)
|
||||||
|
except OSError as e:
|
||||||
|
return 1, _(u"Ebook-converter failed: %(error)s", error=e)
|
||||||
|
|
||||||
|
while p.poll() is None:
|
||||||
|
nextline = p.stdout.readline()
|
||||||
|
if os.name == 'nt' and sys.version_info < (3, 0):
|
||||||
|
nextline = nextline.decode('windows-1252')
|
||||||
|
elif os.name == 'posix' and sys.version_info < (3, 0):
|
||||||
|
nextline = nextline.decode('utf-8')
|
||||||
|
log.debug(nextline.strip('\r\n'))
|
||||||
|
# parse progress string from calibre-converter
|
||||||
|
progress = re.search(r"(\d+)%\s.*", nextline)
|
||||||
|
if progress:
|
||||||
|
self.UIqueue[index]['progress'] = progress.group(1) + ' %'
|
||||||
|
|
||||||
|
# process returncode
|
||||||
|
check = p.returncode
|
||||||
|
calibre_traceback = p.stderr.readlines()
|
||||||
|
error_message = ""
|
||||||
|
for ele in calibre_traceback:
|
||||||
|
if sys.version_info < (3, 0):
|
||||||
|
ele = ele.decode('utf-8')
|
||||||
|
log.debug(ele.strip('\n'))
|
||||||
|
if not ele.startswith('Traceback') and not ele.startswith(' File'):
|
||||||
|
error_message = "Calibre failed with error: %s" % ele.strip('\n')
|
||||||
|
return check, error_message
|
||||||
|
|
||||||
|
|
||||||
|
def _convert_kepubify(self, file_path, format_old_ext, format_new_ext, index):
|
||||||
|
quotes = [1, 3]
|
||||||
|
command = [config.config_kepubifypath, (file_path + format_old_ext), '-o', os.path.dirname(file_path)]
|
||||||
|
try:
|
||||||
|
p = process_open(command, quotes)
|
||||||
|
except OSError as e:
|
||||||
|
return 1, _(u"Kepubify-converter failed: %(error)s", error=e)
|
||||||
|
self.UIqueue[index]['progress'] = '1 %'
|
||||||
|
while True:
|
||||||
|
nextline = p.stdout.readlines()
|
||||||
|
nextline = [x.strip('\n') for x in nextline if x != '\n']
|
||||||
|
if sys.version_info < (3, 0):
|
||||||
|
nextline = [x.decode('utf-8') for x in nextline]
|
||||||
|
for line in nextline:
|
||||||
|
log.debug(line)
|
||||||
|
if p.poll() is not None:
|
||||||
|
break
|
||||||
|
|
||||||
|
# ToD Handle
|
||||||
|
# process returncode
|
||||||
|
check = p.returncode
|
||||||
|
|
||||||
|
# move file
|
||||||
|
if check == 0:
|
||||||
|
converted_file = glob(os.path.join(os.path.dirname(file_path), "*.kepub.epub"))
|
||||||
|
if len(converted_file) == 1:
|
||||||
|
copyfile(converted_file[0], (file_path + format_new_ext))
|
||||||
|
os.unlink(converted_file[0])
|
||||||
|
else:
|
||||||
|
return 1, _(u"Converted file not found or more than one file in folder %(folder)s",
|
||||||
|
folder=os.path.dirname(file_path))
|
||||||
|
return check, None
|
||||||
|
|
||||||
|
|
||||||
def add_convert(self, file_path, bookid, user_name, taskMessage, settings, kindle_mail=None):
|
def add_convert(self, file_path, bookid, user_name, taskMessage, settings, kindle_mail=None):
|
||||||
self.doLock.acquire()
|
self.doLock.acquire()
|
||||||
if self.last >= 20:
|
if self.last >= 20:
|
||||||
|
@ -533,10 +579,6 @@ class WorkerThread(threading.Thread):
|
||||||
self.UIqueue[index]['formRuntime'] = datetime.now() - self.queue[index]['starttime']
|
self.UIqueue[index]['formRuntime'] = datetime.now() - self.queue[index]['starttime']
|
||||||
|
|
||||||
|
|
||||||
_worker = WorkerThread()
|
|
||||||
_worker.start()
|
|
||||||
|
|
||||||
|
|
||||||
def get_taskstatus():
|
def get_taskstatus():
|
||||||
return _worker.get_taskstatus()
|
return _worker.get_taskstatus()
|
||||||
|
|
||||||
|
@ -551,3 +593,7 @@ def add_upload(user_name, taskMessage):
|
||||||
|
|
||||||
def add_convert(file_path, bookid, user_name, taskMessage, settings, kindle_mail=None):
|
def add_convert(file_path, bookid, user_name, taskMessage, settings, kindle_mail=None):
|
||||||
return _worker.add_convert(file_path, bookid, user_name, taskMessage, settings, kindle_mail)
|
return _worker.add_convert(file_path, bookid, user_name, taskMessage, settings, kindle_mail)
|
||||||
|
|
||||||
|
|
||||||
|
_worker = WorkerThread()
|
||||||
|
_worker.start()
|
||||||
|
|
976
messages.pot
976
messages.pot
File diff suppressed because it is too large
Load Diff
|
@ -31,7 +31,7 @@ rarfile>=2.7
|
||||||
|
|
||||||
# other
|
# other
|
||||||
natsort>=2.2.0,<7.1.0
|
natsort>=2.2.0,<7.1.0
|
||||||
git+https://github.com/OzzieIsaacs/comicapi.git@15dff9ce4e1ffed29ba4a2feadfcdb6bed00bcad#egg=comicapi
|
git+https://github.com/OzzieIsaacs/comicapi.git@3e15b950b72724b1b8ca619c36580b5fbaba9784#egg=comicapi
|
||||||
|
|
||||||
#Kobo integration
|
#Kobo integration
|
||||||
jsonschema>=3.2.0,<3.3.0
|
jsonschema>=3.2.0,<3.3.0
|
||||||
|
|
File diff suppressed because it is too large
Load Diff
Loading…
Reference in New Issue
Block a user