Merge branch 'master' into Develop

pull/976/head
Ozzieisaacs 5 years ago
commit be5c67fddd

@ -31,7 +31,10 @@ from flask_babel import gettext as _
from . import db, converter, uploader, server, isoLanguages from . import db, converter, uploader, server, isoLanguages
from .web import render_title_template from .web import render_title_template
try:
from flask_login import __version__ as flask_loginVersion
except ImportError:
from flask_login.__about__ import __version__ as flask_loginVersion
about = flask.Blueprint('about', __name__) about = flask.Blueprint('about', __name__)
@ -40,7 +43,7 @@ _VERSIONS = OrderedDict(
Python=sys.version, Python=sys.version,
WebServer=server.VERSION, WebServer=server.VERSION,
Flask=flask.__version__, Flask=flask.__version__,
Flask_Login=flask_login.__version__, Flask_Login=flask_loginVersion,
Flask_Principal=flask_principal.__version__, Flask_Principal=flask_principal.__version__,
Werkzeug=werkzeug.__version__, Werkzeug=werkzeug.__version__,
Babel=babel.__version__, Babel=babel.__version__,

@ -45,7 +45,7 @@ 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
feature_support = { feature_support = {
'ldap': bool(services.ldap), 'ldap': False, # bool(services.ldap),
'goodreads': bool(services.goodreads) 'goodreads': bool(services.goodreads)
} }
@ -56,10 +56,11 @@ feature_support = {
# feature_support['rar'] = False # feature_support['rar'] = False
try: try:
from .oauth_bb import oauth_check from .oauth_bb import oauth_check, oauthblueprints
feature_support['oauth'] = True feature_support['oauth'] = True
except ImportError: except ImportError:
feature_support['oauth'] = False feature_support['oauth'] = False
oauthblueprints = []
oauth_check = {} oauth_check = {}
@ -343,18 +344,27 @@ def _configuration_update_helper():
_config_int("config_updatechannel") _config_int("config_updatechannel")
# GitHub OAuth configuration # GitHub OAuth configuration
if config.config_login_type == constants.LOGIN_OAUTH_GITHUB: if config.config_login_type == constants.LOGIN_OAUTH:
_config_string("config_github_oauth_client_id") active_oauths = 0
_config_string("config_github_oauth_client_secret")
if not config.config_github_oauth_client_id or not config.config_github_oauth_client_secret: for element in oauthblueprints:
return _configuration_result('Please enter Github oauth credentials', gdriveError) if to_save["config_"+str(element['id'])+"_oauth_client_id"] \
and to_save["config_"+str(element['id'])+"_oauth_client_secret"]:
# Google OAuth configuration active_oauths += 1
if config.config_login_type == constants.LOGIN_OAUTH_GOOGLE: element["active"] = 1
_config_string("config_google_oauth_client_id") ub.session.query(ub.OAuthProvider).filter(ub.OAuthProvider.id == element['id']).update(
_config_string("config_google_oauth_client_secret") {"oauth_client_id":to_save["config_"+str(element['id'])+"_oauth_client_id"],
if not config.config_google_oauth_client_id or not config.config_google_oauth_client_secret: "oauth_client_secret":to_save["config_"+str(element['id'])+"_oauth_client_secret"],
return _configuration_result('Please enter Google oauth credentials', gdriveError) "active":1})
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"]
else:
ub.session.query(ub.OAuthProvider).filter(ub.OAuthProvider.id == element['id']).update(
{"active":0})
element["active"] = 0
_config_int("config_log_level") _config_int("config_log_level")
_config_string("config_logfile") _config_string("config_logfile")
@ -410,7 +420,7 @@ def _configuration_result(error_flash=None, gdriveError=None):
flash(_(error_flash), category="error") flash(_(error_flash), category="error")
show_login_button = False show_login_button = False
return render_title_template("config_edit.html", config=config, return render_title_template("config_edit.html", config=config, provider=oauthblueprints,
show_back_button=show_back_button, show_login_button=show_login_button, show_back_button=show_back_button, show_login_button=show_login_button,
show_authenticate_google_drive=gdrive_authenticate, show_authenticate_google_drive=gdrive_authenticate,
gdriveError=gdriveError, gdrivefolders=gdrivefolders, feature_support=feature_support, gdriveError=gdriveError, gdrivefolders=gdrivefolders, feature_support=feature_support,
@ -653,7 +663,7 @@ def send_logfile(logtype):
@admi.route("/get_update_status", methods=['GET']) @admi.route("/get_update_status", methods=['GET'])
@login_required_if_no_ano @login_required_if_no_ano
def get_update_status(): def get_update_status():
return updater_thread.get_available_updates(request.method) return updater_thread.get_available_updates(request.method, locale=get_locale())
@admi.route("/get_updater_status", methods=['GET', 'POST']) @admi.route("/get_updater_status", methods=['GET', 'POST'])

@ -31,7 +31,7 @@ try:
from comicapi.comicarchive import ComicArchive, MetaDataStyle from comicapi.comicarchive import ComicArchive, MetaDataStyle
use_comic_meta = True use_comic_meta = True
except ImportError as e: except ImportError as e:
log.warning('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
use_comic_meta = False use_comic_meta = False

@ -20,6 +20,7 @@
from __future__ import division, print_function, unicode_literals from __future__ import division, print_function, unicode_literals
import os import os
import json import json
import sys
from sqlalchemy import exc, Column, String, Integer, SmallInteger, Boolean from sqlalchemy import exc, Column, String, Integer, SmallInteger, Boolean
from sqlalchemy.ext.declarative import declarative_base from sqlalchemy.ext.declarative import declarative_base
@ -43,41 +44,48 @@ 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>')
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)
config_certfile = Column(String) config_certfile = Column(String)
config_keyfile = Column(String) config_keyfile = Column(String)
config_calibre_web_title = Column(String, default=u'Calibre-Web') config_calibre_web_title = Column(String, default=u'Calibre-Web')
config_books_per_page = Column(Integer, default=60) config_books_per_page = Column(Integer, default=60)
config_random_books = Column(Integer, default=4) config_random_books = Column(Integer, default=4)
config_authors_max = Column(Integer, default=0) config_authors_max = Column(Integer, default=0)
config_read_column = Column(Integer, default=0) config_read_column = Column(Integer, default=0)
config_title_regex = Column(String, default=r'^(A|The|An|Der|Die|Das|Den|Ein|Eine|Einen|Dem|Des|Einem|Eines)\s+') config_title_regex = Column(String, default=u'^(A|The|An|Der|Die|Das|Den|Ein|Eine|Einen|Dem|Des|Einem|Eines)\s+')
config_mature_content_tags = Column(String, default='')
config_theme = Column(Integer, default=0)
config_log_level = Column(SmallInteger, default=logger.DEFAULT_LOG_LEVEL) config_log_level = Column(SmallInteger, default=logger.DEFAULT_LOG_LEVEL)
config_logfile = Column(String)
config_access_log = Column(SmallInteger, default=0) config_access_log = Column(SmallInteger, default=0)
config_access_logfile = Column(String)
config_uploading = Column(SmallInteger, default=0) config_uploading = Column(SmallInteger, default=0)
config_anonbrowse = Column(SmallInteger, default=0) config_anonbrowse = Column(SmallInteger, default=0)
config_public_reg = Column(SmallInteger, default=0) config_public_reg = Column(SmallInteger, default=0)
config_remote_login = Column(Boolean, default=False)
config_default_role = Column(SmallInteger, default=0) config_default_role = Column(SmallInteger, default=0)
config_default_show = Column(SmallInteger, default=6143) config_default_show = Column(SmallInteger, default=6143)
config_columns_to_ignore = Column(String) config_columns_to_ignore = Column(String)
config_use_google_drive = Column(Boolean, default=False) config_use_google_drive = Column(Boolean, default=False)
config_google_drive_folder = Column(String) config_google_drive_folder = Column(String)
config_google_drive_watch_changes_response = Column(String) config_google_drive_watch_changes_response = Column(String)
config_remote_login = Column(Boolean, default=False)
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_login_type = Column(Integer, default=0) config_login_type = Column(Integer, default=0)
# config_use_ldap = Column(Boolean)
config_ldap_provider_url = Column(String) # config_oauth_provider = Column(Integer)
config_ldap_dn = Column(String)
# config_use_github_oauth = Column(Boolean)
config_github_oauth_client_id = Column(String)
config_github_oauth_client_secret = Column(String)
# config_use_google_oauth = Column(Boolean)
config_google_oauth_client_id = Column(String)
config_google_oauth_client_secret = Column(String)
config_ldap_provider_url = Column(String, default='localhost') config_ldap_provider_url = Column(String, default='localhost')
config_ldap_port = Column(SmallInteger, default=389) config_ldap_port = Column(SmallInteger, default=389)
config_ldap_schema = Column(String, default='ldap') config_ldap_schema = Column(String, default='ldap')
@ -90,14 +98,12 @@ class _Settings(_Base):
config_ldap_dn = Column(String) config_ldap_dn = Column(String)
config_ldap_user_object = Column(String) config_ldap_user_object = Column(String)
config_ldap_openldap = Column(Boolean, default=False) config_ldap_openldap = Column(Boolean, default=False)
config_mature_content_tags = Column(String, default='')
config_logfile = Column(String)
config_access_logfile = Column(String)
config_ebookconverter = Column(Integer, default=0) config_ebookconverter = Column(Integer, default=0)
config_converterpath = Column(String) config_converterpath = Column(String)
config_calibre = Column(String) config_calibre = Column(String)
config_rarfile_location = Column(String) config_rarfile_location = Column(String)
config_theme = Column(Integer, default=0)
config_updatechannel = Column(Integer, default=constants.UPDATE_STABLE) config_updatechannel = Column(Integer, default=constants.UPDATE_STABLE)
def __repr__(self): def __repr__(self):
@ -106,6 +112,7 @@ class _Settings(_Base):
# Class holds all application specific settings in calibre-web # Class holds all application specific settings in calibre-web
class _ConfigSQL(object): class _ConfigSQL(object):
# pylint: disable=no-member
def __init__(self, session): def __init__(self, session):
self._session = session self._session = session
self._settings = None self._settings = None
@ -264,8 +271,15 @@ def _migrate_table(session, orm_class):
session.query(column).first() session.query(column).first()
except exc.OperationalError as err: except exc.OperationalError as err:
log.debug("%s: %s", column_name, err) log.debug("%s: %s", column_name, err)
if column.default is not None:
if sys.version_info < (3, 0):
if isinstance(column.default.arg,unicode):
column.default.arg = column.default.arg.encode('utf-8')
column_default = "" if column.default is None else ("DEFAULT %r" % column.default.arg) column_default = "" if column.default is None else ("DEFAULT %r" % column.default.arg)
alter_table = "ALTER TABLE %s ADD COLUMN `%s` %s %s" % (orm_class.__tablename__, column_name, column.type, column_default) alter_table = "ALTER TABLE %s ADD COLUMN `%s` %s %s" % (orm_class.__tablename__,
column_name,
column.type,
column_default)
log.debug(alter_table) log.debug(alter_table)
session.execute(alter_table) session.execute(alter_table)
changed = True changed = True
@ -273,6 +287,17 @@ def _migrate_table(session, orm_class):
if changed: if changed:
session.commit() session.commit()
def autodetect_calibre_binary():
if sys.platform == "win32":
calibre_path = ["C:\\program files\calibre\calibre-convert.exe",
"C:\\program files(x86)\calibre\calibre-convert.exe"]
else:
calibre_path = ["/opt/calibre/ebook-convert"]
for element in calibre_path:
if os.path.isfile(element) and os.access(element, os.X_OK):
return element
return None
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

@ -21,6 +21,7 @@ import sys
import os import os
from collections import namedtuple from collections import namedtuple
HOME_CONFIG = False
# Base dir is parent of current file, necessary if called from different folder # Base dir is parent of current file, necessary if called from different folder
if sys.version_info < (3, 0): if sys.version_info < (3, 0):
@ -32,7 +33,14 @@ else:
STATIC_DIR = os.path.join(BASE_DIR, 'cps', 'static') STATIC_DIR = os.path.join(BASE_DIR, 'cps', 'static')
TEMPLATES_DIR = os.path.join(BASE_DIR, 'cps', 'templates') TEMPLATES_DIR = os.path.join(BASE_DIR, 'cps', 'templates')
TRANSLATIONS_DIR = os.path.join(BASE_DIR, 'cps', 'translations') TRANSLATIONS_DIR = os.path.join(BASE_DIR, 'cps', 'translations')
CONFIG_DIR = os.environ.get('CALIBRE_DBPATH', BASE_DIR)
if HOME_CONFIG:
home_dir = os.path.join(os.path.expanduser("~"),".calibre-web")
if not os.path.exists(home_dir):
os.makedirs(home_dir)
CONFIG_DIR = os.environ.get('CALIBRE_DBPATH', home_dir)
else:
CONFIG_DIR = os.environ.get('CALIBRE_DBPATH', BASE_DIR)
ROLE_USER = 0 << 0 ROLE_USER = 0 << 0
@ -83,8 +91,8 @@ AUTO_UPDATE_NIGHTLY = 1 << 2
LOGIN_STANDARD = 0 LOGIN_STANDARD = 0
LOGIN_LDAP = 1 LOGIN_LDAP = 1
LOGIN_OAUTH_GITHUB = 2 LOGIN_OAUTH = 2
LOGIN_OAUTH_GOOGLE = 3 # LOGIN_OAUTH_GOOGLE = 3
DEFAULT_PASSWORD = "admin123" DEFAULT_PASSWORD = "admin123"
@ -116,7 +124,7 @@ def selected_roles(dictionary):
BookMeta = namedtuple('BookMeta', 'file_path, extension, title, author, cover, description, tags, series, ' BookMeta = namedtuple('BookMeta', 'file_path, extension, title, author, cover, description, tags, series, '
'series_id, languages') 'series_id, languages')
STABLE_VERSION = {'version': '0.6.4 Beta'} STABLE_VERSION = {'version': '0.6.5 Beta'}
NIGHTLY_VERSION = {} NIGHTLY_VERSION = {}
NIGHTLY_VERSION[0] = '$Format:%H$' NIGHTLY_VERSION[0] = '$Format:%H$'

@ -39,12 +39,12 @@ except ImportError:
gdrive_support = False gdrive_support = False
from . import logger, cli, config from . import logger, cli, config
from .constants import BASE_DIR as _BASE_DIR from .constants import CONFIG_DIR as _CONFIG_DIR
SETTINGS_YAML = os.path.join(_BASE_DIR, 'settings.yaml') SETTINGS_YAML = os.path.join(_CONFIG_DIR, 'settings.yaml')
CREDENTIALS = os.path.join(_BASE_DIR, 'gdrive_credentials') CREDENTIALS = os.path.join(_CONFIG_DIR, 'gdrive_credentials')
CLIENT_SECRETS = os.path.join(_BASE_DIR, 'client_secrets.json') CLIENT_SECRETS = os.path.join(_CONFIG_DIR, 'client_secrets.json')
log = logger.create() log = logger.create()

@ -71,14 +71,6 @@ from .worker import TASK_EMAIL, TASK_CONVERT, TASK_UPLOAD, TASK_CONVERT_ANY
log = logger.create() log = logger.create()
def update_download(book_id, user_id):
check = ub.session.query(ub.Downloads).filter(ub.Downloads.user_id == user_id).filter(ub.Downloads.book_id ==
book_id).first()
if not check:
new_download = ub.Downloads(user_id=user_id, book_id=book_id)
ub.session.add(new_download)
ub.session.commit()
# 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 = db.session.query(db.Books).filter(db.Books.id == book_id).first()
@ -563,6 +555,7 @@ def check_unrar(unrarLocation):
try: try:
if sys.version_info < (3, 0): if sys.version_info < (3, 0):
unrarLocation = unrarLocation.encode(sys.getfilesystemencoding()) unrarLocation = unrarLocation.encode(sys.getfilesystemencoding())
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)
if value: if value:

@ -23,7 +23,7 @@ import logging
from logging import Formatter, StreamHandler from logging import Formatter, StreamHandler
from logging.handlers import RotatingFileHandler from logging.handlers import RotatingFileHandler
from .constants import BASE_DIR as _BASE_DIR from .constants import CONFIG_DIR as _CONFIG_DIR
ACCESS_FORMATTER_GEVENT = Formatter("%(message)s") ACCESS_FORMATTER_GEVENT = Formatter("%(message)s")
@ -31,8 +31,8 @@ ACCESS_FORMATTER_TORNADO = Formatter("[%(asctime)s] %(message)s")
FORMATTER = Formatter("[%(asctime)s] %(levelname)5s {%(name)s:%(lineno)d} %(message)s") FORMATTER = Formatter("[%(asctime)s] %(levelname)5s {%(name)s:%(lineno)d} %(message)s")
DEFAULT_LOG_LEVEL = logging.INFO DEFAULT_LOG_LEVEL = logging.INFO
DEFAULT_LOG_FILE = os.path.join(_BASE_DIR, "calibre-web.log") DEFAULT_LOG_FILE = os.path.join(_CONFIG_DIR, "calibre-web.log")
DEFAULT_ACCESS_LOG = os.path.join(_BASE_DIR, "access.log") DEFAULT_ACCESS_LOG = os.path.join(_CONFIG_DIR, "access.log")
LOG_TO_STDERR = '/dev/stderr' LOG_TO_STDERR = '/dev/stderr'
logging.addLevelName(logging.WARNING, "WARN") logging.addLevelName(logging.WARNING, "WARN")
@ -76,7 +76,7 @@ def is_valid_logfile(file_path):
def _absolute_log_file(log_file, default_log_file): def _absolute_log_file(log_file, default_log_file):
if log_file: if log_file:
if not os.path.dirname(log_file): if not os.path.dirname(log_file):
log_file = os.path.join(_BASE_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
@ -95,6 +95,11 @@ def setup(log_file, log_level=None):
Configure the logging output. Configure the logging output.
May be called multiple times. May be called multiple times.
''' '''
# if debugging, start logging to stderr immediately
if os.environ.get('FLASK_DEBUG', None):
log_file = LOG_TO_STDERR
log_level = logging.DEBUG
log_file = _absolute_log_file(log_file, DEFAULT_LOG_FILE) log_file = _absolute_log_file(log_file, DEFAULT_LOG_FILE)
log_level = log_level or DEFAULT_LOG_LEVEL log_level = log_level or DEFAULT_LOG_LEVEL
@ -162,8 +167,3 @@ class StderrLogger(object):
self.buffer += message self.buffer += message
except Exception: except Exception:
self.log.debug("Logging Error") self.log.debug("Logging Error")
# if debugging, start logging to stderr immediately
if os.environ.get('FLASK_DEBUG', None):
setup(LOG_TO_STDERR, logging.DEBUG)

@ -1,6 +1,22 @@
#!/usr/bin/env python #!/usr/bin/env python
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
# This file is part of the Calibre-Web (https://github.com/janeczku/calibre-web)
# Copyright (C) 2018-2019 jim3ma
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>
from __future__ import division, print_function, unicode_literals from __future__ import division, print_function, unicode_literals
from flask import session from flask import session
@ -16,13 +32,14 @@ try:
.. _SQLAlchemy: http://www.sqlalchemy.org/ .. _SQLAlchemy: http://www.sqlalchemy.org/
""" """
def __init__(self, model, session, def __init__(self, model, session, provider_id,
user=None, user_id=None, user_required=None, anon_user=None, user=None, user_id=None, user_required=None, anon_user=None,
cache=None): cache=None):
self.provider_id = provider_id
super(OAuthBackend, self).__init__(model, session, user, user_id, user_required, anon_user, cache) super(OAuthBackend, self).__init__(model, session, user, user_id, user_required, anon_user, cache)
def get(self, blueprint, user=None, user_id=None): def get(self, blueprint, user=None, user_id=None):
if blueprint.name + '_oauth_token' in session and session[blueprint.name + '_oauth_token'] != '': if self.provider_id + '_oauth_token' in session and session[self.provider_id + '_oauth_token'] != '':
return session[blueprint.name + '_oauth_token'] return session[blueprint.name + '_oauth_token']
# check cache # check cache
cache_key = self.make_cache_key(blueprint=blueprint, user=user, user_id=user_id) cache_key = self.make_cache_key(blueprint=blueprint, user=user, user_id=user_id)
@ -33,15 +50,15 @@ try:
# if not cached, make database queries # if not cached, make database queries
query = ( query = (
self.session.query(self.model) self.session.query(self.model)
.filter_by(provider=blueprint.name) .filter_by(provider=self.provider_id)
) )
uid = first([user_id, self.user_id, blueprint.config.get("user_id")]) uid = first([user_id, self.user_id, blueprint.config.get("user_id")])
u = first(_get_real_user(ref, self.anon_user) u = first(_get_real_user(ref, self.anon_user)
for ref in (user, self.user, blueprint.config.get("user"))) for ref in (user, self.user, blueprint.config.get("user")))
use_provider_user_id = False use_provider_user_id = False
if blueprint.name + '_oauth_user_id' in session and session[blueprint.name + '_oauth_user_id'] != '': if self.provider_id + '_oauth_user_id' in session and session[self.provider_id + '_oauth_user_id'] != '':
query = query.filter_by(provider_user_id=session[blueprint.name + '_oauth_user_id']) query = query.filter_by(provider_user_id=session[self.provider_id + '_oauth_user_id'])
use_provider_user_id = True use_provider_user_id = True
if self.user_required and not u and not uid and not use_provider_user_id: if self.user_required and not u and not uid and not use_provider_user_id:
@ -78,7 +95,7 @@ try:
# if there was an existing model, delete it # if there was an existing model, delete it
existing_query = ( existing_query = (
self.session.query(self.model) self.session.query(self.model)
.filter_by(provider=blueprint.name) .filter_by(provider=self.provider_id)
) )
# check for user ID # check for user ID
has_user_id = hasattr(self.model, "user_id") has_user_id = hasattr(self.model, "user_id")
@ -92,7 +109,7 @@ try:
existing_query.delete() existing_query.delete()
# create a new model for this token # create a new model for this token
kwargs = { kwargs = {
"provider": blueprint.name, "provider": self.provider_id,
"token": token, "token": token,
} }
if has_user_id and uid: if has_user_id and uid:
@ -110,7 +127,7 @@ try:
def delete(self, blueprint, user=None, user_id=None): def delete(self, blueprint, user=None, user_id=None):
query = ( query = (
self.session.query(self.model) self.session.query(self.model)
.filter_by(provider=blueprint.name) .filter_by(provider=self.provider_id)
) )
uid = first([user_id, self.user_id, blueprint.config.get("user_id")]) uid = first([user_id, self.user_id, blueprint.config.get("user_id")])
u = first(_get_real_user(ref, self.anon_user) u = first(_get_real_user(ref, self.anon_user)

@ -45,10 +45,10 @@ oauth = Blueprint('oauth', __name__)
log = logger.create() log = logger.create()
def github_oauth_required(f): def oauth_required(f):
@wraps(f) @wraps(f)
def inner(*args, **kwargs): def inner(*args, **kwargs):
if config.config_login_type == constants.LOGIN_OAUTH_GITHUB: if config.config_login_type == constants.LOGIN_OAUTH:
return f(*args, **kwargs) return f(*args, **kwargs)
if request.is_xhr: if request.is_xhr:
data = {'status': 'error', 'message': 'Not Found'} data = {'status': 'error', 'message': 'Not Found'}
@ -60,30 +60,14 @@ def github_oauth_required(f):
return inner return inner
def google_oauth_required(f): def register_oauth_blueprint(id, show_name):
@wraps(f) oauth_check[id] = show_name
def inner(*args, **kwargs):
if config.config_use_google_oauth == constants.LOGIN_OAUTH_GOOGLE:
return f(*args, **kwargs)
if request.is_xhr:
data = {'status': 'error', 'message': 'Not Found'}
response = make_response(json.dumps(data, ensure_ascii=False))
response.headers["Content-Type"] = "application/json; charset=utf-8"
return response, 404
abort(404)
return inner
def register_oauth_blueprint(blueprint, show_name):
if blueprint.name != "":
oauth_check[blueprint.name] = show_name
def register_user_with_oauth(user=None): def register_user_with_oauth(user=None):
all_oauth = {} all_oauth = {}
for oauth in oauth_check.keys(): for oauth in oauth_check.keys():
if oauth + '_oauth_user_id' in session and session[oauth + '_oauth_user_id'] != '': if str(oauth) + '_oauth_user_id' in session and session[str(oauth) + '_oauth_user_id'] != '':
all_oauth[oauth] = oauth_check[oauth] all_oauth[oauth] = oauth_check[oauth]
if len(all_oauth.keys()) == 0: if len(all_oauth.keys()) == 0:
return return
@ -94,7 +78,7 @@ def register_user_with_oauth(user=None):
# Find this OAuth token in the database, or create it # Find this OAuth token in the database, or create it
query = ub.session.query(ub.OAuth).filter_by( query = ub.session.query(ub.OAuth).filter_by(
provider=oauth, provider=oauth,
provider_user_id=session[oauth + "_oauth_user_id"], provider_user_id=session[str(oauth) + "_oauth_user_id"],
) )
try: try:
oauth = query.one() oauth = query.one()
@ -111,39 +95,61 @@ def register_user_with_oauth(user=None):
def logout_oauth_user(): def logout_oauth_user():
for oauth in oauth_check.keys(): for oauth in oauth_check.keys():
if oauth + '_oauth_user_id' in session: if str(oauth) + '_oauth_user_id' in session:
session.pop(oauth + '_oauth_user_id') session.pop(str(oauth) + '_oauth_user_id')
if ub.oauth_support: if ub.oauth_support:
github_blueprint = make_github_blueprint( oauthblueprints =[]
client_id=config.config_github_oauth_client_id, if not ub.session.query(ub.OAuthProvider).count():
client_secret=config.config_github_oauth_client_secret, oauth = ub.OAuthProvider()
redirect_to="oauth.github_login",) oauth.provider_name = "github"
oauth.active = False
google_blueprint = make_google_blueprint( ub.session.add(oauth)
client_id=config.config_google_oauth_client_id, ub.session.commit()
client_secret=config.config_google_oauth_client_secret, oauth = ub.OAuthProvider()
redirect_to="oauth.google_login", oauth.provider_name = "google"
scope=[ oauth.active = False
"https://www.googleapis.com/auth/plus.me", ub.session.add(oauth)
"https://www.googleapis.com/auth/userinfo.email", ub.session.commit()
]
) oauth_ids = ub.session.query(ub.OAuthProvider).all()
ele1=dict(provider_name='github',
app.register_blueprint(google_blueprint, url_prefix="/login") id=oauth_ids[0].id,
app.register_blueprint(github_blueprint, url_prefix='/login') active=oauth_ids[0].active,
oauth_client_id=oauth_ids[0].oauth_client_id,
github_blueprint.backend = OAuthBackend(ub.OAuth, ub.session, user=current_user, user_required=True) scope=None,
google_blueprint.backend = OAuthBackend(ub.OAuth, ub.session, user=current_user, user_required=True) oauth_client_secret=oauth_ids[0].oauth_client_secret,
obtain_link='https://github.com/settings/developers')
ele2=dict(provider_name='google',
if config.config_login_type == constants.LOGIN_OAUTH_GITHUB: id=oauth_ids[1].id,
register_oauth_blueprint(github_blueprint, 'GitHub') active=oauth_ids[1].active,
if config.config_login_type == constants.LOGIN_OAUTH_GOOGLE: scope=["https://www.googleapis.com/auth/plus.me", "https://www.googleapis.com/auth/userinfo.email"],
register_oauth_blueprint(google_blueprint, 'Google') oauth_client_id=oauth_ids[1].oauth_client_id,
oauth_client_secret=oauth_ids[1].oauth_client_secret,
obtain_link='https://github.com/settings/developers')
oauthblueprints.append(ele1)
oauthblueprints.append(ele2)
for element in oauthblueprints:
if element['provider_name'] == 'github':
blueprint_func = make_github_blueprint
else:
blueprint_func = make_google_blueprint
blueprint = blueprint_func(
client_id=element['oauth_client_id'],
client_secret=element['oauth_client_secret'],
redirect_to="oauth."+element['provider_name']+"_login",
scope = element['scope']
)
element['blueprint']=blueprint
app.register_blueprint(blueprint, url_prefix="/login")
element['blueprint'].backend = OAuthBackend(ub.OAuth, ub.session, str(element['id']),
user=current_user, user_required=True)
if element['active']:
register_oauth_blueprint(element['id'], element['provider_name'])
@oauth_authorized.connect_via(github_blueprint) @oauth_authorized.connect_via(oauthblueprints[0]['blueprint'])
def github_logged_in(blueprint, token): def github_logged_in(blueprint, token):
if not token: if not token:
flash(_(u"Failed to log in with GitHub."), category="error") flash(_(u"Failed to log in with GitHub."), category="error")
@ -156,10 +162,10 @@ if ub.oauth_support:
github_info = resp.json() github_info = resp.json()
github_user_id = str(github_info["id"]) github_user_id = str(github_info["id"])
return oauth_update_token(blueprint, token, github_user_id) return oauth_update_token(str(oauthblueprints[0]['id']), token, github_user_id)
@oauth_authorized.connect_via(google_blueprint) @oauth_authorized.connect_via(oauthblueprints[1]['blueprint'])
def google_logged_in(blueprint, token): def google_logged_in(blueprint, token):
if not token: if not token:
flash(_(u"Failed to log in with Google."), category="error") flash(_(u"Failed to log in with Google."), category="error")
@ -172,17 +178,16 @@ if ub.oauth_support:
google_info = resp.json() google_info = resp.json()
google_user_id = str(google_info["id"]) google_user_id = str(google_info["id"])
return oauth_update_token(str(oauthblueprints[1]['id']), token, google_user_id)
return oauth_update_token(blueprint, token, google_user_id)
def oauth_update_token(provider_id, token, provider_user_id):
def oauth_update_token(blueprint, token, provider_user_id): session[provider_id + "_oauth_user_id"] = provider_user_id
session[blueprint.name + "_oauth_user_id"] = provider_user_id session[provider_id + "_oauth_token"] = token
session[blueprint.name + "_oauth_token"] = token
# Find this OAuth token in the database, or create it # Find this OAuth token in the database, or create it
query = ub.session.query(ub.OAuth).filter_by( query = ub.session.query(ub.OAuth).filter_by(
provider=blueprint.name, provider=provider_id,
provider_user_id=provider_user_id, provider_user_id=provider_user_id,
) )
try: try:
@ -191,7 +196,7 @@ if ub.oauth_support:
oauth.token = token oauth.token = token
except NoResultFound: except NoResultFound:
oauth = ub.OAuth( oauth = ub.OAuth(
provider=blueprint.name, provider=provider_id,
provider_user_id=provider_user_id, provider_user_id=provider_user_id,
token=token, token=token,
) )
@ -206,9 +211,9 @@ if ub.oauth_support:
return False return False
def bind_oauth_or_register(provider, provider_user_id, redirect_url): def bind_oauth_or_register(provider_id, provider_user_id, redirect_url):
query = ub.session.query(ub.OAuth).filter_by( query = ub.session.query(ub.OAuth).filter_by(
provider=provider, provider=provider_id,
provider_user_id=provider_user_id, provider_user_id=provider_user_id,
) )
try: try:
@ -245,7 +250,7 @@ if ub.oauth_support:
try: try:
oauths = query.all() oauths = query.all()
for oauth in oauths: for oauth in oauths:
status.append(oauth.provider) status.append(int(oauth.provider))
return status return status
except NoResultFound: except NoResultFound:
return None return None
@ -278,7 +283,7 @@ if ub.oauth_support:
# notify on OAuth provider error # notify on OAuth provider error
@oauth_error.connect_via(github_blueprint) @oauth_error.connect_via(oauthblueprints[0]['blueprint'])
def github_error(blueprint, error, error_description=None, error_uri=None): def github_error(blueprint, error, error_description=None, error_uri=None):
msg = ( msg = (
u"OAuth error from {name}! " u"OAuth error from {name}! "
@ -293,14 +298,14 @@ if ub.oauth_support:
@oauth.route('/github') @oauth.route('/github')
@github_oauth_required @oauth_required
def github_login(): def github_login():
if not github.authorized: if not github.authorized:
return redirect(url_for('github.login')) return redirect(url_for('github.login'))
account_info = github.get('/user') account_info = github.get('/user')
if account_info.ok: if account_info.ok:
account_info_json = account_info.json() account_info_json = account_info.json()
return bind_oauth_or_register(github_blueprint.name, account_info_json['id'], 'github.login') return bind_oauth_or_register(oauthblueprints[0]['id'], account_info_json['id'], 'github.login')
flash(_(u"GitHub Oauth error, please retry later."), category="error") flash(_(u"GitHub Oauth error, please retry later."), category="error")
return redirect(url_for('web.login')) return redirect(url_for('web.login'))
@ -308,23 +313,23 @@ if ub.oauth_support:
@oauth.route('/unlink/github', methods=["GET"]) @oauth.route('/unlink/github', methods=["GET"])
@login_required @login_required
def github_login_unlink(): def github_login_unlink():
return unlink_oauth(github_blueprint.name) return unlink_oauth(oauthblueprints[0]['id'])
@oauth.route('/login/google') @oauth.route('/login/google')
@google_oauth_required @oauth_required
def google_login(): def google_login():
if not google.authorized: if not google.authorized:
return redirect(url_for("google.login")) return redirect(url_for("google.login"))
resp = google.get("/oauth2/v2/userinfo") resp = google.get("/oauth2/v2/userinfo")
if resp.ok: if resp.ok:
account_info_json = resp.json() account_info_json = resp.json()
return bind_oauth_or_register(google_blueprint.name, account_info_json['id'], 'google.login') return bind_oauth_or_register(oauthblueprints[1]['id'], account_info_json['id'], 'google.login')
flash(_(u"Google Oauth error, please retry later."), category="error") flash(_(u"Google Oauth error, please retry later."), category="error")
return redirect(url_for('web.login')) return redirect(url_for('web.login'))
@oauth_error.connect_via(google_blueprint) @oauth_error.connect_via(oauthblueprints[1]['blueprint'])
def google_error(blueprint, error, error_description=None, error_uri=None): def google_error(blueprint, error, error_description=None, error_uri=None):
msg = ( msg = (
u"OAuth error from {name}! " u"OAuth error from {name}! "
@ -341,4 +346,4 @@ if ub.oauth_support:
@oauth.route('/unlink/google', methods=["GET"]) @oauth.route('/unlink/google', methods=["GET"])
@login_required @login_required
def google_login_unlink(): def google_login_unlink():
return unlink_oauth(google_blueprint.name) return unlink_oauth(oauthblueprints[1]['blueprint'].name)

@ -47,8 +47,6 @@ def init_app(app, config):
app.config['LDAP_USE_TLS'] = bool(config.config_ldap_use_tls) app.config['LDAP_USE_TLS'] = bool(config.config_ldap_use_tls)
app.config['LDAP_OPENLDAP'] = bool(config.config_ldap_openldap) app.config['LDAP_OPENLDAP'] = bool(config.config_ldap_openldap)
# app.config['LDAP_BASE_DN'] = 'ou=users,dc=yunohost,dc=org'
# app.config['LDAP_USER_OBJECT_FILTER'] = '(uid=%s)'
_ldap.init_app(app) _ldap.init_app(app)

@ -141,6 +141,7 @@ input.pill:not(:checked) + label .glyphicon {
.filterheader { margin-bottom: 20px; } .filterheader { margin-bottom: 20px; }
.errorlink {margin-top: 20px;}
.modal-body .comments { .modal-body .comments {
max-height:300px; max-height:300px;
overflow-y: auto; overflow-y: auto;

@ -37,10 +37,11 @@ $(document).on("change", "select[data-control]", function() {
var showOrHide = parseInt($this.val()); var showOrHide = parseInt($this.val());
// var showOrHideLast = $("#" + name + " option:last").val() // var showOrHideLast = $("#" + name + " option:last").val()
for (var i = 0; i < $(this)[0].length; i++) { for (var i = 0; i < $(this)[0].length; i++) {
if (parseInt($(this)[0][i].value) === showOrHide) { var element = parseInt($(this)[0][i].value);
$("[data-related=\"" + name + "-" + i + "\"]").show(); if (element === showOrHide) {
$("[data-related=" + name + "-" + element + "]").show();
} else { } else {
$("[data-related=\"" + name + "-" + i + "\"]").hide(); $("[data-related=" + name + "-" + element + "]").hide();
} }
} }
}); });

@ -19,6 +19,7 @@
<label for="config_calibre_dir">{{_('Location of Calibre database')}}</label> <label for="config_calibre_dir">{{_('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" name="config_calibre_dir" id="config_calibre_dir" value="{% if config.config_calibre_dir != None %}{{ config.config_calibre_dir }}{% endif %}" autocomplete="off">
</div> </div>
{% 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 %} >
<label for="config_use_google_drive">{{_('Use Google Drive?')}}</label> <label for="config_use_google_drive">{{_('Use Google Drive?')}}</label>
@ -66,6 +67,7 @@
{% endif %} {% endif %}
{% endif %} {% endif %}
</div> </div>
{% endif %}
</div> </div>
</div> </div>
</div> </div>
@ -193,8 +195,7 @@
<option value="1" {% if config.config_login_type == 1 %}selected{% endif %}>{{_('Use LDAP Authentication')}}</option> <option value="1" {% if config.config_login_type == 1 %}selected{% endif %}>{{_('Use LDAP Authentication')}}</option>
{% endif %} {% endif %}
{% if feature_support['oauth'] %} {% if feature_support['oauth'] %}
<option value="2" {% if config.config_login_type == 2 %}selected{% endif %}>{{_('Use GitHub OAuth')}}</option> <option value="2" {% if config.config_login_type == 2 %}selected{% endif %}>{{_('Use OAuth')}}</option>
<option value="3" {% if config.config_login_type == 3 %}selected{% endif %}>{{_('Use Google OAuth')}}</option>
{% endif %} {% endif %}
</select> </select>
</div> </div>
@ -254,30 +255,19 @@
{% endif %} {% endif %}
{% if feature_support['oauth'] %} {% if feature_support['oauth'] %}
<div data-related="login-settings-2"> <div data-related="login-settings-2">
{% for prov in provider %}
<div class="form-group"> <div class="form-group">
<a href="https://github.com/settings/developers" target="_blank">{{_('Obtain GitHub OAuth Credential')}}</a> <a href="{{prov['obtain_link']}}" target="_blank">{{_('Obtain %(provider)s OAuth Credential', provider=prov['provider_name'])}}</a>
</div>
<div class="form-group">
<label for="config_github_oauth_client_id">{{_('GitHub OAuth Client Id')}}</label>
<input type="text" class="form-control" id="config_github_oauth_client_id" name="config_github_oauth_client_id" value="{% if config.config_github_oauth_client_id != None %}{{ config.config_github_oauth_client_id }}{% endif %}" autocomplete="off">
</div>
<div class="form-group">
<label for="config_github_oauth_client_secret">{{_('GitHub OAuth Client Secret')}}</label>
<input type="text" class="form-control" id="config_github_oauth_client_secret" name="config_github_oauth_client_secret" value="{% if config.config_github_oauth_client_secret != None %}{{ config.config_github_oauth_client_secret }}{% endif %}" autocomplete="off">
</div>
</div>
<div data-related="login-settings-3">
<div class="form-group">
<a href="https://console.developers.google.com/apis/credentials" target="_blank">{{_('Obtain Google OAuth Credential')}}</a>
</div> </div>
<div class="form-group"> <div class="form-group">
<label for="config_google_oauth_client_id">{{_('Google OAuth Client Id')}}</label> <label for="config_{{ prov['id'] }}_oauth_client_id">{{_('%(provider)s OAuth Client Id', provider=prov['provider_name'])}}</label>
<input type="text" class="form-control" id="config_google_oauth_client_id" name="config_google_oauth_client_id" value="{% if config.config_google_oauth_client_id != None %}{{ config.config_google_oauth_client_id }}{% endif %}" autocomplete="off"> <input type="text" class="form-control" id="config_{{ prov['id'] }}_oauth_client_id" name="config_{{ prov['id'] }}_oauth_client_id" value="{% if prov['oauth_client_id']%}{{ prov['oauth_client_id'] }}{% endif %}" autocomplete="off">
</div> </div>
<div class="form-group"> <div class="form-group">
<label for="config_google_oauth_client_secret">{{_('Google OAuth Client Secret')}}</label> <label for="config_{{ prov['id'] }}_oauth_client_secret">{{_('%(provider)s OAuth Client Secret', provider=prov['provider_name'])}}</label>
<input type="text" class="form-control" id="config_google_oauth_client_secret" name="config_google_oauth_client_secret" value="{% if config.config_google_oauth_client_secret != None %}{{ config.config_google_oauth_client_secret }}{% endif %}" autocomplete="off"> <input type="text" class="form-control" id="config_{{ prov['id'] }}_oauth_client_secret" name="config_{{ prov['id'] }}_oauth_client_secret" value="{% if prov['oauth_client_secret']%}{{ prov['oauth_client_secret'] }}{% endif %}" autocomplete="off">
</div> </div>
{% endfor %}
</div> </div>
{% endif %} {% endif %}
{% endif %} {% endif %}

@ -17,10 +17,34 @@
{% endif %} {% endif %}
</head> </head>
<body> <body>
<div class="text-center"> <div class="container-fluid">
<h1>{{ error_code }}</h1> <div class="row">
<div class="col">
<h1 class="text-center">{{ error_code }}</h1>
<h3>{{ error_name }}</h3> <h3>{{ error_name }}</h3>
</div>
</div>
<div class="row">
<div class="col-md-offset-4 text-left">
{% for element in error_stack %}
<div>{{ element }}</div>
{% endfor %}
</div>
</div>
{% if issue %}
<div class="row">
<div class="col errorlink">Please report this issue with all related information:
<a href="https://github.com/janeczku/calibre-web/issues/new">{{_('Create issue')}}</a>
</div>
</div>
{% endif %}
<div class="row">
<div class="col errorlink">
<a href="{{url_for('web.index')}}" title="{{ _('Back to home') }}">{{_('Back to home')}}</a> <a href="{{url_for('web.index')}}" title="{{ _('Back to home') }}">{{_('Back to home')}}</a>
</div> </div>
</div>
</div>
</body> </body>
</html> </html>

@ -10,9 +10,10 @@
{% endif %} {% endif %}
<div class="row"> <div class="row">
<div class="col-xs-2 col-sm-2 col-md-1" align="left"><span class="badge">{{lang_counter[loop.index0].bookcount}}</span></div> <div class="col-xs-2 col-sm-2 col-md-1" align="left"><span class="badge">{{lang_counter[loop.index0].bookcount}}</span></div>
<div class="col-xs-10 col-sm-10 col-md-11"><a id="list_{{loop.index0}}" href="{{url_for('web.language', name=lang.lang_code)}}">{{lang.name}}</a></div> <div class="col-xs-10 col-sm-10 col-md-11"><a id="list_{{loop.index0}}" href="{{url_for('web.books_list', book_id=lang.lang_code, data=data, sort='new')}}">{{lang.name}}</a></div>
</div> </div>
{% endfor %} {% endfor %}
</div> </div>
</div> </div>
{% endblock %} {% endblock %}

@ -79,15 +79,6 @@
</div> </div>
<div class="overlay"></div> <div class="overlay"></div>
<script type="text/javascript">
window.calibre = {
filePath: "{{ url_for('static', filename='js/libs/') }}",
cssPath: "{{ url_for('static', filename='css/') }}",
bookmarkUrl: "{{ url_for('bookmark', book_id=bookid, book_format='EPUB') }}",
bookUrl: "{{ url_for('get_download_link_ext', book_id=bookid, book_format="epub", anyname='file.epub') }}",
bookmark: "{{ bookmark.bookmark_key if bookmark != None }}",
useBookmarks: {{ g.user.is_authenticated | tojson }} };
</script>
<script src="{{ url_for('static', filename='js/libs/jszip.min.js') }}"> <script src="{{ url_for('static', filename='js/libs/jszip.min.js') }}">
</script> <script src="{{ url_for('static', filename='js/libs/epub.min.js') }}"></script> </script> <script src="{{ url_for('static', filename='js/libs/epub.min.js') }}"></script>
<script type="text/javascript"> <script type="text/javascript">

@ -47,13 +47,13 @@
</div> </div>
{% if registered_oauth.keys()| length > 0 %} {% if registered_oauth.keys()| length > 0 %}
{% for oauth, name in registered_oauth.iteritems() %} {% for id, name in registered_oauth.items() %}
<div class="form-group"> <div class="form-group">
<label>{{ name }} {{_('OAuth Settings')}}</label> <label>{{ name }} {{_('OAuth Settings')}}</label>
{% if oauth not in oauth_status %} {% if id not in oauth_status %}
<div><a href="{{ url_for(oauth +'.login') }}" id="config_{{ oauth }}_oauth" class="btn btn-primary">{{_('Link')}}</a></div> <div><a href="{{ url_for('oauth.'+ name +'_login') }}" id="config_{{ id }}_oauth" class="btn btn-primary">{{_('Link')}}</a></div>
{% else %} {% else %}
<div><a href="{{ url_for('oauth.'+ oauth +'_login_unlink') }}" id="config_{{ oauth }}_oauth" class="btn btn-primary">{{_('Unlink')}}</a></div> <div><a href="{{ url_for('oauth.'+ name +'_login_unlink') }}" id="config_{{ id }}_oauth" class="btn btn-primary">{{_('Unlink')}}</a></div>
{% endif %} {% endif %}
{% endfor %} {% endfor %}
</div> </div>

@ -180,6 +180,7 @@ class User(UserBase, Base):
default_language = Column(String(3), default="all") default_language = Column(String(3), default="all")
mature_content = Column(Boolean, default=True) mature_content = Column(Boolean, default=True)
if oauth_support: if oauth_support:
class OAuth(OAuthConsumerMixin, Base): class OAuth(OAuthConsumerMixin, Base):
provider_user_id = Column(String(256)) provider_user_id = Column(String(256))
@ -187,6 +188,16 @@ if oauth_support:
user = relationship(User) user = relationship(User)
class OAuthProvider(Base):
__tablename__ = 'oauthProvider'
id = Column(Integer, primary_key=True)
provider_name = Column(String)
oauth_client_id = Column(String)
oauth_client_secret = Column(String)
active = Column(Boolean)
# Class for anonymous user is derived from User base and completly overrides methods and properties for the # Class for anonymous user is derived from User base and completly overrides methods and properties for the
# anonymous user # anonymous user
class Anonymous(AnonymousUserMixin, UserBase): class Anonymous(AnonymousUserMixin, UserBase):

@ -33,7 +33,7 @@ import requests
from babel.dates import format_datetime from babel.dates import format_datetime
from flask_babel import gettext as _ from flask_babel import gettext as _
from . import constants, logger, config, get_locale, web_server from . import constants, logger, config, web_server
log = logger.create() log = logger.create()
@ -62,10 +62,10 @@ class Updater(threading.Thread):
return self._stable_version_info() return self._stable_version_info()
return self._nightly_version_info() return self._nightly_version_info()
def get_available_updates(self, request_method): def get_available_updates(self, request_method, locale):
if config.config_updatechannel == constants.UPDATE_STABLE: if config.config_updatechannel == constants.UPDATE_STABLE:
return self._stable_available_updates(request_method) return self._stable_available_updates(request_method)
return self._nightly_available_updates(request_method) return self._nightly_available_updates(request_method,locale)
def run(self): def run(self):
try: try:
@ -239,7 +239,7 @@ class Updater(threading.Thread):
def _stable_version_info(cls): def _stable_version_info(cls):
return constants.STABLE_VERSION # Current version return constants.STABLE_VERSION # Current version
def _nightly_available_updates(self, request_method): def _nightly_available_updates(self, request_method, locale):
tz = datetime.timedelta(seconds=time.timezone if (time.localtime().tm_isdst == 0) else time.altzone) tz = datetime.timedelta(seconds=time.timezone if (time.localtime().tm_isdst == 0) else time.altzone)
if request_method == "GET": if request_method == "GET":
repository_url = _REPOSITORY_API_URL repository_url = _REPOSITORY_API_URL
@ -288,7 +288,7 @@ class Updater(threading.Thread):
update_data['committer']['date'], '%Y-%m-%dT%H:%M:%SZ') - tz update_data['committer']['date'], '%Y-%m-%dT%H:%M:%SZ') - tz
parents.append( parents.append(
[ [
format_datetime(new_commit_date, format='short', locale=get_locale()), format_datetime(new_commit_date, format='short', locale=locale),
update_data['message'], update_data['message'],
update_data['sha'] update_data['sha']
] ]
@ -319,7 +319,7 @@ class Updater(threading.Thread):
parent_commit_date = datetime.datetime.strptime( parent_commit_date = datetime.datetime.strptime(
parent_data['committer']['date'], '%Y-%m-%dT%H:%M:%SZ') - tz parent_data['committer']['date'], '%Y-%m-%dT%H:%M:%SZ') - tz
parent_commit_date = format_datetime( parent_commit_date = format_datetime(
parent_commit_date, format='short', locale=get_locale()) parent_commit_date, format='short', locale=locale)
parents.append([parent_commit_date, parents.append([parent_commit_date,
parent_data['message'].replace('\r\n', '<p>').replace('\n', '<p>')]) parent_data['message'].replace('\r\n', '<p>').replace('\n', '<p>')])
@ -331,7 +331,7 @@ class Updater(threading.Thread):
else: else:
# parent is our current version # parent is our current version
break break
status['history'] = parents[::-1]
else: else:
status['success'] = False status['success'] = False
status['message'] = _(u'Could not fetch update information') status['message'] = _(u'Could not fetch update information')

@ -42,7 +42,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.warning('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:
@ -50,21 +50,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.warning('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.warning('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.warning('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:
@ -72,7 +72,7 @@ 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.warning('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_generic_pdf_cover = True use_generic_pdf_cover = True
use_PIL = False use_PIL = False

@ -27,6 +27,8 @@ import base64
import datetime import datetime
import json import json
import mimetypes import mimetypes
import traceback
import sys
from babel import Locale as LC from babel import Locale as LC
from babel.dates import format_date from babel.dates import format_date
@ -52,7 +54,7 @@ from .pagination import Pagination
from .redirect import redirect_back from .redirect import redirect_back
feature_support = { feature_support = {
'ldap': bool(services.ldap), 'ldap': False, # bool(services.ldap),
'goodreads': bool(services.goodreads) 'goodreads': bool(services.goodreads)
} }
@ -83,16 +85,30 @@ except ImportError:
# custom error page # custom error page
def error_http(error): def error_http(error):
return render_template('http_error.html', return render_template('http_error.html',
error_code=error.code, error_code="Error {0}".format(error.code),
error_name=error.name, error_name=error.name,
issue=False,
instance=config.config_calibre_web_title instance=config.config_calibre_web_title
), error.code ), error.code
def internal_error(error):
__, __, tb = sys.exc_info()
return render_template('http_error.html',
error_code="Internal Server Error",
error_name=str(error),
issue=True,
error_stack=traceback.format_tb(tb),
instance=config.config_calibre_web_title
), 500
# http error handling # http error handling
for ex in default_exceptions: for ex in default_exceptions:
# new routine for all client errors, server errors stay
if ex < 500: if ex < 500:
app.register_error_handler(ex, error_http) app.register_error_handler(ex, error_http)
elif ex == 500:
app.register_error_handler(ex, internal_error)
web = Blueprint('web', __name__) web = Blueprint('web', __name__)
@ -498,6 +514,8 @@ def books_list(data, sort, book_id, page):
return render_formats_books(page, book_id, order) return render_formats_books(page, book_id, order)
elif data == "category": elif data == "category":
return render_category_books(page, book_id, order) return render_category_books(page, book_id, order)
elif data == "language":
return render_language_books(page, book_id, order)
else: else:
entries, random, pagination = fill_indexpage(page, db.Books, True, order) entries, random, pagination = 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,
@ -552,8 +570,8 @@ def render_author_books(page, author_id, order):
other_books = services.goodreads.get_other_books(author_info, entries) other_books = services.goodreads.get_other_books(author_info, entries)
return render_title_template('author.html', entries=entries, pagination=pagination, id=author_id, return render_title_template('author.html', entries=entries, pagination=pagination, id=author_id,
title=_(u"Author: %(name)s", name=author_name), author=author_info, other_books=other_books, title=_(u"Author: %(name)s", name=author_name), author=author_info,
page="author") other_books=other_books, page="author")
def render_publisher_books(page, book_id, order): def render_publisher_books(page, book_id, order):
@ -574,19 +592,19 @@ def render_series_books(page, book_id, order):
if name: if name:
entries, random, pagination = fill_indexpage(page, db.Books, db.Books.series.any(db.Series.id == book_id), entries, random, pagination = fill_indexpage(page, db.Books, db.Books.series.any(db.Series.id == book_id),
[db.Books.series_index, order[0]]) [db.Books.series_index, order[0]])
return render_title_template('index.html', random=random, pagination=pagination, entries=entries, 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:
abort(404) abort(404)
def render_ratings_books(page, book_id, order): def render_ratings_books(page, book_id, order):
if book_id <=5: name = db.session.query(db.Ratings).filter(db.Ratings.id == book_id).first()
name = 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 = fill_indexpage(page, db.Books, db.Books.ratings.any(db.Ratings.id == book_id), [db.Books.timestamp.desc(), order[0]])
[db.Books.timestamp.desc(), order[0]]) if name and name.rating <= 10:
return render_title_template('index.html', random=random, pagination=pagination, entries=entries, 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")
else: else:
abort(404) abort(404)
@ -596,7 +614,7 @@ def render_formats_books(page, book_id, order):
if name: if name:
entries, random, pagination = fill_indexpage(page, db.Books, db.Books.data.any(db.Data.format == book_id.upper()), entries, random, pagination = fill_indexpage(page, db.Books, db.Books.data.any(db.Data.format == book_id.upper()),
[db.Books.timestamp.desc(), order[0]]) [db.Books.timestamp.desc(), order[0]])
return render_title_template('index.html', random=random, pagination=pagination, entries=entries, 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:
abort(404) abort(404)
@ -608,12 +626,27 @@ def render_category_books(page, book_id, order):
entries, random, pagination = fill_indexpage(page, db.Books, db.Books.tags.any(db.Tags.id == book_id), entries, random, pagination = fill_indexpage(page, db.Books, db.Books.tags.any(db.Tags.id == book_id),
[db.Series.name, db.Books.series_index, order[0]], [db.Series.name, db.Books.series_index, order[0]],
db.books_series_link, db.Series) db.books_series_link, db.Series)
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"Category: %(name)s", name=name.name), page="category") title=_(u"Category: %(name)s", name=name.name), page="category")
else: else:
abort(404) abort(404)
def render_language_books(page, name, order):
try:
cur_l = LC.parse(name)
lang_name = cur_l.get_language_name(get_locale())
except UnknownLocaleError:
try:
lang_name = _(isoLanguages.get(part3=name).name)
except KeyError:
abort(404)
entries, random, pagination = fill_indexpage(page, 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,
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():
@ -714,29 +747,12 @@ def language_overview():
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,
charlist=charlist, title=_(u"Available languages"), page="langlist", data="language") charlist=charlist, title=_(u"Available languages"), page="langlist",
data="language")
else: else:
abort(404) abort(404)
@web.route("/language/<name>", defaults={'page': 1})
@web.route('/language/<name>/page/<int:page>')
@login_required_if_no_ano
def language(name, page):
try:
cur_l = LC.parse(name)
lang_name = cur_l.get_language_name(get_locale())
except UnknownLocaleError:
try:
lang_name = _(isoLanguages.get(part3=name).name)
except KeyError:
abort(404)
entries, random, pagination = fill_indexpage(page, db.Books, db.Books.languages.any(db.Languages.lang_code == name),
[db.Books.timestamp.desc()])
return render_title_template('index.html', random=random, entries=entries, pagination=pagination,
title=_(u"Language: %(name)s", name=lang_name), page="language")
@web.route("/category") @web.route("/category")
@login_required_if_no_ano @login_required_if_no_ano
def category_list(): def category_list():

@ -187,25 +187,28 @@ class WorkerThread(threading.Thread):
self.UIqueue = list() self.UIqueue = list()
self.asyncSMTP = None self.asyncSMTP = None
self.id = 0 self.id = 0
self.doLock = threading.Lock()
# Main thread loop starting the different tasks # Main thread loop starting the different tasks
def run(self): def run(self):
main_thread = _get_main_thread() main_thread = _get_main_thread()
while main_thread.is_alive(): while main_thread.is_alive():
doLock = threading.Lock() self.doLock.acquire()
doLock.acquire()
if self.current != self.last: if self.current != self.last:
doLock.release() index = self.current
if self.queue[self.current]['taskType'] == TASK_EMAIL: self.doLock.release()
if self.queue[index]['taskType'] == TASK_EMAIL:
self._send_raw_email() self._send_raw_email()
if self.queue[self.current]['taskType'] == TASK_CONVERT: if self.queue[index]['taskType'] == TASK_CONVERT:
self._convert_any_format() self._convert_any_format()
if self.queue[self.current]['taskType'] == TASK_CONVERT_ANY: if self.queue[index]['taskType'] == TASK_CONVERT_ANY:
self._convert_any_format() self._convert_any_format()
# TASK_UPLOAD is handled implicitly # TASK_UPLOAD is handled implicitly
self.doLock.acquire()
self.current += 1 self.current += 1
self.doLock.release()
else: else:
doLock.release() self.doLock.release()
if main_thread.is_alive(): if main_thread.is_alive():
time.sleep(1) time.sleep(1)
@ -226,6 +229,7 @@ class WorkerThread(threading.Thread):
self.last = len(self.queue) self.last = len(self.queue)
def get_taskstatus(self): def get_taskstatus(self):
self.doLock.acquire()
if self.current < len(self.queue): if self.current < len(self.queue):
if self.UIqueue[self.current]['stat'] == STAT_STARTED: if self.UIqueue[self.current]['stat'] == STAT_STARTED:
if self.queue[self.current]['taskType'] == TASK_EMAIL: if self.queue[self.current]['taskType'] == TASK_EMAIL:
@ -234,30 +238,37 @@ class WorkerThread(threading.Thread):
self.UIqueue[self.current]['rt'] = self.UIqueue[self.current]['formRuntime'].days*24*60 \ self.UIqueue[self.current]['rt'] = self.UIqueue[self.current]['formRuntime'].days*24*60 \
+ self.UIqueue[self.current]['formRuntime'].seconds \ + self.UIqueue[self.current]['formRuntime'].seconds \
+ self.UIqueue[self.current]['formRuntime'].microseconds + self.UIqueue[self.current]['formRuntime'].microseconds
self.doLock.release()
return self.UIqueue return self.UIqueue
def _convert_any_format(self): def _convert_any_format(self):
# convert book, and upload in case of google drive # convert book, and upload in case of google drive
self.UIqueue[self.current]['stat'] = STAT_STARTED self.doLock.acquire()
self.queue[self.current]['starttime'] = datetime.now() index = self.current
self.UIqueue[self.current]['formStarttime'] = self.queue[self.current]['starttime'] self.doLock.release()
curr_task = self.queue[self.current]['taskType'] self.UIqueue[index]['stat'] = STAT_STARTED
self.queue[index]['starttime'] = datetime.now()
self.UIqueue[index]['formStarttime'] = self.queue[self.current]['starttime']
curr_task = self.queue[index]['taskType']
filename = self._convert_ebook_format() filename = self._convert_ebook_format()
if filename: if filename:
if config.config_use_google_drive: if config.config_use_google_drive:
gdriveutils.updateGdriveCalibreFromLocal() gdriveutils.updateGdriveCalibreFromLocal()
if curr_task == TASK_CONVERT: if curr_task == TASK_CONVERT:
self.add_email(self.queue[self.current]['settings']['subject'], self.queue[self.current]['path'], self.add_email(self.queue[index]['settings']['subject'], self.queue[index]['path'],
filename, self.queue[self.current]['settings'], self.queue[self.current]['kindle'], filename, self.queue[index]['settings'], self.queue[index]['kindle'],
self.UIqueue[self.current]['user'], self.queue[self.current]['title'], self.UIqueue[index]['user'], self.queue[index]['title'],
self.queue[self.current]['settings']['body']) self.queue[index]['settings']['body'])
def _convert_ebook_format(self): def _convert_ebook_format(self):
error_message = None error_message = None
file_path = self.queue[self.current]['file_path'] self.doLock.acquire()
bookid = self.queue[self.current]['bookid'] index = self.current
format_old_ext = u'.' + self.queue[self.current]['settings']['old_book_format'].lower() self.doLock.release()
format_new_ext = u'.' + self.queue[self.current]['settings']['new_book_format'].lower() file_path = self.queue[index]['file_path']
bookid = self.queue[index]['bookid']
format_old_ext = u'.' + self.queue[index]['settings']['old_book_format'].lower()
format_new_ext = u'.' + self.queue[index]['settings']['new_book_format'].lower()
# check to see if destination format already exists - # check to see if destination format already exists -
# 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
@ -265,8 +276,8 @@ class WorkerThread(threading.Thread):
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", bookid, format_new_ext)
cur_book = db.session.query(db.Books).filter(db.Books.id == bookid).first() cur_book = db.session.query(db.Books).filter(db.Books.id == bookid).first()
self.queue[self.current]['path'] = file_path self.queue[index]['path'] = file_path
self.queue[self.current]['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:
@ -304,13 +315,13 @@ class WorkerThread(threading.Thread):
else:''' else:'''
command = [config.config_converterpath, (file_path + format_old_ext), command = [config.config_converterpath, (file_path + format_old_ext),
(file_path + format_new_ext)] (file_path + format_new_ext)]
index = 3 quotes_index = 3
if config.config_calibre: if config.config_calibre:
parameters = config.config_calibre.split(" ") parameters = config.config_calibre.split(" ")
for param in parameters: for param in parameters:
command.append(param) command.append(param)
quotes.append(index) quotes.append(quotes_index)
index += 1 quotes_index += 1
p = process_open(command, quotes) p = process_open(command, quotes)
# p = subprocess.Popen(command, stdout=subprocess.PIPE, universal_newlines=True) # p = subprocess.Popen(command, stdout=subprocess.PIPE, universal_newlines=True)
except OSError as e: except OSError as e:
@ -338,7 +349,7 @@ class WorkerThread(threading.Thread):
# parse progress string from calibre-converter # parse progress string from calibre-converter
progress = re.search(r"(\d+)%\s.*", nextline) progress = re.search(r"(\d+)%\s.*", nextline)
if progress: if progress:
self.UIqueue[self.current]['progress'] = progress.group(1) + ' %' self.UIqueue[index]['progress'] = progress.group(1) + ' %'
# process returncode # process returncode
check = p.returncode check = p.returncode
@ -359,12 +370,12 @@ class WorkerThread(threading.Thread):
cur_book = db.session.query(db.Books).filter(db.Books.id == bookid).first() 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):
new_format = db.Data(name=cur_book.data[0].name, new_format = db.Data(name=cur_book.data[0].name,
book_format=self.queue[self.current]['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=bookid, uncompressed_size=os.path.getsize(file_path + format_new_ext))
cur_book.data.append(new_format) cur_book.data.append(new_format)
db.session.commit() db.session.commit()
self.queue[self.current]['path'] = cur_book.path self.queue[index]['path'] = cur_book.path
self.queue[self.current]['title'] = cur_book.title self.queue[index]['title'] = cur_book.title
if config.config_use_google_drive: if config.config_use_google_drive:
os.remove(file_path + format_old_ext) os.remove(file_path + format_old_ext)
self._handleSuccess() self._handleSuccess()
@ -430,16 +441,19 @@ class WorkerThread(threading.Thread):
def _send_raw_email(self): def _send_raw_email(self):
self.queue[self.current]['starttime'] = datetime.now() self.doLock.acquire()
self.UIqueue[self.current]['formStarttime'] = self.queue[self.current]['starttime'] index = self.current
self.UIqueue[self.current]['stat'] = STAT_STARTED self.doLock.release()
obj=self.queue[self.current] self.queue[index]['starttime'] = datetime.now()
self.UIqueue[index]['formStarttime'] = self.queue[index]['starttime']
self.UIqueue[index]['stat'] = STAT_STARTED
obj=self.queue[index]
# create MIME message # create MIME message
msg = MIMEMultipart() msg = MIMEMultipart()
msg['Subject'] = self.queue[self.current]['subject'] msg['Subject'] = self.queue[index]['subject']
msg['Message-Id'] = make_msgid('calibre-web') msg['Message-Id'] = make_msgid('calibre-web')
msg['Date'] = formatdate(localtime=True) msg['Date'] = formatdate(localtime=True)
text = self.queue[self.current]['text'] text = self.queue[index]['text']
msg.attach(MIMEText(text.encode('UTF-8'), 'plain', 'UTF-8')) msg.attach(MIMEText(text.encode('UTF-8'), 'plain', 'UTF-8'))
if obj['attachment']: if obj['attachment']:
result = get_attachment(obj['filepath'], obj['attachment']) result = get_attachment(obj['filepath'], obj['attachment'])
@ -506,15 +520,21 @@ class WorkerThread(threading.Thread):
def _handleError(self, error_message): def _handleError(self, error_message):
log.error(error_message) log.error(error_message)
self.UIqueue[self.current]['stat'] = STAT_FAIL self.doLock.acquire()
self.UIqueue[self.current]['progress'] = "100 %" index = self.current
self.UIqueue[self.current]['formRuntime'] = datetime.now() - self.queue[self.current]['starttime'] self.doLock.release()
self.UIqueue[self.current]['message'] = error_message self.UIqueue[index]['stat'] = STAT_FAIL
self.UIqueue[index]['progress'] = "100 %"
self.UIqueue[index]['formRuntime'] = datetime.now() - self.queue[self.current]['starttime']
self.UIqueue[index]['message'] = error_message
def _handleSuccess(self): def _handleSuccess(self):
self.UIqueue[self.current]['stat'] = STAT_FINISH_SUCCESS self.doLock.acquire()
self.UIqueue[self.current]['progress'] = "100 %" index = self.current
self.UIqueue[self.current]['formRuntime'] = datetime.now() - self.queue[self.current]['starttime'] self.doLock.release()
self.UIqueue[index]['stat'] = STAT_FINISH_SUCCESS
self.UIqueue[index]['progress'] = "100 %"
self.UIqueue[index]['formRuntime'] = datetime.now() - self.queue[self.current]['starttime']
_worker = WorkerThread() _worker = WorkerThread()

@ -18,7 +18,11 @@ python-Levenshtein>=0.12.0
# ldap login # ldap login
python_ldap>=3.0.0 python_ldap>=3.0.0
flask-simpleldap flask-simpleldap>1.3.0
#oauth
flask-dance>=0.13.0
sqlalchemy_utils>=0.33.5
# extracting metadata # extracting metadata
lxml>=3.8.0 lxml>=3.8.0
@ -27,7 +31,4 @@ rarfile>=2.7
# other # other
natsort>=2.2.0 natsort>=2.2.0
git+https://github.com/OzzieIsaacs/comicapi.git@5346716578b2843f54d522f44d01bc8d25001d24#egg=comicapi
# Oauth Login
flask-dance>=0.13.0
sqlalchemy_utils>=0.33.5

@ -13,4 +13,3 @@ SQLAlchemy>=1.1.0
tornado>=4.1 tornado>=4.1
Wand>=0.4.4 Wand>=0.4.4
unidecode>=0.04.19 unidecode>=0.04.19
git+https://github.com/wildthyme/comicapi.git@cb279168f9c5cec742b5a05ac8326b9c168a8a91#egg=comicapi

@ -31,14 +31,14 @@ keywords =
calibre calibre
calibre-web calibre-web
library library
python_requires = >=2.6
[options.entry_points] [options.entry_points]
console_scripts = console_scripts =
cps = calibreweb:main cps = calibreweb:main
[options] [options]
python_requires = >=2.6
include_package_data = True include_package_data = True
zip_safe = False dependency_links = comicapi @ git+https://github.com/OzzieIsaacs/comicapi.git@5346716578b2843f54d522f44d01bc8d25001d24#egg=comicapi
install_requires = install_requires =
Babel >= 1.3 Babel >= 1.3
Flask-Babel >= 0.11.1 Flask-Babel >= 0.11.1
@ -80,7 +80,8 @@ metadata =
Wand >= 0.4.4 Wand >= 0.4.4
comics= comics=
natsort>=2.2.0 natsort>=2.2.0
# https://github.com/wildthyme/comicapi/archive/cb279168f9c5cec742b5a05ac8326b9c168a8a91.zip#egg=comicapi # find solution for this should belong to comics
# comicapi @ git+https://github.com/wildthyme/comicapi.git@cb279168f9c5cec742b5a05ac8326b9c168a8a91#egg=comicapi # comicapi @ git+https://github.com/OzzieIsaacs/comicapi/archive/5346716578b2843f54d522f44d01bc8d25001d24.zip#egg=comicapi
# find solution for this

@ -20,6 +20,7 @@
# """Calibre-web distribution package setuptools installer.""" # """Calibre-web distribution package setuptools installer."""
from setuptools import setup from setuptools import setup
from setuptools import find_packages
import os import os
import re import re
import codecs import codecs
@ -39,6 +40,7 @@ def find_version(*file_paths):
raise RuntimeError("Unable to find version string.") raise RuntimeError("Unable to find version string.")
setup( setup(
packages=find_packages("src"),
package_dir = {'': 'src'}, package_dir = {'': 'src'},
version=find_version("src", "calibreweb", "cps", "constants.py") version=find_version("src", "calibreweb", "cps", "constants.py")
) )

Loading…
Cancel
Save