diff --git a/cps.py b/cps.py
index 184bee0a..f71c60c4 100755
--- a/cps.py
+++ b/cps.py
@@ -17,14 +17,14 @@
# You should have received a copy of the GNU General Public License
# along with this program. If not, see .
-import os
+from __future__ import absolute_import, division, print_function, unicode_literals
import sys
+import os
+
-base_path = os.path.dirname(os.path.abspath(__file__))
# Insert local directories into path
-sys.path.append(base_path)
-sys.path.append(os.path.join(base_path, 'cps'))
-sys.path.append(os.path.join(base_path, 'vendor'))
+sys.path.append(os.path.join(sys.path[0], 'vendor'))
+
from cps import create_app
from cps.opds import opds
diff --git a/cps/__init__.py b/cps/__init__.py
index dfe72e5c..3b35570b 100755
--- a/cps/__init__.py
+++ b/cps/__init__.py
@@ -20,29 +20,28 @@
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see .
-__all__ =['app']
-import mimetypes
-from flask import Flask, request, g
-from flask_login import LoginManager
-from flask_babel import Babel
-import cache_buster
-from reverseproxy import ReverseProxied
-import logging
-from logging.handlers import RotatingFileHandler
-from flask_principal import Principal
-from babel.core import UnknownLocaleError
-from babel import Locale as LC
-from babel import negotiate_locale
-import os
-import ub
+from __future__ import division, print_function, unicode_literals
import sys
-from ub import Config, Settings
+import os
+import mimetypes
try:
import cPickle
except ImportError:
import pickle as cPickle
+from babel import Locale as LC
+from babel import negotiate_locale
+from babel.core import UnknownLocaleError
+from flask import Flask, request, g
+from flask_login import LoginManager
+from flask_babel import Babel
+from flask_principal import Principal
+
+from . import logger, cache_buster, ub
+from .constants import TRANSLATIONS_DIR as _TRANSLATIONS_DIR
+from .reverseproxy import ReverseProxied
+
mimetypes.init()
mimetypes.add_type('application/xhtml+xml', '.xhtml')
@@ -70,12 +69,11 @@ lm.anonymous_user = ub.Anonymous
ub.init_db()
-config = Config()
-
+config = ub.Config()
from . import db
try:
- with open(os.path.join(config.get_main_dir, 'cps/translations/iso639.pickle'), 'rb') as f:
+ with open(os.path.join(_TRANSLATIONS_DIR, 'iso639.pickle'), 'rb') as f:
language_table = cPickle.load(f)
except cPickle.UnpicklingError as error:
# app.logger.error("Can't read file cps/translations/iso639.pickle: %s", error)
@@ -91,24 +89,14 @@ from .server import server
Server = server()
babel = Babel()
+log = logger.create()
+
def create_app():
app.wsgi_app = ReverseProxied(app.wsgi_app)
cache_buster.init_cache_busting(app)
- formatter = logging.Formatter(
- "[%(asctime)s] {%(pathname)s:%(lineno)d} %(levelname)s - %(message)s")
- try:
- file_handler = RotatingFileHandler(config.get_config_logfile(), maxBytes=50000, backupCount=2)
- except IOError:
- file_handler = RotatingFileHandler(os.path.join(config.get_main_dir, "calibre-web.log"),
- maxBytes=50000, backupCount=2)
- # ToDo: reset logfile value in config class
- file_handler.setFormatter(formatter)
- app.logger.addHandler(file_handler)
- app.logger.setLevel(config.config_log_level)
-
- app.logger.info('Starting Calibre Web...')
+ log.info('Starting Calibre Web...')
Principal(app)
lm.init_app(app)
app.secret_key = os.getenv('SECRET_KEY', 'A0Zr98j/3yX R~XHH!jmN]LWX/,?RT')
@@ -132,7 +120,7 @@ def get_locale():
try:
preferred.append(str(LC.parse(x.replace('-', '_'))))
except (UnknownLocaleError, ValueError) as e:
- app.logger.debug("Could not parse locale: %s", e)
+ log.warning('Could not parse locale "%s": %s', x, e)
preferred.append('en')
return negotiate_locale(preferred, translations)
@@ -145,3 +133,6 @@ def get_timezone():
from .updater import Updater
updater_thread = Updater()
+
+
+__all__ = ['app']
diff --git a/cps/about.py b/cps/about.py
index 59623f16..bc7b0e8a 100644
--- a/cps/about.py
+++ b/cps/about.py
@@ -21,29 +21,30 @@
# You should have received a copy of the GNU General Public License
# along with this program. If not, see .
-from flask import Blueprint
-from flask_login import login_required
-from . import db
+from __future__ import division, print_function, unicode_literals
import sys
-from .uploader import get_versions
-from babel import __version__ as babelVersion
-from sqlalchemy import __version__ as sqlalchemyVersion
-from flask_principal import __version__ as flask_principalVersion
-from iso639 import __version__ as iso639Version
-from pytz import __version__ as pytzVersion
-from flask import __version__ as flaskVersion
-from werkzeug import __version__ as werkzeugVersion
-from jinja2 import __version__ as jinja2Version
-from .converter import versioncheck
-from flask_babel import gettext as _
-from cps import Server
import requests
-from .web import render_title_template
+from flask import Blueprint
+from flask import __version__ as flaskVersion
+from flask_babel import gettext as _
+from flask_principal import __version__ as flask_principalVersion
+from flask_login import login_required
try:
from flask_login import __version__ as flask_loginVersion
except ImportError:
from flask_login.__about__ import __version__ as flask_loginVersion
+from werkzeug import __version__ as werkzeugVersion
+
+from babel import __version__ as babelVersion
+from jinja2 import __version__ as jinja2Version
+from pytz import __version__ as pytzVersion
+from sqlalchemy import __version__ as sqlalchemyVersion
+
+from . import db, converter, Server, uploader
+from .isoLanguages import __version__ as iso639Version
+from .web import render_title_template
+
about = Blueprint('about', __name__)
@@ -55,7 +56,7 @@ def stats():
authors = db.session.query(db.Authors).count()
categorys = db.session.query(db.Tags).count()
series = db.session.query(db.Series).count()
- versions = get_versions()
+ versions = uploader.get_versions()
versions['Babel'] = 'v' + babelVersion
versions['Sqlalchemy'] = 'v' + sqlalchemyVersion
versions['Werkzeug'] = 'v' + werkzeugVersion
@@ -69,7 +70,7 @@ def stats():
versions['Requests'] = 'v' + requests.__version__
versions['pySqlite'] = 'v' + db.engine.dialect.dbapi.version
versions['Sqlite'] = 'v' + db.engine.dialect.dbapi.sqlite_version
- versions.update(versioncheck())
+ versions.update(converter.versioncheck())
versions.update(Server.getNameVersion())
versions['Python'] = sys.version
return render_title_template('stats.html', bookcounter=counter, authorcounter=authors, versions=versions,
diff --git a/cps/admin.py b/cps/admin.py
index 305dd9e3..85088336 100644
--- a/cps/admin.py
+++ b/cps/admin.py
@@ -21,29 +21,32 @@
# You should have received a copy of the GNU General Public License
# along with this program. If not, see .
+from __future__ import division, print_function, unicode_literals
import os
-from flask import Blueprint, flash, redirect, url_for
-from flask import abort, request, make_response
-from flask_login import login_required, current_user, logout_user
-from .web import admin_required, render_title_template, before_request, unconfigured, \
- login_required_if_no_ano
-from . import db, ub, Server, get_locale, config, app, updater_thread, babel
import json
-from datetime import datetime, timedelta
import time
-from babel.dates import format_datetime
-from flask_babel import gettext as _
-from babel import Locale as LC
-from sqlalchemy.exc import IntegrityError
-from .gdriveutils import is_gdrive_ready, gdrive_support, downloadFile, deleteDatabaseOnChange, listRootFolders
-from .helper import speaking_language, check_valid_domain, check_unrar, send_test_mail, generate_random_password, \
- send_registration_mail
-from werkzeug.security import generate_password_hash
+from datetime import datetime, timedelta
try:
from imp import reload
except ImportError:
pass
+from babel import Locale as LC
+from babel.dates import format_datetime
+from flask import Blueprint, flash, redirect, url_for, abort, request, make_response
+from flask_login import login_required, current_user, logout_user
+from flask_babel import gettext as _
+from sqlalchemy import and_
+from sqlalchemy.exc import IntegrityError
+from werkzeug.security import generate_password_hash
+
+from . import constants, logger
+from . import db, ub, Server, get_locale, config, updater_thread, babel, gdriveutils
+from .helper import speaking_language, check_valid_domain, check_unrar, send_test_mail, generate_random_password, \
+ send_registration_mail
+from .gdriveutils import is_gdrive_ready, gdrive_support, downloadFile, deleteDatabaseOnChange, listRootFolders
+from .web import admin_required, render_title_template, before_request, unconfigured, login_required_if_no_ano
+
feature_support = dict()
try:
from goodreads.client import GoodreadsClient
@@ -51,11 +54,11 @@ try:
except ImportError:
feature_support['goodreads'] = False
-try:
- import rarfile
- feature_support['rar'] = True
-except ImportError:
- feature_support['rar'] = False
+# try:
+# import rarfile
+# feature_support['rar'] = True
+# except ImportError:
+# feature_support['rar'] = False
try:
import ldap
@@ -70,8 +73,10 @@ except ImportError:
feature_support['oauth'] = False
oauth_check = {}
+
feature_support['gdrive'] = gdrive_support
admi = Blueprint('admin', __name__)
+log = logger.create()
@admi.route("/admin")
@@ -174,7 +179,7 @@ def view_configuration():
if "config_mature_content_tags" in to_save:
content.config_mature_content_tags = to_save["config_mature_content_tags"].strip()
if "Show_mature_content" in to_save:
- content.config_default_show = content.config_default_show + ub.MATURE_CONTENT
+ content.config_default_show |= constants.MATURE_CONTENT
if "config_authors_max" in to_save:
content.config_authors_max = int(to_save["config_authors_max"])
@@ -182,26 +187,26 @@ def view_configuration():
# Default user configuration
content.config_default_role = 0
if "admin_role" in to_save:
- content.config_default_role = content.config_default_role + ub.ROLE_ADMIN
+ content.config_default_role |= constants.ROLE_ADMIN
if "download_role" in to_save:
- content.config_default_role = content.config_default_role + ub.ROLE_DOWNLOAD
+ content.config_default_role |= constants.ROLE_DOWNLOAD
if "viewer_role" in to_save:
- content.config_default_role = content.config_default_role + ub.ROLE_VIEWER
+ content.config_default_role |= constants.ROLE_VIEWER
if "upload_role" in to_save:
- content.config_default_role = content.config_default_role + ub.ROLE_UPLOAD
+ content.config_default_role |= constants.ROLE_UPLOAD
if "edit_role" in to_save:
- content.config_default_role = content.config_default_role + ub.ROLE_EDIT
+ content.config_default_role |= constants.ROLE_EDIT
if "delete_role" in to_save:
- content.config_default_role = content.config_default_role + ub.ROLE_DELETE_BOOKS
+ content.config_default_role |= constants.ROLE_DELETE_BOOKS
if "passwd_role" in to_save:
- content.config_default_role = content.config_default_role + ub.ROLE_PASSWD
+ content.config_default_role |= constants.ROLE_PASSWD
if "edit_shelf_role" in to_save:
- content.config_default_role = content.config_default_role + ub.ROLE_EDIT_SHELFS
+ content.config_default_role |= constants.ROLE_EDIT_SHELFS
val = 0
for key,v in to_save.items():
if key.startswith('show'):
- val += int(key[5:])
+ val |= int(key[5:])
content.config_default_show = val
ub.session.commit()
@@ -215,9 +220,9 @@ def view_configuration():
# stop Server
Server.setRestartTyp(True)
Server.stopServer()
- app.logger.info('Reboot required, restarting')
+ log.info('Reboot required, restarting')
readColumn = db.session.query(db.Custom_Columns)\
- .filter(db.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()
return render_title_template("config_view_edit.html", conf=config, readColumns=readColumn,
title=_(u"UI Configuration"), page="uiconfig")
@@ -294,10 +299,10 @@ def configuration_helper(origin):
if not feature_support['gdrive']:
gdriveError = _('Import of optional Google Drive requirements missing')
else:
- if not os.path.isfile(os.path.join(config.get_main_dir, 'client_secrets.json')):
+ if not os.path.isfile(gdriveutils.CLIENT_SECRETS):
gdriveError = _('client_secrets.json is missing or not readable')
else:
- with open(os.path.join(config.get_main_dir, 'client_secrets.json'), 'r') as settings:
+ with open(gdriveutils.CLIENT_SECRETS, 'r') as settings:
filedata = json.load(settings)
if 'web' not in filedata:
gdriveError = _('client_secrets.json is not configured for web application')
@@ -309,13 +314,13 @@ def configuration_helper(origin):
content.config_calibre_dir = to_save["config_calibre_dir"]
db_change = True
# Google drive setup
- if not os.path.isfile(os.path.join(config.get_main_dir, 'settings.yaml')):
+ if not os.path.isfile(gdriveutils.SETTINGS_YAML):
content.config_use_google_drive = False
if "config_use_google_drive" in to_save and not content.config_use_google_drive and not gdriveError:
if filedata:
if filedata['web']['redirect_uris'][0].endswith('/'):
filedata['web']['redirect_uris'][0] = filedata['web']['redirect_uris'][0][:-1]
- with open(os.path.join(config.get_main_dir, 'settings.yaml'), 'w') as f:
+ with open(gdriveutils.SETTINGS_YAML, 'w') as f:
yaml = "client_config_backend: settings\nclient_config_file: %(client_file)s\n" \
"client_config:\n" \
" client_id: %(client_id)s\n client_secret: %(client_secret)s\n" \
@@ -323,11 +328,11 @@ def configuration_helper(origin):
"save_credentials_backend: file\nsave_credentials_file: %(credential)s\n\n" \
"get_refresh_token: True\n\noauth_scope:\n" \
" - https://www.googleapis.com/auth/drive\n"
- f.write(yaml % {'client_file': os.path.join(config.get_main_dir, 'client_secrets.json'),
+ f.write(yaml % {'client_file': gdriveutils.CLIENT_SECRETS,
'client_id': filedata['web']['client_id'],
'client_secret': filedata['web']['client_secret'],
'redirect_uri': filedata['web']['redirect_uris'][0],
- 'credential': os.path.join(config.get_main_dir, 'gdrive_credentials')})
+ 'credential': gdriveutils.CREDENTIALS})
else:
flash(_(u'client_secrets.json is not configured for web application'), category="error")
return render_title_template("config_edit.html", config=config, origin=origin,
@@ -397,7 +402,7 @@ def configuration_helper(origin):
gdriveError=gdriveError, feature_support=feature_support,
title=_(u"Basic Configuration"), page="config")
else:
- content.config_login_type = ub.LOGIN_LDAP
+ content.config_login_type = constants.LOGIN_LDAP
content.config_ldap_provider_url = to_save["config_ldap_provider_url"]
content.config_ldap_dn = to_save["config_ldap_dn"]
db_change = True
@@ -425,7 +430,7 @@ def configuration_helper(origin):
gdriveError=gdriveError, feature_support=feature_support,
title=_(u"Basic Configuration"), page="config")
else:
- content.config_login_type = ub.LOGIN_OAUTH_GITHUB
+ content.config_login_type = constants.LOGIN_OAUTH_GITHUB
content.config_github_oauth_client_id = to_save["config_github_oauth_client_id"]
content.config_github_oauth_client_secret = to_save["config_github_oauth_client_secret"]
reboot_required = True
@@ -439,31 +444,25 @@ def configuration_helper(origin):
gdriveError=gdriveError, feature_support=feature_support,
title=_(u"Basic Configuration"), page="config")
else:
- content.config_login_type = ub.LOGIN_OAUTH_GOOGLE
+ content.config_login_type = constants.LOGIN_OAUTH_GOOGLE
content.config_google_oauth_client_id = to_save["config_google_oauth_client_id"]
content.config_google_oauth_client_secret = to_save["config_google_oauth_client_secret"]
reboot_required = True
if "config_login_type" in to_save and to_save["config_login_type"] == "0":
- content.config_login_type = ub.LOGIN_STANDARD
+ content.config_login_type = constants.LOGIN_STANDARD
if "config_log_level" in to_save:
content.config_log_level = int(to_save["config_log_level"])
if content.config_logfile != to_save["config_logfile"]:
# check valid path, only path or file
- if os.path.dirname(to_save["config_logfile"]):
- if os.path.exists(os.path.dirname(to_save["config_logfile"])) and \
- os.path.basename(to_save["config_logfile"]) and not os.path.isdir(to_save["config_logfile"]):
- content.config_logfile = to_save["config_logfile"]
- else:
+ if not logger.is_valid_logfile(to_save["config_logfile"]):
ub.session.commit()
flash(_(u'Logfile location is not valid, please enter correct path'), category="error")
return render_title_template("config_edit.html", config=config, origin=origin,
gdriveError=gdriveError, feature_support=feature_support,
title=_(u"Basic Configuration"), page="config")
- else:
- content.config_logfile = to_save["config_logfile"]
- reboot_required = True
+ content.config_logfile = to_save["config_logfile"]
# Rarfile Content configuration
if "config_rarfile_location" in to_save and to_save['config_rarfile_location'] is not u"":
@@ -485,7 +484,6 @@ def configuration_helper(origin):
ub.session.commit()
flash(_(u"Calibre-Web configuration updated"), category="success")
config.loadSettings()
- app.logger.setLevel(config.config_log_level)
except Exception as e:
flash(e, category="error")
return render_title_template("config_edit.html", config=config, origin=origin,
@@ -502,7 +500,7 @@ def configuration_helper(origin):
# stop Server
Server.setRestartTyp(True)
Server.stopServer()
- app.logger.info('Reboot required, restarting')
+ log.info('Reboot required, restarting')
if origin:
success = True
if is_gdrive_ready() and feature_support['gdrive'] is True: # and config.config_use_google_drive == True:
@@ -536,23 +534,23 @@ def new_user():
content.sidebar_view = val
if "show_detail_random" in to_save:
- content.sidebar_view += ub.DETAIL_RANDOM
+ content.sidebar_view |= constants.DETAIL_RANDOM
content.role = 0
if "admin_role" in to_save:
- content.role = content.role + ub.ROLE_ADMIN
+ content.role |= constants.ROLE_ADMIN
if "download_role" in to_save:
- content.role = content.role + ub.ROLE_DOWNLOAD
+ content.role |= constants.ROLE_DOWNLOAD
if "upload_role" in to_save:
- content.role = content.role + ub.ROLE_UPLOAD
+ content.role |= constants.ROLE_UPLOAD
if "edit_role" in to_save:
- content.role = content.role + ub.ROLE_EDIT
+ content.role |= constants.ROLE_EDIT
if "delete_role" in to_save:
- content.role = content.role + ub.ROLE_DELETE_BOOKS
+ content.role |= constants.ROLE_DELETE_BOOKS
if "passwd_role" in to_save:
- content.role = content.role + ub.ROLE_PASSWD
+ content.role |= constants.ROLE_PASSWD
if "edit_shelf_role" in to_save:
- content.role = content.role + ub.ROLE_EDIT_SHELFS
+ content.role |= constants.ROLE_EDIT_SHELFS
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,
@@ -576,7 +574,7 @@ def new_user():
else:
content.role = config.config_default_role
content.sidebar_view = config.config_default_show
- content.mature_content = bool(config.config_default_show & ub.MATURE_CONTENT)
+ content.mature_content = bool(config.config_default_show & constants.MATURE_CONTENT)
return render_title_template("user_edit.html", new_user=1, content=content, translations=translations,
languages=languages, title=_(u"Add new user"), page="newuser",
registered_oauth=oauth_check)
@@ -642,58 +640,58 @@ def edit_user(user_id):
if "password" in to_save and to_save["password"]:
content.password = generate_password_hash(to_save["password"])
- if "admin_role" in to_save and not content.role_admin():
- content.role = content.role + ub.ROLE_ADMIN
- elif "admin_role" not in to_save and content.role_admin():
- content.role = content.role - ub.ROLE_ADMIN
+ if "admin_role" in to_save:
+ content.role |= constants.ROLE_ADMIN
+ else:
+ content.role &= ~constants.ROLE_ADMIN
- if "download_role" in to_save and not content.role_download():
- content.role = content.role + ub.ROLE_DOWNLOAD
- elif "download_role" not in to_save and content.role_download():
- content.role = content.role - ub.ROLE_DOWNLOAD
+ if "download_role" in to_save:
+ content.role |= constants.ROLE_DOWNLOAD
+ else:
+ content.role &= ~constants.ROLE_DOWNLOAD
- if "viewer_role" in to_save and not content.role_viewer():
- content.role = content.role + ub.ROLE_VIEWER
- elif "viewer_role" not in to_save and content.role_viewer():
- content.role = content.role - ub.ROLE_VIEWER
+ if "viewer_role" in to_save:
+ content.role |= constants.ROLE_VIEWER
+ else:
+ content.role &= ~constants.ROLE_VIEWER
- if "upload_role" in to_save and not content.role_upload():
- content.role = content.role + ub.ROLE_UPLOAD
- elif "upload_role" not in to_save and content.role_upload():
- content.role = content.role - ub.ROLE_UPLOAD
+ if "upload_role" in to_save:
+ content.role |= constants.ROLE_UPLOAD
+ else:
+ content.role &= ~constants.ROLE_UPLOAD
- if "edit_role" in to_save and not content.role_edit():
- content.role = content.role + ub.ROLE_EDIT
- elif "edit_role" not in to_save and content.role_edit():
- content.role = content.role - ub.ROLE_EDIT
+ if "edit_role" in to_save:
+ content.role |= constants.ROLE_EDIT
+ else:
+ content.role &= ~constants.ROLE_EDIT
- if "delete_role" in to_save and not content.role_delete_books():
- content.role = content.role + ub.ROLE_DELETE_BOOKS
- elif "delete_role" not in to_save and content.role_delete_books():
- content.role = content.role - ub.ROLE_DELETE_BOOKS
+ if "delete_role" in to_save:
+ content.role |= constants.ROLE_DELETE_BOOKS
+ else:
+ content.role &= ~constants.ROLE_DELETE_BOOKS
- if "passwd_role" in to_save and not content.role_passwd():
- content.role = content.role + ub.ROLE_PASSWD
- elif "passwd_role" not in to_save and content.role_passwd():
- content.role = content.role - ub.ROLE_PASSWD
+ if "passwd_role" in to_save:
+ content.role |= constants.ROLE_PASSWD
+ else:
+ content.role &= ~constants.ROLE_PASSWD
- if "edit_shelf_role" in to_save and not content.role_edit_shelfs():
- content.role = content.role + ub.ROLE_EDIT_SHELFS
- elif "edit_shelf_role" not in to_save and content.role_edit_shelfs():
- content.role = content.role - ub.ROLE_EDIT_SHELFS
+ if "edit_shelf_role" in to_save:
+ content.role |= constants.ROLE_EDIT_SHELFS
+ else:
+ content.role &= ~constants.ROLE_EDIT_SHELFS
- val = [int(k[5:]) for k, __ in to_save.items() if k.startswith('show')]
+ val = [int(k[5:]) for k, __ in to_save.items() if k.startswith('show_')]
sidebar = ub.get_sidebar_config()
for element in sidebar:
if element['visibility'] in val and not content.check_visibility(element['visibility']):
- content.sidebar_view += element['visibility']
+ content.sidebar_view |= element['visibility']
elif not element['visibility'] in val and content.check_visibility(element['visibility']):
- content.sidebar_view -= element['visibility']
+ content.sidebar_view &= ~element['visibility']
- if "Show_detail_random" in to_save and not content.show_detail_random():
- content.sidebar_view += ub.DETAIL_RANDOM
- elif "Show_detail_random" not in to_save and content.show_detail_random():
- content.sidebar_view -= ub.DETAIL_RANDOM
+ if "Show_detail_random" in to_save:
+ content.sidebar_view |= constants.DETAIL_RANDOM
+ else:
+ content.sidebar_view &= ~constants.DETAIL_RANDOM
content.mature_content = "Show_mature_content" in to_save
diff --git a/cps/cache_buster.py b/cps/cache_buster.py
index edd73cec..02aa7187 100644
--- a/cps/cache_buster.py
+++ b/cps/cache_buster.py
@@ -17,8 +17,14 @@
# Inspired by https://github.com/ChrisTM/Flask-CacheBust
# Uses query strings so CSS font files are found without having to resort to absolute URLs
-import hashlib
+from __future__ import division, print_function, unicode_literals
import os
+import hashlib
+
+from . import logger
+
+
+log = logger.create()
def init_cache_busting(app):
@@ -34,7 +40,7 @@ def init_cache_busting(app):
hash_table = {} # map of file hashes
- app.logger.debug('Computing cache-busting values...')
+ log.debug('Computing cache-busting values...')
# compute file hashes
for dirpath, __, filenames in os.walk(static_folder):
for filename in filenames:
@@ -47,7 +53,7 @@ def init_cache_busting(app):
file_path = rooted_filename.replace(static_folder, "")
file_path = file_path.replace("\\", "/") # Convert Windows path to web path
hash_table[file_path] = file_hash
- app.logger.debug('Finished computing cache-busting values')
+ log.debug('Finished computing cache-busting values')
def bust_filename(filename):
return hash_table.get(filename, "")
diff --git a/cps/cli.py b/cps/cli.py
index 0495b313..5d304ad0 100644
--- a/cps/cli.py
+++ b/cps/cli.py
@@ -18,9 +18,13 @@
# You should have received a copy of the GNU General Public License
# along with this program. If not, see .
-import argparse
-import os
+from __future__ import division, print_function, unicode_literals
import sys
+import os
+import argparse
+
+from .constants import CONFIG_DIR as _CONFIG_DIR
+
parser = argparse.ArgumentParser(description='Calibre Web is a web app'
' providing a interface for browsing, reading and downloading eBooks\n', prog='cps.py')
@@ -33,17 +37,8 @@ parser.add_argument('-k', metavar='path',
parser.add_argument('-v', action='store_true', help='shows version number and exits Calibre-web')
args = parser.parse_args()
-generalPath = os.path.normpath(os.getenv("CALIBRE_DBPATH",
- os.path.dirname(os.path.realpath(__file__)) + os.sep + ".." + os.sep))
-if args.p:
- settingspath = args.p
-else:
- settingspath = os.path.join(generalPath, "app.db")
-
-if args.g:
- gdpath = args.g
-else:
- gdpath = os.path.join(generalPath, "gdrive.db")
+settingspath = args.p or os.path.join(_CONFIG_DIR, "app.db")
+gdpath = args.g or os.path.join(_CONFIG_DIR, "gdrive.db")
certfilepath = None
keyfilepath = None
diff --git a/cps/comic.py b/cps/comic.py
index b68e638d..738b2a89 100755
--- a/cps/comic.py
+++ b/cps/comic.py
@@ -17,17 +17,21 @@
# You should have received a copy of the GNU General Public License
# along with this program. If not, see .
+from __future__ import division, print_function, unicode_literals
import os
-from constants import BookMeta
-from cps import app
-from iso639 import languages as isoLanguages
+
+from . import logger, isoLanguages
+from .constants import BookMeta
+
+
+log = logger.create()
try:
from comicapi.comicarchive import ComicArchive, MetaDataStyle
use_comic_meta = True
except ImportError as e:
- app.logger.warning('cannot import comicapi, extracting comic metadata will not work: %s', e)
+ log.warning('cannot import comicapi, extracting comic metadata will not work: %s', e)
import zipfile
import tarfile
use_comic_meta = False
diff --git a/cps/constants.py b/cps/constants.py
index 9e89f70f..09ac107d 100644
--- a/cps/constants.py
+++ b/cps/constants.py
@@ -2,7 +2,7 @@
# -*- coding: utf-8 -*-
# This file is part of the Calibre-Web (https://github.com/janeczku/calibre-web)
-# Copyright (C) 2019 OzzieIsaacs
+# Copyright (C) 2019 OzzieIsaacs, pwr
#
# 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
@@ -17,10 +17,99 @@
# You should have received a copy of the GNU General Public License
# along with this program. If not, see .
+from __future__ import division, print_function, unicode_literals
+import sys
+import os
from collections import namedtuple
+
+BASE_DIR = sys.path[0]
+STATIC_DIR = os.path.join(BASE_DIR, 'cps', 'static')
+TEMPLATES_DIR = os.path.join(BASE_DIR, 'cps', 'templates')
+TRANSLATIONS_DIR = os.path.join(BASE_DIR, 'cps', 'translations')
+CONFIG_DIR = os.environ.get('CALIBRE_DBPATH', BASE_DIR)
+
+
+ROLE_USER = 0 << 0
+ROLE_ADMIN = 1 << 0
+ROLE_DOWNLOAD = 1 << 1
+ROLE_UPLOAD = 1 << 2
+ROLE_EDIT = 1 << 3
+ROLE_PASSWD = 1 << 4
+ROLE_ANONYMOUS = 1 << 5
+ROLE_EDIT_SHELFS = 1 << 6
+ROLE_DELETE_BOOKS = 1 << 7
+ROLE_VIEWER = 1 << 8
+
+ALL_ROLES = {
+ "admin_role": ROLE_ADMIN,
+ "download_role": ROLE_DOWNLOAD,
+ "upload_role": ROLE_UPLOAD,
+ "edit_role": ROLE_EDIT,
+ "passwd_role": ROLE_PASSWD,
+ "edit_shelf_role": ROLE_EDIT_SHELFS,
+ "delete_role": ROLE_DELETE_BOOKS,
+ "viewer_role": ROLE_VIEWER,
+ }
+
+DETAIL_RANDOM = 1 << 0
+SIDEBAR_LANGUAGE = 1 << 1
+SIDEBAR_SERIES = 1 << 2
+SIDEBAR_CATEGORY = 1 << 3
+SIDEBAR_HOT = 1 << 4
+SIDEBAR_RANDOM = 1 << 5
+SIDEBAR_AUTHOR = 1 << 6
+SIDEBAR_BEST_RATED = 1 << 7
+SIDEBAR_READ_AND_UNREAD = 1 << 8
+SIDEBAR_RECENT = 1 << 9
+SIDEBAR_SORTED = 1 << 10
+MATURE_CONTENT = 1 << 11
+SIDEBAR_PUBLISHER = 1 << 12
+SIDEBAR_RATING = 1 << 13
+SIDEBAR_FORMAT = 1 << 14
+
+ADMIN_USER_ROLES = (ROLE_VIEWER << 1) - 1 - (ROLE_ANONYMOUS | ROLE_EDIT_SHELFS)
+ADMIN_USER_SIDEBAR = (SIDEBAR_FORMAT << 1) - 1
+
+UPDATE_STABLE = 0 << 0
+AUTO_UPDATE_STABLE = 1 << 0
+UPDATE_NIGHTLY = 1 << 1
+AUTO_UPDATE_NIGHTLY = 1 << 2
+
+LOGIN_STANDARD = 0
+LOGIN_LDAP = 1
+LOGIN_OAUTH_GITHUB = 2
+LOGIN_OAUTH_GOOGLE = 3
+
+
+DEFAULT_PASSWORD = "admin123"
+DEFAULT_PORT = 8083
+try:
+ env_CALIBRE_PORT = os.environ.get("CALIBRE_PORT", DEFAULT_PORT)
+ DEFAULT_PORT = int(env_CALIBRE_PORT)
+except ValueError:
+ print('Environment variable CALIBRE_PORT has invalid value (%s), faling back to default (8083)' % env_CALIBRE_PORT)
+del env_CALIBRE_PORT
+
+
+EXTENSIONS_AUDIO = {'mp3', 'm4a', 'm4b'}
+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',
+ 'fb2', 'html', 'rtf', 'odt', 'mp3', 'm4a', 'm4b'}
+# EXTENSIONS_READER = set(['txt', 'pdf', 'epub', 'zip', 'cbz', 'tar', 'cbt'] +
+# (['rar','cbr'] if feature_support['rar'] else []))
+
+
+def has_flag(value, bit_flag):
+ return bit_flag == (bit_flag & (value or 0))
+
+
"""
:rtype: BookMeta
"""
BookMeta = namedtuple('BookMeta', 'file_path, extension, title, author, cover, description, tags, series, '
'series_id, languages')
+
+
+# clean-up the module namespace
+del sys, os, namedtuple
diff --git a/cps/converter.py b/cps/converter.py
index d538619d..a2eb572d 100644
--- a/cps/converter.py
+++ b/cps/converter.py
@@ -17,14 +17,14 @@
# You should have received a copy of the GNU General Public License
# along with this program. If not, see .
-
+from __future__ import division, print_function, unicode_literals
import os
-# import subprocess
-import ub
import re
+
from flask_babel import gettext as _
-from subproc_wrapper import process_open
+
from . import config
+from .subproc_wrapper import process_open
def versionKindle():
diff --git a/cps/db.py b/cps/db.py
index 07ff54f7..ad75b123 100755
--- a/cps/db.py
+++ b/cps/db.py
@@ -18,16 +18,20 @@
# You should have received a copy of the GNU General Public License
# along with this program. If not, see .
-from sqlalchemy import *
-from sqlalchemy.ext.declarative import declarative_base
-from sqlalchemy.orm import *
+from __future__ import division, print_function, unicode_literals
+import sys
import os
import re
import ast
-from . import config
-import ub
-import sys
-import unidecode
+
+from sqlalchemy import create_engine
+from sqlalchemy import Table, Column, ForeignKey
+from sqlalchemy import String, Integer, Boolean
+from sqlalchemy.orm import relationship, sessionmaker, scoped_session
+from sqlalchemy.ext.declarative import declarative_base
+
+from . import config, ub
+
session = None
cc_exceptions = ['datetime', 'comments', 'float', 'composite', 'series']
diff --git a/cps/editbooks.py b/cps/editbooks.py
index 0cab9e23..15ee1661 100644
--- a/cps/editbooks.py
+++ b/cps/editbooks.py
@@ -21,28 +21,25 @@
# You should have received a copy of the GNU General Public License
# along with this program. If not, see .
-# opds routing functions
-from . import config, language_table, get_locale, app, ub, global_WorkerThread, db
-from flask import request, flash, redirect, url_for, abort, Markup, Response
-from flask import Blueprint
-import datetime
+from __future__ import division, print_function, unicode_literals
import os
+import datetime
import json
-from flask_babel import gettext as _
-from uuid import uuid4
-from . import helper
-from .helper import order_authors, common_filters
-from flask_login import current_user
-from .web import login_required_if_no_ano, render_title_template, edit_required, \
- upload_required, login_required, EXTENSIONS_UPLOAD
-from . import gdriveutils
from shutil import move, copyfile
-from . import uploader
-from iso639 import languages as isoLanguages
+from uuid import uuid4
+
+from flask import Blueprint, request, flash, redirect, url_for, abort, Markup, Response
+from flask_babel import gettext as _
+from flask_login import current_user
+
+from . import constants, logger, isoLanguages, gdriveutils, uploader, helper
+from . import config, get_locale, db, ub, global_WorkerThread, language_table
+from .helper import order_authors, common_filters
+from .web import login_required_if_no_ano, render_title_template, edit_required, upload_required, login_required
+
editbook = Blueprint('editbook', __name__)
-
-EXTENSIONS_CONVERT = {'pdf', 'epub', 'mobi', 'azw3', 'docx', 'rtf', 'fb2', 'lit', 'lrf', 'txt', 'htmlz', 'rtf', 'odt'}
+log = logger.create()
# Modifies different Database objects, first check if elements have to be added to database, than check
@@ -201,7 +198,7 @@ def delete_book(book_id, book_format):
db.session.commit()
else:
# book not found
- app.logger.info('Book with id "'+str(book_id)+'" could not be deleted')
+ log.error('Book with id "%s" could not be deleted: not found', book_id)
if book_format:
return redirect(url_for('editbook.edit_book', book_id=book_id))
else:
@@ -231,16 +228,16 @@ def render_edit_book(book_id):
valid_source_formats=list()
if config.config_ebookconverter == 2:
for file in book.data:
- if file.format.lower() in EXTENSIONS_CONVERT:
+ if file.format.lower() in constants.EXTENSIONS_CONVERT:
valid_source_formats.append(file.format.lower())
# Determine what formats don't already exist
- allowed_conversion_formats = EXTENSIONS_CONVERT.copy()
+ allowed_conversion_formats = constants.EXTENSIONS_CONVERT.copy()
for file in book.data:
try:
allowed_conversion_formats.remove(file.format.lower())
except Exception:
- app.logger.warning(file.format.lower() + ' already removed from list.')
+ log.warning('%s already removed from list.', file.format.lower())
return render_title_template('book_edit.html', book=book, authors=author_names, cc=cc,
title=_(u"edit metadata"), page="editbook",
@@ -321,7 +318,7 @@ def upload_single_file(request, book, book_id):
if requested_file.filename != '':
if '.' in requested_file.filename:
file_ext = requested_file.filename.rsplit('.', 1)[-1].lower()
- if file_ext not in EXTENSIONS_UPLOAD:
+ 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 redirect(url_for('web.show_book', book_id=book.id))
@@ -352,7 +349,7 @@ def upload_single_file(request, book, book_id):
# Format entry already exists, no need to update the database
if is_format:
- app.logger.info('Book format already existing')
+ log.warning('Book format %s already existing', file_ext.upper())
else:
db_format = db.Data(book_id, file_ext.upper(), file_size, file_name)
db.session.add(db_format)
@@ -530,7 +527,7 @@ def edit_book(book_id):
res = list(language_table[get_locale()].keys())[invers_lang_table.index(lang)]
input_l.append(res)
except ValueError:
- app.logger.error('%s is not a valid language' % lang)
+ log.error('%s is not a valid language', lang)
flash(_(u"%(langname)s is not a valid language", langname=lang), category="error")
modify_database_object(input_l, book.languages, db.Languages, db.session, 'languages')
@@ -569,7 +566,7 @@ def edit_book(book_id):
flash(error, category="error")
return render_edit_book(book_id)
except Exception as e:
- app.logger.exception(e)
+ log.exception(e)
db.session.rollback()
flash(_("Error editing book, please check logfile for details"), category="error")
return redirect(url_for('web.show_book', book_id=book.id))
@@ -590,7 +587,7 @@ def upload():
# 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 EXTENSIONS_UPLOAD:
+ 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")
@@ -631,7 +628,7 @@ def upload():
if meta.cover is None:
has_cover = 0
- copyfile(os.path.join(config.get_main_dir, "cps/static/generic_cover.jpg"),
+ copyfile(os.path.join(constants.STATIC_DIR, 'generic_cover.jpg'),
os.path.join(filepath, "cover.jpg"))
else:
has_cover = 1
@@ -741,9 +738,7 @@ def convert_bookformat(book_id):
flash(_(u"Source or destination format for conversion missing"), category="error")
return redirect(request.environ["HTTP_REFERER"])
- app.logger.debug('converting: book id: ' + str(book_id) +
- ' from: ' + request.form['book_format_from'] +
- ' to: ' + request.form['book_format_to'])
+ log.info('converting: book id: %s from: %s to: %s', book_id, book_format_from, book_format_to)
rtn = helper.convert_book_format(book_id, config.config_calibre_dir, book_format_from.upper(),
book_format_to.upper(), current_user.nickname)
diff --git a/cps/epub.py b/cps/epub.py
index 68325089..d9129646 100644
--- a/cps/epub.py
+++ b/cps/epub.py
@@ -17,11 +17,13 @@
# You should have received a copy of the GNU General Public License
# along with this program. If not, see .
+from __future__ import division, print_function, unicode_literals
+import os
import zipfile
from lxml import etree
-import os
+
+from . import isoLanguages
from .constants import BookMeta
-import isoLanguages
def extractCover(zipFile, coverFile, coverpath, tmp_file_name):
@@ -125,7 +127,7 @@ def get_epub_info(tmp_file_path, original_file_name, original_file_extension):
else:
title = epub_metadata['title']
- return uploader.BookMeta(
+ return BookMeta(
file_path=tmp_file_path,
extension=original_file_extension,
title=title.encode('utf-8').decode('utf-8'),
diff --git a/cps/fb2.py b/cps/fb2.py
index 4f113383..cd61b511 100644
--- a/cps/fb2.py
+++ b/cps/fb2.py
@@ -17,7 +17,9 @@
# You should have received a copy of the GNU General Public License
# along with this program. If not, see .
+from __future__ import division, print_function, unicode_literals
from lxml import etree
+
from .constants import BookMeta
diff --git a/cps/gdrive.py b/cps/gdrive.py
index b4749aa1..196b9dac 100644
--- a/cps/gdrive.py
+++ b/cps/gdrive.py
@@ -20,26 +20,31 @@
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see .
+
+from __future__ import division, print_function, unicode_literals
import os
-from flask import Blueprint
-from . import gdriveutils
-from flask import flash, request, redirect, url_for, abort
-from flask_babel import gettext as _
-from . import app, config, ub, db
-from flask_login import login_required
+import hashlib
import json
+import tempfile
from uuid import uuid4
from time import time
-import tempfile
from shutil import move, copyfile
-from .web import admin_required
+
+from flask import Blueprint, flash, request, redirect, url_for, abort
+from flask_babel import gettext as _
+from flask_login import login_required
try:
from googleapiclient.errors import HttpError
except ImportError:
pass
+from . import logger, gdriveutils, config, ub, db
+from .web import admin_required
+
+
gdrive = Blueprint('gdrive', __name__)
+log = logger.create()
current_milli_time = lambda: int(round(time() * 1000))
@@ -66,10 +71,10 @@ def google_drive_callback():
abort(403)
try:
credentials = gdriveutils.Gauth.Instance().auth.flow.step2_exchange(auth_code)
- with open(os.path.join(config.get_main_dir,'gdrive_credentials'), 'w') as f:
+ with open(gdriveutils.CREDENTIALS, 'w') as f:
f.write(credentials.to_json())
except ValueError as error:
- app.logger.error(error)
+ log.error(error)
return redirect(url_for('admin.configuration'))
@@ -78,7 +83,7 @@ def google_drive_callback():
@admin_required
def watch_gdrive():
if not config.config_google_drive_watch_changes_response:
- with open(os.path.join(config.get_main_dir,'client_secrets.json'), 'r') as settings:
+ with open(gdriveutils.CLIENT_SECRETS, 'r') as settings:
filedata = json.load(settings)
if filedata['web']['redirect_uris'][0].endswith('/'):
filedata['web']['redirect_uris'][0] = filedata['web']['redirect_uris'][0][:-((len('/gdrive/callback')+1))]
@@ -126,7 +131,7 @@ def revoke_watch_gdrive():
@gdrive.route("/gdrive/watch/callback", methods=['GET', 'POST'])
def on_received_watch_confirmation():
- app.logger.debug(request.headers)
+ log.debug('%r', request.headers)
if request.headers.get('X-Goog-Channel-Token') == gdrive_watch_callback_token \
and request.headers.get('X-Goog-Resource-State') == 'change' \
and request.data:
@@ -134,27 +139,26 @@ def on_received_watch_confirmation():
data = request.data
def updateMetaData():
- app.logger.info('Change received from gdrive')
- app.logger.debug(data)
+ log.info('Change received from gdrive')
+ log.debug('%r', data)
try:
j = json.loads(data)
- app.logger.info('Getting change details')
+ log.info('Getting change details')
response = gdriveutils.getChangeById(gdriveutils.Gdrive.Instance().drive, j['id'])
- app.logger.debug(response)
+ log.debug('%r', response)
if response:
dbpath = os.path.join(config.config_calibre_dir, "metadata.db")
if not response['deleted'] and response['file']['title'] == 'metadata.db' and response['file']['md5Checksum'] != hashlib.md5(dbpath):
tmpDir = tempfile.gettempdir()
- app.logger.info('Database file updated')
+ log.info('Database file updated')
copyfile(dbpath, os.path.join(tmpDir, "metadata.db_" + str(current_milli_time())))
- app.logger.info('Backing up existing and downloading updated metadata.db')
+ log.info('Backing up existing and downloading updated metadata.db')
gdriveutils.downloadFile(None, "metadata.db", os.path.join(tmpDir, "tmp_metadata.db"))
- app.logger.info('Setting up new DB')
+ log.info('Setting up new DB')
# prevent error on windows, as os.rename does on exisiting files
move(os.path.join(tmpDir, "tmp_metadata.db"), dbpath)
db.setup_db()
except Exception as e:
- app.logger.info(e.message)
- app.logger.exception(e)
+ log.exception(e)
updateMetaData()
return ''
diff --git a/cps/gdriveutils.py b/cps/gdriveutils.py
index c4b32e95..705966b2 100644
--- a/cps/gdriveutils.py
+++ b/cps/gdriveutils.py
@@ -17,23 +17,35 @@
# You should have received a copy of the GNU General Public License
# along with this program. If not, see .
+from __future__ import division, print_function, unicode_literals
+import os
+import shutil
+
+from flask import Response, stream_with_context
+from sqlalchemy import create_engine
+from sqlalchemy import Column, UniqueConstraint
+from sqlalchemy import String, Integer
+from sqlalchemy.orm import sessionmaker, scoped_session
+from sqlalchemy.ext.declarative import declarative_base
+
try:
from pydrive.auth import GoogleAuth
from pydrive.drive import GoogleDrive
- from pydrive.auth import RefreshError, InvalidConfigError
+ from pydrive.auth import RefreshError
from apiclient import errors
gdrive_support = True
except ImportError:
gdrive_support = False
-import os
-from . import config, app
-import cli
-import shutil
-from flask import Response, stream_with_context
-from sqlalchemy import *
-from sqlalchemy.ext.declarative import declarative_base
-from sqlalchemy.orm import *
+from . import logger, cli, config
+from .constants import BASE_DIR as _BASE_DIR
+
+
+SETTINGS_YAML = os.path.join(_BASE_DIR, 'settings.yaml')
+CREDENTIALS = os.path.join(_BASE_DIR, 'gdrive_credentials')
+CLIENT_SECRETS = os.path.join(_BASE_DIR, 'client_secrets.json')
+
+log = logger.create()
class Singleton:
@@ -78,7 +90,7 @@ class Singleton:
@Singleton
class Gauth:
def __init__(self):
- self.auth = GoogleAuth(settings_file=os.path.join(config.get_main_dir,'settings.yaml'))
+ self.auth = GoogleAuth(settings_file=SETTINGS_YAML)
@Singleton
@@ -87,8 +99,7 @@ class Gdrive:
self.drive = getDrive(gauth=Gauth.Instance().auth)
def is_gdrive_ready():
- return os.path.exists(os.path.join(config.get_main_dir, 'settings.yaml')) and \
- os.path.exists(os.path.join(config.get_main_dir, 'gdrive_credentials'))
+ return os.path.exists(SETTINGS_YAML) and os.path.exists(CREDENTIALS)
engine = create_engine('sqlite:///{0}'.format(cli.gdpath), echo=False)
@@ -150,17 +161,17 @@ migrate()
def getDrive(drive=None, gauth=None):
if not drive:
if not gauth:
- gauth = GoogleAuth(settings_file=os.path.join(config.get_main_dir,'settings.yaml'))
+ gauth = GoogleAuth(settings_file=SETTINGS_YAML)
# Try to load saved client credentials
- gauth.LoadCredentialsFile(os.path.join(config.get_main_dir,'gdrive_credentials'))
+ gauth.LoadCredentialsFile(CREDENTIALS)
if gauth.access_token_expired:
# Refresh them if expired
try:
gauth.Refresh()
except RefreshError as e:
- app.logger.error("Google Drive error: " + e.message)
+ log.error("Google Drive error: %s", e)
except Exception as e:
- app.logger.exception(e)
+ log.exception(e)
else:
# Initialize the saved creds
gauth.Authorize()
@@ -170,7 +181,7 @@ def getDrive(drive=None, gauth=None):
try:
drive.auth.Refresh()
except RefreshError as e:
- app.logger.error("Google Drive error: " + e.message)
+ log.error("Google Drive error: %s", e)
return drive
def listRootFolders():
@@ -207,7 +218,7 @@ def getEbooksFolderId(drive=None):
try:
gDriveId.gdrive_id = getEbooksFolder(drive)['id']
except Exception:
- app.logger.error('Error gDrive, root ID not found')
+ log.error('Error gDrive, root ID not found')
gDriveId.path = '/'
session.merge(gDriveId)
session.commit()
@@ -447,10 +458,10 @@ def getChangeById (drive, change_id):
change = drive.auth.service.changes().get(changeId=change_id).execute()
return change
except (errors.HttpError) as error:
- app.logger.info(error.message)
+ log.error(error)
return None
except Exception as e:
- app.logger.info(e)
+ log.error(e)
return None
@@ -520,6 +531,6 @@ def do_gdrive_download(df, headers):
if resp.status == 206:
yield content
else:
- app.logger.info('An error occurred: %s' % resp)
+ log.warning('An error occurred: %s', resp)
return
return Response(stream_with_context(stream()), headers=headers)
diff --git a/cps/helper.py b/cps/helper.py
index 2ff6515d..cc309780 100644
--- a/cps/helper.py
+++ b/cps/helper.py
@@ -18,40 +18,30 @@
# You should have received a copy of the GNU General Public License
# along with this program. If not, see .
-
-from . import config, global_WorkerThread, get_locale, db, mimetypes
-from flask import current_app as app
-from tempfile import gettempdir
+from __future__ import division, print_function, unicode_literals
import sys
-import io
import os
+import io
+import json
+import mimetypes
+import random
import re
-import unicodedata
-from .worker import STAT_WAITING, STAT_FAIL, STAT_STARTED, STAT_FINISH_SUCCESS, TASK_EMAIL, TASK_CONVERT, TASK_UPLOAD, \
- TASK_CONVERT_ANY
+import requests
+import shutil
import time
+import unicodedata
+from datetime import datetime
+from functools import reduce
+from tempfile import gettempdir
+
+from babel import Locale as LC
+from babel.core import UnknownLocaleError
+from babel.dates import format_datetime
from flask import send_from_directory, make_response, redirect, abort
from flask_babel import gettext as _
from flask_login import current_user
-from babel.dates import format_datetime
-from babel.core import UnknownLocaleError
-from datetime import datetime
-from babel import Locale as LC
-import shutil
-import requests
-from sqlalchemy.sql.expression import true, and_, false, text, func
-from iso639 import languages as isoLanguages
-from pagination import Pagination
+from sqlalchemy.sql.expression import true, false, and_, or_, text, func
from werkzeug.datastructures import Headers
-import json
-
-try:
- import gdriveutils as gd
-except ImportError:
- pass
-import random
-from subproc_wrapper import process_open
-import ub
try:
from urllib.parse import quote
@@ -70,17 +60,23 @@ try:
except ImportError:
use_levenshtein = False
-try:
- from functools import reduce
-except ImportError:
- pass # We're not using Python 3
-
try:
from PIL import Image
use_PIL = True
except ImportError:
use_PIL = False
+from . import logger, config, global_WorkerThread, get_locale, db, ub, isoLanguages
+from . import gdriveutils as gd
+from .constants import STATIC_DIR as _STATIC_DIR
+from .pagination import Pagination
+from .subproc_wrapper import process_open
+from .worker import STAT_WAITING, STAT_FAIL, STAT_STARTED, STAT_FINISH_SUCCESS
+from .worker import TASK_EMAIL, TASK_CONVERT, TASK_UPLOAD, TASK_CONVERT_ANY
+
+
+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 ==
@@ -96,7 +92,7 @@ def convert_book_format(book_id, calibrepath, old_book_format, new_book_format,
data = db.session.query(db.Data).filter(db.Data.book == book.id).filter(db.Data.format == old_book_format).first()
if not data:
error_message = _(u"%(format)s format not found for book id: %(book)d", format=old_book_format, book=book_id)
- app.logger.error("convert_book_format: " + error_message)
+ log.error("convert_book_format: %s", error_message)
return error_message
if config.config_use_google_drive:
df = gd.getFileFromEbooksFolder(book.path, data.name + "." + old_book_format.lower())
@@ -190,7 +186,7 @@ def check_send_to_kindle(entry):
'text':_('Convert %(orig)s to %(format)s and send to Kindle',orig='Epub',format='Azw3')})'''
return bookformats
else:
- app.logger.error(u'Cannot find book entry %d', entry.id)
+ log.error(u'Cannot find book entry %d', entry.id)
return None
@@ -275,8 +271,8 @@ def get_sorted_author(value):
value2 = value[-1] + ", " + " ".join(value[:-1])
else:
value2 = value
- except Exception:
- app.logger.error("Sorting author " + str(value) + "failed")
+ except Exception as ex:
+ log.error("Sorting author %s failed: %s", value, ex)
value2 = value
return value2
@@ -293,13 +289,12 @@ def delete_book_file(book, calibrepath, book_format=None):
else:
if os.path.isdir(path):
if len(next(os.walk(path))[1]):
- app.logger.error(
- "Deleting book " + str(book.id) + " failed, path has subfolders: " + book.path)
+ log.error("Deleting book %s failed, path has subfolders: %s", book.id, book.path)
return False
shutil.rmtree(path, ignore_errors=True)
return True
else:
- app.logger.error("Deleting book " + str(book.id) + " failed, book path not valid: " + book.path)
+ log.error("Deleting book %s failed, book path not valid: %s", book.id, book.path)
return False
@@ -322,7 +317,7 @@ def update_dir_structure_file(book_id, calibrepath, first_author):
if not os.path.exists(new_title_path):
os.renames(path, new_title_path)
else:
- app.logger.info("Copying title: " + path + " into existing: " + new_title_path)
+ log.info("Copying title: %s into existing: %s", path, new_title_path)
for dir_name, __, file_list in os.walk(path):
for file in file_list:
os.renames(os.path.join(dir_name, file),
@@ -330,8 +325,8 @@ def update_dir_structure_file(book_id, calibrepath, first_author):
path = new_title_path
localbook.path = localbook.path.split('/')[0] + '/' + new_titledir
except OSError as ex:
- app.logger.error("Rename title from: " + path + " to " + new_title_path + ": " + str(ex))
- app.logger.debug(ex, exc_info=True)
+ log.error("Rename title from: %s to %s: %s", path, new_title_path, ex)
+ log.debug(ex, exc_info=True)
return _("Rename title from: '%(src)s' to '%(dest)s' failed with error: %(error)s",
src=path, dest=new_title_path, error=str(ex))
if authordir != new_authordir:
@@ -340,8 +335,8 @@ def update_dir_structure_file(book_id, calibrepath, first_author):
os.renames(path, new_author_path)
localbook.path = new_authordir + '/' + localbook.path.split('/')[1]
except OSError as ex:
- app.logger.error("Rename author from: " + path + " to " + new_author_path + ": " + str(ex))
- app.logger.debug(ex, exc_info=True)
+ log.error("Rename author from: %s to %s: %s", path, new_author_path, ex)
+ log.debug(ex, exc_info=True)
return _("Rename author from: '%(src)s' to '%(dest)s' failed with error: %(error)s",
src=path, dest=new_author_path, error=str(ex))
# Rename all files from old names to new names
@@ -354,8 +349,8 @@ def update_dir_structure_file(book_id, calibrepath, first_author):
os.path.join(path_name, new_name + '.' + file_format.format.lower()))
file_format.name = new_name
except OSError as ex:
- app.logger.error("Rename file in path " + path + " to " + new_name + ": " + str(ex))
- app.logger.debug(ex, exc_info=True)
+ log.error("Rename file in path %s to %s: %s", path, new_name, ex)
+ log.debug(ex, exc_info=True)
return _("Rename file in path '%(src)s' to '%(dest)s' failed with error: %(error)s",
src=path, dest=new_name, error=str(ex))
return False
@@ -454,26 +449,25 @@ def get_book_cover(book_id):
if config.config_use_google_drive:
try:
if not gd.is_gdrive_ready():
- return send_from_directory(os.path.join(os.path.dirname(__file__), "static"), "generic_cover.jpg")
+ return send_from_directory(_STATIC_DIR, "generic_cover.jpg")
path=gd.get_cover_via_gdrive(book.path)
if path:
return redirect(path)
else:
- app.logger.error(book.path + '/cover.jpg not found on Google Drive')
- return send_from_directory(os.path.join(os.path.dirname(__file__), "static"), "generic_cover.jpg")
+ log.error('%s/cover.jpg not found on Google Drive', book.path)
+ return send_from_directory(_STATIC_DIR, "generic_cover.jpg")
except Exception as e:
- app.logger.error("Error Message: " + e.message)
- app.logger.exception(e)
+ log.exception(e)
# traceback.print_exc()
- return send_from_directory(os.path.join(os.path.dirname(__file__), "static"),"generic_cover.jpg")
+ return send_from_directory(_STATIC_DIR,"generic_cover.jpg")
else:
cover_file_path = os.path.join(config.config_calibre_dir, book.path)
if os.path.isfile(os.path.join(cover_file_path, "cover.jpg")):
return send_from_directory(cover_file_path, "cover.jpg")
else:
- return send_from_directory(os.path.join(os.path.dirname(__file__), "static"),"generic_cover.jpg")
+ return send_from_directory(_STATIC_DIR,"generic_cover.jpg")
else:
- return send_from_directory(os.path.join(os.path.dirname(__file__), "static"),"generic_cover.jpg")
+ return send_from_directory(_STATIC_DIR,"generic_cover.jpg")
# saves book cover from url
@@ -493,15 +487,15 @@ def save_cover_from_filestorage(filepath, saved_filename, img):
try:
os.makedirs(filepath)
except OSError:
- app.logger.error(u"Failed to create path for cover")
+ log.error(u"Failed to create path for cover")
return False
try:
img.save(os.path.join(filepath, saved_filename))
except OSError:
- app.logger.error(u"Failed to store cover-file")
+ log.error(u"Failed to store cover-file")
return False
except IOError:
- app.logger.error(u"Cover-file is not a valid image file")
+ log.error(u"Cover-file is not a valid image file")
return False
return True
@@ -512,7 +506,7 @@ def save_cover(img, book_path):
if use_PIL:
if content_type not in ('image/jpeg', 'image/png', 'image/webp'):
- app.logger.error("Only jpg/jpeg/png/webp files are supported as coverfile")
+ log.error("Only jpg/jpeg/png/webp files are supported as coverfile")
return False
# convert to jpg because calibre only supports jpg
if content_type in ('image/png', 'image/webp'):
@@ -526,7 +520,7 @@ def save_cover(img, book_path):
img._content = tmp_bytesio.getvalue()
else:
if content_type not in ('image/jpeg'):
- app.logger.error("Only jpg/jpeg files are supported as coverfile")
+ log.error("Only jpg/jpeg files are supported as coverfile")
return False
if ub.config.config_use_google_drive:
@@ -534,7 +528,7 @@ def save_cover(img, book_path):
if save_cover_from_filestorage(tmpDir, "uploaded_cover.jpg", img) is True:
gd.uploadFileToEbooksFolder(os.path.join(book_path, 'cover.jpg'),
os.path.join(tmpDir, "uploaded_cover.jpg"))
- app.logger.info("Cover is saved on Google Drive")
+ log.info("Cover is saved on Google Drive")
return True
else:
return False
@@ -547,7 +541,7 @@ def do_download_file(book, book_format, data, headers):
if config.config_use_google_drive:
startTime = time.time()
df = gd.getFileFromEbooksFolder(book.path, data.name + "." + book_format)
- app.logger.debug(time.time() - startTime)
+ log.debug('%s', time.time() - startTime)
if df:
return gd.do_gdrive_download(df, headers)
else:
@@ -556,7 +550,7 @@ def do_download_file(book, book_format, data, headers):
filename = os.path.join(config.config_calibre_dir, book.path)
if not os.path.isfile(os.path.join(filename, data.name + "." + book_format)):
# ToDo: improve error handling
- app.logger.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))
response = make_response(send_from_directory(filename, data.name + "." + book_format))
response.headers = headers
return response
@@ -581,7 +575,7 @@ def check_unrar(unrarLocation):
version = value.group(1)
except OSError as e:
error = True
- app.logger.exception(e)
+ log.exception(e)
version =_(u'Error excecuting UnRar')
else:
version = _(u'Unrar binary file not found')
@@ -724,12 +718,12 @@ def get_search_results(term):
db.Books.authors.any(db.func.lower(db.Authors.name).ilike("%" + term + "%"))
return db.session.query(db.Books).filter(common_filters()).filter(
- db.or_(db.Books.tags.any(db.func.lower(db.Tags.name).ilike("%" + term + "%")),
- db.Books.series.any(db.func.lower(db.Series.name).ilike("%" + term + "%")),
- db.Books.authors.any(and_(*q)),
- db.Books.publishers.any(db.func.lower(db.Publishers.name).ilike("%" + term + "%")),
- db.func.lower(db.Books.title).ilike("%" + term + "%")
- )).all()
+ or_(db.Books.tags.any(db.func.lower(db.Tags.name).ilike("%" + term + "%")),
+ db.Books.series.any(db.func.lower(db.Series.name).ilike("%" + term + "%")),
+ db.Books.authors.any(and_(*q)),
+ db.Books.publishers.any(db.func.lower(db.Publishers.name).ilike("%" + term + "%")),
+ db.func.lower(db.Books.title).ilike("%" + term + "%")
+ )).all()
def get_unique_other_books(library_books, author_books):
# Get all identifiers (ISBN, Goodreads, etc) and filter author's books by that list so we show fewer duplicates
diff --git a/cps/isoLanguages.py b/cps/isoLanguages.py
index 31ef341e..ab112270 100644
--- a/cps/isoLanguages.py
+++ b/cps/isoLanguages.py
@@ -17,6 +17,9 @@
# You should have received a copy of the GNU General Public License
# along with this program. If not, see .
+from __future__ import division, print_function, unicode_literals
+
+
try:
from iso639 import languages, __version__
get = languages.get
diff --git a/cps/jinjia.py b/cps/jinjia.py
index 480ae274..37f9ce30 100644
--- a/cps/jinjia.py
+++ b/cps/jinjia.py
@@ -23,15 +23,21 @@
# custom jinja filters
-from flask import Blueprint, request, url_for
+from __future__ import division, print_function, unicode_literals
import datetime
+import mimetypes
import re
-from . import mimetypes, app
+
from babel.dates import format_date
+from flask import Blueprint, request, url_for
from flask_babel import get_locale
from flask_login import current_user
+from . import logger
+
+
jinjia = Blueprint('jinjia', __name__)
+log = logger.create()
# pagination links in jinja
@@ -79,8 +85,7 @@ def formatdate_filter(val):
formatdate = datetime.datetime.strptime(conformed_timestamp[:15], "%Y%m%d %H%M%S")
return format_date(formatdate, format='medium', locale=get_locale())
except AttributeError as e:
- app.logger.error('Babel error: %s, Current user locale: %s, Current User: %s' %
- (e, current_user.locale, current_user.nickname))
+ log.error('Babel error: %s, Current user locale: %s, Current User: %s', e, current_user.locale, current_user.nickname)
return formatdate
@jinjia.app_template_filter('formatdateinput')
diff --git a/cps/logger.py b/cps/logger.py
new file mode 100644
index 00000000..cf90f3f8
--- /dev/null
+++ b/cps/logger.py
@@ -0,0 +1,126 @@
+# -*- coding: utf-8 -*-
+
+# This file is part of the Calibre-Web (https://github.com/janeczku/calibre-web)
+# Copyright (C) 2019 pwr
+#
+# 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 .
+
+from __future__ import division, print_function, unicode_literals
+import os
+import inspect
+import logging
+from logging import Formatter, StreamHandler
+from logging.handlers import RotatingFileHandler
+
+from .constants import BASE_DIR as _BASE_DIR
+
+
+# FORMATTER = Formatter("[%(asctime)s] %(levelname)5s {%(name)s:%(lineno)d %(funcName)s} %(message)s")
+FORMATTER = Formatter("[%(asctime)s] %(levelname)5s {%(name)s:%(lineno)d} %(message)s")
+DEFAULT_LOG_LEVEL = logging.INFO
+DEFAULT_LOG_FILE = os.path.join(_BASE_DIR, "calibre-web.log")
+LOG_TO_STDERR = '/dev/stderr'
+
+
+logging.addLevelName(logging.WARNING, "WARN")
+logging.addLevelName(logging.CRITICAL, "CRIT")
+
+
+def get(name=None):
+ return logging.getLogger(name)
+
+
+def create():
+ parent_frame = inspect.stack(0)[1]
+ if hasattr(parent_frame, 'frame'):
+ parent_frame = parent_frame.frame
+ else:
+ parent_frame = parent_frame[0]
+ parent_module = inspect.getmodule(parent_frame)
+ return get(parent_module.__name__)
+
+
+def is_debug_enabled():
+ return logging.root.level <= logging.DEBUG
+
+
+def get_level_name(level):
+ return logging.getLevelName(level)
+
+
+def is_valid_logfile(file_path):
+ if not file_path:
+ return True
+ if os.path.isdir(file_path):
+ return False
+ log_dir = os.path.dirname(file_path)
+ return (not log_dir) or os.path.isdir(log_dir)
+
+
+def setup(log_file, log_level=None):
+ if log_file:
+ if not os.path.dirname(log_file):
+ log_file = os.path.join(_BASE_DIR, log_file)
+ log_file = os.path.abspath(log_file)
+ else:
+ # log_file = LOG_TO_STDERR
+ log_file = DEFAULT_LOG_FILE
+
+ # print ('%r -- %r' % (log_level, log_file))
+ r = logging.root
+ r.setLevel(log_level or DEFAULT_LOG_LEVEL)
+
+ previous_handler = r.handlers[0] if r.handlers else None
+ # print ('previous %r' % previous_handler)
+
+ if previous_handler:
+ # if the log_file has not changed, don't create a new handler
+ if getattr(previous_handler, 'baseFilename', None) == log_file:
+ return
+ r.debug("logging to %s level %s", log_file, r.level)
+
+ if log_file == LOG_TO_STDERR:
+ file_handler = StreamHandler()
+ file_handler.baseFilename = LOG_TO_STDERR
+ else:
+ try:
+ file_handler = RotatingFileHandler(log_file, maxBytes=50000, backupCount=2)
+ except IOError:
+ if log_file == DEFAULT_LOG_FILE:
+ raise
+ file_handler = RotatingFileHandler(DEFAULT_LOG_FILE, maxBytes=50000, backupCount=2)
+ file_handler.setFormatter(FORMATTER)
+
+ for h in r.handlers:
+ r.removeHandler(h)
+ h.close()
+ r.addHandler(file_handler)
+ # print ('new handler %r' % file_handler)
+
+
+# Enable logging of smtp lib debug output
+class StderrLogger(object):
+ def __init__(self, name=None):
+ self.log = get(name or self.__class__.__name__)
+ self.buffer = ''
+
+ def write(self, message):
+ try:
+ if message == '\n':
+ self.log.debug(self.buffer.replace('\n', '\\n'))
+ self.buffer = ''
+ else:
+ self.buffer += message
+ except:
+ self.logger.debug("Logging Error")
diff --git a/cps/oauth.py b/cps/oauth.py
index 960a3810..35362dbf 100644
--- a/cps/oauth.py
+++ b/cps/oauth.py
@@ -1,7 +1,10 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
+from __future__ import division, print_function, unicode_literals
from flask import session
+
+
try:
from flask_dance.consumer.backend.sqla import SQLAlchemyBackend, first, _get_real_user
from sqlalchemy.orm.exc import NoResultFound
diff --git a/cps/oauth_bb.py b/cps/oauth_bb.py
index fb4ba60c..7a0a7101 100644
--- a/cps/oauth_bb.py
+++ b/cps/oauth_bb.py
@@ -21,30 +21,34 @@
# You should have received a copy of the GNU General Public License
# along with this program. If not, see
-from flask_dance.contrib.github import make_github_blueprint, github
-from flask_dance.contrib.google import make_google_blueprint, google
-from flask_dance.consumer import oauth_authorized, oauth_error
-from oauth import OAuthBackend
-from sqlalchemy.orm.exc import NoResultFound
-from flask import session, request, make_response, abort
+from __future__ import division, print_function, unicode_literals
import json
-from cps import config, app
-import ub
-from flask_login import login_user, current_user
+from functools import wraps
+from oauth import OAuthBackend
+
+from flask import session, request, make_response, abort
from flask import Blueprint, flash, redirect, url_for
from flask_babel import gettext as _
-# from web import github_oauth_required
-from functools import wraps
-from web import login_required
+from flask_dance.consumer import oauth_authorized, oauth_error
+from flask_dance.contrib.github import make_github_blueprint, github
+from flask_dance.contrib.google import make_google_blueprint, google
+from flask_login import login_user, current_user
+from sqlalchemy.orm.exc import NoResultFound
+
+from . import constants, logger, config, app, ub
+from .web import login_required
+# from .web import github_oauth_required
+
oauth_check = {}
oauth = Blueprint('oauth', __name__)
+log = logger.create()
def github_oauth_required(f):
@wraps(f)
def inner(*args, **kwargs):
- if config.config_login_type == ub.LOGIN_OAUTH_GITHUB:
+ if config.config_login_type == constants.LOGIN_OAUTH_GITHUB:
return f(*args, **kwargs)
if request.is_xhr:
data = {'status': 'error', 'message': 'Not Found'}
@@ -59,7 +63,7 @@ def github_oauth_required(f):
def google_oauth_required(f):
@wraps(f)
def inner(*args, **kwargs):
- if config.config_use_google_oauth == ub.LOGIN_OAUTH_GOOGLE:
+ if config.config_use_google_oauth == constants.LOGIN_OAUTH_GOOGLE:
return f(*args, **kwargs)
if request.is_xhr:
data = {'status': 'error', 'message': 'Not Found'}
@@ -101,7 +105,7 @@ def register_user_with_oauth(user=None):
try:
ub.session.commit()
except Exception as e:
- app.logger.exception(e)
+ log.exception(e)
ub.session.rollback()
@@ -195,7 +199,7 @@ if ub.oauth_support:
ub.session.add(oauth)
ub.session.commit()
except Exception as e:
- app.logger.exception(e)
+ log.exception(e)
ub.session.rollback()
# Disable Flask-Dance's default behavior for saving the OAuth token
@@ -221,7 +225,7 @@ if ub.oauth_support:
ub.session.add(oauth)
ub.session.commit()
except Exception as e:
- app.logger.exception(e)
+ log.exception(e)
ub.session.rollback()
return redirect(url_for('web.login'))
#if config.config_public_reg:
@@ -264,11 +268,11 @@ if ub.oauth_support:
logout_oauth_user()
flash(_(u"Unlink to %(oauth)s success.", oauth=oauth_check[provider]), category="success")
except Exception as e:
- app.logger.exception(e)
+ log.exception(e)
ub.session.rollback()
flash(_(u"Unlink to %(oauth)s failed.", oauth=oauth_check[provider]), category="error")
except NoResultFound:
- app.logger.warning("oauth %s for user %d not fount" % (provider, current_user.id))
+ log.warning("oauth %s for user %d not fount", provider, current_user.id)
flash(_(u"Not linked to %(oauth)s.", oauth=oauth_check[provider]), category="error")
return redirect(url_for('web.profile'))
diff --git a/cps/opds.py b/cps/opds.py
index 86b24ab6..dd7755bb 100644
--- a/cps/opds.py
+++ b/cps/opds.py
@@ -21,22 +21,24 @@
# You should have received a copy of the GNU General Public License
# along with this program. If not, see .
-# opds routing functions
-from . import config, db
-from flask import request, render_template, Response, g, make_response
-from pagination import Pagination
-from flask import Blueprint
-import datetime
-import ub
-from flask_login import current_user
-from functools import wraps
-from .web import login_required_if_no_ano, common_filters, get_search_results, render_read_books, download_required
-from sqlalchemy.sql.expression import func, text
-from werkzeug.security import check_password_hash
-from .helper import fill_indexpage, get_download_link, get_book_cover
+from __future__ import division, print_function, unicode_literals
import sys
+import datetime
+from functools import wraps
+
+from flask import Blueprint, request, render_template, Response, g, make_response
+from flask_login import current_user
+from sqlalchemy.sql.expression import func, text, or_, and_
+from werkzeug.security import check_password_hash
+
+from . import logger, config, db, ub
+from .helper import fill_indexpage, get_download_link, get_book_cover
+from .pagination import Pagination
+from .web import common_filters, get_search_results, render_read_books, download_required
+
opds = Blueprint('opds', __name__)
+log = logger.create()
def requires_basic_auth_if_no_ano(f):
@@ -231,10 +233,10 @@ def feed_shelf(book_id):
if current_user.is_anonymous:
shelf = ub.session.query(ub.Shelf).filter(ub.Shelf.is_public == 1, ub.Shelf.id == book_id).first()
else:
- shelf = ub.session.query(ub.Shelf).filter(ub.or_(ub.and_(ub.Shelf.user_id == int(current_user.id),
- ub.Shelf.id == book_id),
- ub.and_(ub.Shelf.is_public == 1,
- ub.Shelf.id == book_id))).first()
+ shelf = ub.session.query(ub.Shelf).filter(or_(and_(ub.Shelf.user_id == int(current_user.id),
+ ub.Shelf.id == book_id),
+ and_(ub.Shelf.is_public == 1,
+ ub.Shelf.id == book_id))).first()
result = list()
# user is allowed to access shelf
if shelf:
diff --git a/cps/pagination.py b/cps/pagination.py
index 50fbc4e5..0a138a64 100644
--- a/cps/pagination.py
+++ b/cps/pagination.py
@@ -21,6 +21,7 @@
# You should have received a copy of the GNU General Public License
# along with this program. If not, see .
+from __future__ import division, print_function, unicode_literals
from math import ceil
diff --git a/cps/redirect.py b/cps/redirect.py
index 7b3981c4..324c4b20 100644
--- a/cps/redirect.py
+++ b/cps/redirect.py
@@ -28,10 +28,12 @@
# http://flask.pocoo.org/snippets/62/
+from __future__ import division, print_function, unicode_literals
try:
from urllib.parse import urlparse, urljoin
except ImportError:
from urlparse import urlparse, urljoin
+
from flask import request, url_for, redirect
diff --git a/cps/reverseproxy.py b/cps/reverseproxy.py
index 3b256cb4..25bbe77b 100644
--- a/cps/reverseproxy.py
+++ b/cps/reverseproxy.py
@@ -37,6 +37,8 @@
#
# Inspired by http://flask.pocoo.org/snippets/35/
+from __future__ import division, print_function, unicode_literals
+
class ReverseProxied(object):
"""Wrap the application in this middleware and configure the
diff --git a/cps/server.py b/cps/server.py
index f104203a..3dd03257 100644
--- a/cps/server.py
+++ b/cps/server.py
@@ -17,12 +17,11 @@
# You should have received a copy of the GNU General Public License
# along with this program. If not, see .
-
-from socket import error as SocketError
+from __future__ import division, print_function, unicode_literals
import sys
import os
import signal
-from . import config, global_WorkerThread
+import socket
try:
from gevent.pywsgi import WSGIServer
@@ -36,6 +35,11 @@ except ImportError:
from tornado import version as tornadoVersion
gevent_present = False
+from . import logger, config, global_WorkerThread
+
+
+log = logger.create()
+
class server:
@@ -49,76 +53,77 @@ class server:
def init_app(self, application):
self.app = application
+ self.port = config.config_port
+
+ self.ssl_args = None
+ certfile_path = config.get_config_certfile()
+ keyfile_path = config.get_config_keyfile()
+ if certfile_path and keyfile_path:
+ if os.path.isfile(certfile_path) and os.path.isfile(keyfile_path):
+ self.ssl_args = {"certfile": certfile_path,
+ "keyfile": keyfile_path}
+ else:
+ log.warning('The specified paths for the ssl certificate file and/or key file seem to be broken. Ignoring ssl.')
+ log.warning('Cert path: %s', certfile_path)
+ log.warning('Key path: %s', keyfile_path)
+
+ def _make_gevent_socket(self):
+ if os.name == 'nt':
+ return ('0.0.0.0', self.port)
+
+ try:
+ s = WSGIServer.get_listener(('', self.port), family=socket.AF_INET6)
+ except socket.error as ex:
+ log.error('%s', ex)
+ log.warning('Unable to listen on \'\', trying on IPv4 only...')
+ s = WSGIServer.get_listener(('', self.port), family=socket.AF_INET)
+ log.debug("%r %r", s._sock, s._sock.getsockname())
+ return s
def start_gevent(self):
- ssl_args = dict()
- try:
- certfile_path = config.get_config_certfile()
- keyfile_path = config.get_config_keyfile()
- if certfile_path and keyfile_path:
- if os.path.isfile(certfile_path) and os.path.isfile(keyfile_path):
- ssl_args = {"certfile": certfile_path,
- "keyfile": keyfile_path}
- else:
- self.app.logger.info('The specified paths for the ssl certificate file and/or key file seem '
- 'to be broken. Ignoring ssl. Cert path: %s | Key path: '
- '%s' % (certfile_path, keyfile_path))
- if os.name == 'nt':
- self.wsgiserver = WSGIServer(('0.0.0.0', config.config_port), self.app, spawn=Pool(), **ssl_args)
- else:
- self.wsgiserver = WSGIServer(('', config.config_port), self.app, spawn=Pool(), **ssl_args)
- self.wsgiserver.serve_forever()
+ ssl_args = self.ssl_args or {}
+ log.info('Starting Gevent server')
- except SocketError:
- try:
- self.app.logger.info('Unable to listen on \'\', trying on IPv4 only...')
- self.wsgiserver = WSGIServer(('0.0.0.0', config.config_port), self.app, spawn=Pool(), **ssl_args)
- self.wsgiserver.serve_forever()
- except (OSError, SocketError) as e:
- self.app.logger.info("Error starting server: %s" % e.strerror)
- print("Error starting server: %s" % e.strerror)
- global_WorkerThread.stop()
- sys.exit(1)
+ try:
+ sock = self._make_gevent_socket()
+ self.wsgiserver = WSGIServer(sock, self.app, spawn=Pool(), **ssl_args)
+ self.wsgiserver.serve_forever()
+ except (OSError, socket.error) as e:
+ log.info("Error starting server: %s", e.strerror)
+ print("Error starting server: %s" % e.strerror)
+ global_WorkerThread.stop()
+ sys.exit(1)
except Exception:
- self.app.logger.info("Unknown error while starting gevent")
+ log.exception("Unknown error while starting gevent")
+
+ def start_tornado(self):
+ log.info('Starting Tornado server')
+
+ try:
+ # Max Buffersize set to 200MB
+ http_server = HTTPServer(WSGIContainer(self.app),
+ max_buffer_size = 209700000,
+ ssl_options=self.ssl_args)
+ http_server.listen(self.port)
+ self.wsgiserver=IOLoop.instance()
+ self.wsgiserver.start()
+ # wait for stop signal
+ self.wsgiserver.close(True)
+ except socket.error as err:
+ log.exception("Error starting tornado server")
+ print("Error starting server: %s" % err.strerror)
+ global_WorkerThread.stop()
+ sys.exit(1)
def startServer(self):
if gevent_present:
- self.app.logger.info('Starting Gevent server')
# leave subprocess out to allow forking for fetchers and processors
self.start_gevent()
else:
- try:
- ssl = None
- self.app.logger.info('Starting Tornado server')
- certfile_path = config.get_config_certfile()
- keyfile_path = config.get_config_keyfile()
- if certfile_path and keyfile_path:
- if os.path.isfile(certfile_path) and os.path.isfile(keyfile_path):
- ssl = {"certfile": certfile_path,
- "keyfile": keyfile_path}
- else:
- self.app.logger.info('The specified paths for the ssl certificate file and/or key file '
- 'seem to be broken. Ignoring ssl. Cert path: %s | Key '
- 'path: %s' % (certfile_path, keyfile_path))
-
- # Max Buffersize set to 200MB
- http_server = HTTPServer(WSGIContainer(self.app),
- max_buffer_size = 209700000,
- ssl_options=ssl)
- http_server.listen(config.config_port)
- self.wsgiserver=IOLoop.instance()
- self.wsgiserver.start()
- # wait for stop signal
- self.wsgiserver.close(True)
- except SocketError as e:
- self.app.logger.info("Error starting server: %s" % e.strerror)
- print("Error starting server: %s" % e.strerror)
- global_WorkerThread.stop()
- sys.exit(1)
+ self.start_tornado()
if self.restart is True:
- self.app.logger.info("Performing restart of Calibre-Web")
+ log.info("Performing restart of Calibre-Web")
global_WorkerThread.stop()
if os.name == 'nt':
arguments = ["\"" + sys.executable + "\""]
@@ -128,7 +133,7 @@ class server:
else:
os.execl(sys.executable, sys.executable, *sys.argv)
else:
- self.app.logger.info("Performing shutdown of Calibre-Web")
+ log.info("Performing shutdown of Calibre-Web")
global_WorkerThread.stop()
sys.exit(0)
diff --git a/cps/shelf.py b/cps/shelf.py
index 1a926b0e..392c05b1 100644
--- a/cps/shelf.py
+++ b/cps/shelf.py
@@ -21,28 +21,34 @@
# You should have received a copy of the GNU General Public License
# along with this program. If not, see .
+from __future__ import division, print_function, unicode_literals
+
from flask import Blueprint, request, flash, redirect, url_for
-from . import ub, searched_ids, app, db
from flask_babel import gettext as _
-from sqlalchemy.sql.expression import func, or_
from flask_login import login_required, current_user
+from sqlalchemy.sql.expression import func, or_, and_
+
+from . import logger, ub, searched_ids, db
from .web import render_title_template
+
shelf = Blueprint('shelf', __name__)
+log = logger.create()
+
@shelf.route("/shelf/add//")
@login_required
def add_to_shelf(shelf_id, book_id):
shelf = ub.session.query(ub.Shelf).filter(ub.Shelf.id == shelf_id).first()
if shelf is None:
- app.logger.info("Invalid shelf specified")
+ log.error("Invalid shelf specified: %s", shelf_id)
if not request.is_xhr:
flash(_(u"Invalid shelf specified"), category="error")
return redirect(url_for('web.index'))
return "Invalid shelf specified", 400
if not shelf.is_public and not shelf.user_id == int(current_user.id):
- app.logger.info("Sorry you are not allowed to add a book to the the shelf: %s" % shelf.name)
+ log.error("User %s not allowed to add a book to %s", current_user, shelf)
if not request.is_xhr:
flash(_(u"Sorry you are not allowed to add a book to the the shelf: %(shelfname)s", shelfname=shelf.name),
category="error")
@@ -50,7 +56,7 @@ def add_to_shelf(shelf_id, book_id):
return "Sorry you are not allowed to add a book to the the shelf: %s" % shelf.name, 403
if shelf.is_public and not current_user.role_edit_shelfs():
- app.logger.info("User is not allowed to edit public shelves")
+ log.info("User %s not allowed to edit public shelves", current_user)
if not request.is_xhr:
flash(_(u"You are not allowed to edit public shelves"), category="error")
return redirect(url_for('web.index'))
@@ -59,7 +65,7 @@ def add_to_shelf(shelf_id, book_id):
book_in_shelf = ub.session.query(ub.BookShelf).filter(ub.BookShelf.shelf == shelf_id,
ub.BookShelf.book_id == book_id).first()
if book_in_shelf:
- app.logger.info("Book is already part of the shelf: %s" % shelf.name)
+ log.error("Book %s is already part of %s", book_id, shelf)
if not request.is_xhr:
flash(_(u"Book is already part of the shelf: %(shelfname)s", shelfname=shelf.name), category="error")
return redirect(url_for('web.index'))
@@ -88,17 +94,17 @@ def add_to_shelf(shelf_id, book_id):
def search_to_shelf(shelf_id):
shelf = ub.session.query(ub.Shelf).filter(ub.Shelf.id == shelf_id).first()
if shelf is None:
- app.logger.info("Invalid shelf specified")
+ log.error("Invalid shelf specified: %s", shelf_id)
flash(_(u"Invalid shelf specified"), category="error")
return redirect(url_for('web.index'))
if not shelf.is_public and not shelf.user_id == int(current_user.id):
- app.logger.info("You are not allowed to add a book to the the shelf: %s" % shelf.name)
+ log.error("User %s not allowed to add a book to %s", current_user, shelf)
flash(_(u"You are not allowed to add a book to the the shelf: %(name)s", name=shelf.name), category="error")
return redirect(url_for('web.index'))
if shelf.is_public and not current_user.role_edit_shelfs():
- app.logger.info("User is not allowed to edit public shelves")
+ log.error("User %s not allowed to edit public shelves", current_user)
flash(_(u"User is not allowed to edit public shelves"), category="error")
return redirect(url_for('web.index'))
@@ -116,7 +122,7 @@ def search_to_shelf(shelf_id):
books_for_shelf = searched_ids[current_user.id]
if not books_for_shelf:
- app.logger.info("Books are already part of the shelf: %s" % shelf.name)
+ log.error("Books are already part of %s", shelf)
flash(_(u"Books are already part of the shelf: %(name)s", name=shelf.name), category="error")
return redirect(url_for('web.index'))
@@ -142,7 +148,7 @@ def search_to_shelf(shelf_id):
def remove_from_shelf(shelf_id, book_id):
shelf = ub.session.query(ub.Shelf).filter(ub.Shelf.id == shelf_id).first()
if shelf is None:
- app.logger.info("Invalid shelf specified")
+ log.error("Invalid shelf specified: %s", shelf_id)
if not request.is_xhr:
return redirect(url_for('web.index'))
return "Invalid shelf specified", 400
@@ -161,7 +167,7 @@ def remove_from_shelf(shelf_id, book_id):
ub.BookShelf.book_id == book_id).first()
if book_shelf is None:
- app.logger.info("Book already removed from shelf")
+ log.error("Book %s already removed from %s", book_id, shelf)
if not request.is_xhr:
return redirect(url_for('web.index'))
return "Book already removed from shelf", 410
@@ -174,7 +180,7 @@ def remove_from_shelf(shelf_id, book_id):
return redirect(request.environ["HTTP_REFERER"])
return "", 204
else:
- app.logger.info("Sorry you are not allowed to remove a book from this shelf: %s" % shelf.name)
+ log.error("User %s not allowed to remove a book from %s", current_user, shelf)
if not request.is_xhr:
flash(_(u"Sorry you are not allowed to remove a book from this shelf: %(sname)s", sname=shelf.name),
category="error")
@@ -248,15 +254,15 @@ def delete_shelf(shelf_id):
else:
if (not cur_shelf.is_public and cur_shelf.user_id == int(current_user.id)) \
or (cur_shelf.is_public and current_user.role_edit_shelfs()):
- deleted = ub.session.query(ub.Shelf).filter(ub.or_(ub.and_(ub.Shelf.user_id == int(current_user.id),
- ub.Shelf.id == shelf_id),
- ub.and_(ub.Shelf.is_public == 1,
- ub.Shelf.id == shelf_id))).delete()
+ deleted = ub.session.query(ub.Shelf).filter(or_(and_(ub.Shelf.user_id == int(current_user.id),
+ ub.Shelf.id == shelf_id),
+ and_(ub.Shelf.is_public == 1,
+ ub.Shelf.id == shelf_id))).delete()
if deleted:
ub.session.query(ub.BookShelf).filter(ub.BookShelf.shelf == shelf_id).delete()
ub.session.commit()
- app.logger.info(_(u"successfully deleted shelf %(name)s", name=cur_shelf.name, category="success"))
+ log.info("successfully deleted %s", cur_shelf)
return redirect(url_for('web.index'))
# @shelf.route("/shelfdown/")
@@ -267,10 +273,10 @@ def show_shelf(type, shelf_id):
if current_user.is_anonymous:
shelf = ub.session.query(ub.Shelf).filter(ub.Shelf.is_public == 1, ub.Shelf.id == shelf_id).first()
else:
- shelf = ub.session.query(ub.Shelf).filter(ub.or_(ub.and_(ub.Shelf.user_id == int(current_user.id),
- ub.Shelf.id == shelf_id),
- ub.and_(ub.Shelf.is_public == 1,
- ub.Shelf.id == shelf_id))).first()
+ shelf = ub.session.query(ub.Shelf).filter(or_(and_(ub.Shelf.user_id == int(current_user.id),
+ ub.Shelf.id == shelf_id),
+ and_(ub.Shelf.is_public == 1,
+ ub.Shelf.id == shelf_id))).first()
result = list()
# user is allowed to access shelf
if shelf:
@@ -283,7 +289,7 @@ def show_shelf(type, shelf_id):
if cur_book:
result.append(cur_book)
else:
- app.logger.info('Not existing book %s in shelf %s deleted' % (book.book_id, shelf.id))
+ 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.commit()
return render_title_template(page, entries=result, title=_(u"Shelf: '%(name)s'", name=shelf.name),
@@ -309,10 +315,10 @@ def order_shelf(shelf_id):
if current_user.is_anonymous:
shelf = ub.session.query(ub.Shelf).filter(ub.Shelf.is_public == 1, ub.Shelf.id == shelf_id).first()
else:
- shelf = ub.session.query(ub.Shelf).filter(ub.or_(ub.and_(ub.Shelf.user_id == int(current_user.id),
- ub.Shelf.id == shelf_id),
- ub.and_(ub.Shelf.is_public == 1,
- ub.Shelf.id == shelf_id))).first()
+ shelf = ub.session.query(ub.Shelf).filter(or_(and_(ub.Shelf.user_id == int(current_user.id),
+ ub.Shelf.id == shelf_id),
+ and_(ub.Shelf.is_public == 1,
+ ub.Shelf.id == shelf_id))).first()
result = list()
if shelf:
books_in_shelf2 = ub.session.query(ub.BookShelf).filter(ub.BookShelf.shelf == shelf_id) \
diff --git a/cps/subproc_wrapper.py b/cps/subproc_wrapper.py
index d8826111..0371fd7e 100644
--- a/cps/subproc_wrapper.py
+++ b/cps/subproc_wrapper.py
@@ -16,9 +16,11 @@
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see .
-import subprocess
-import os
+
+from __future__ import division, print_function, unicode_literals
import sys
+import os
+import subprocess
def process_open(command, quotes=(), env=None, sout=subprocess.PIPE):
diff --git a/cps/ub.py b/cps/ub.py
index 9253dafe..b8d44375 100644
--- a/cps/ub.py
+++ b/cps/ub.py
@@ -18,79 +18,36 @@
# You should have received a copy of the GNU General Public License
# along with this program. If not, see .
-from sqlalchemy import *
-from sqlalchemy import exc
-from sqlalchemy.ext.declarative import declarative_base
-from sqlalchemy.orm import *
-from flask_login import AnonymousUserMixin
+from __future__ import division, print_function, unicode_literals
import sys
import os
-import logging
-from werkzeug.security import generate_password_hash
-import json
import datetime
+import json
from binascii import hexlify
-import cli
+
from flask import g
from flask_babel import gettext as _
-
+from flask_login import AnonymousUserMixin
try:
from flask_dance.consumer.backend.sqla import OAuthConsumerMixin
oauth_support = True
except ImportError:
oauth_support = False
+from sqlalchemy import create_engine, exc, exists
+from sqlalchemy import Column, ForeignKey
+from sqlalchemy import String, Integer, SmallInteger, Boolean, DateTime
+from sqlalchemy.orm import relationship, sessionmaker
+from sqlalchemy.ext.declarative import declarative_base
+from werkzeug.security import generate_password_hash
try:
import ldap
except ImportError:
pass
-ROLE_USER = 0
-ROLE_ADMIN = 1
-ROLE_DOWNLOAD = 2
-ROLE_UPLOAD = 4
-ROLE_EDIT = 8
-ROLE_PASSWD = 16
-ROLE_ANONYMOUS = 32
-ROLE_EDIT_SHELFS = 64
-ROLE_DELETE_BOOKS = 128
-ROLE_VIEWER = 256
+from . import constants, logger, cli
-DETAIL_RANDOM = 1
-SIDEBAR_LANGUAGE = 2
-SIDEBAR_SERIES = 4
-SIDEBAR_CATEGORY = 8
-SIDEBAR_HOT = 16
-SIDEBAR_RANDOM = 32
-SIDEBAR_AUTHOR = 64
-SIDEBAR_BEST_RATED = 128
-SIDEBAR_READ_AND_UNREAD = 256
-SIDEBAR_RECENT = 512
-SIDEBAR_SORTED = 1024
-MATURE_CONTENT = 2048
-SIDEBAR_PUBLISHER = 4096
-SIDEBAR_RATING = 8192
-SIDEBAR_FORMAT = 16384
-
-UPDATE_STABLE = 0
-AUTO_UPDATE_STABLE = 1
-UPDATE_NIGHTLY = 2
-AUTO_UPDATE_NIGHTLY = 4
-
-LOGIN_STANDARD = 0
-LOGIN_LDAP = 1
-LOGIN_OAUTH_GITHUB = 2
-LOGIN_OAUTH_GOOGLE = 3
-
-DEFAULT_PASS = "admin123"
-try:
- DEFAULT_PORT = int(os.environ.get("CALIBRE_PORT", 8083))
-except ValueError:
- print ('Environmentvariable CALIBRE_PORT is set to an invalid value: ' +
- os.environ.get("CALIBRE_PORT", 8083) + ', faling back to default (8083)')
- DEFAULT_PORT = 8083
-
session = None
@@ -109,47 +66,47 @@ def get_sidebar_config(kwargs=None):
content = 'conf' in kwargs
sidebar = list()
sidebar.append({"glyph": "glyphicon-book", "text": _('Recently Added'), "link": 'web.index', "id": "new",
- "visibility": SIDEBAR_RECENT, 'public': True, "page": "root",
+ "visibility": constants.SIDEBAR_RECENT, 'public': True, "page": "root",
"show_text": _('Show recent books'), "config_show":True})
sidebar.append({"glyph": "glyphicon-fire", "text": _('Hot Books'), "link": 'web.books_list', "id": "hot",
- "visibility": SIDEBAR_HOT, 'public': True, "page": "hot", "show_text": _('Show hot books'),
+ "visibility": constants.SIDEBAR_HOT, 'public': True, "page": "hot", "show_text": _('Show hot books'),
"config_show":True})
sidebar.append(
{"glyph": "glyphicon-star", "text": _('Best rated Books'), "link": 'web.books_list', "id": "rated",
- "visibility": SIDEBAR_BEST_RATED, 'public': True, "page": "rated",
+ "visibility": constants.SIDEBAR_BEST_RATED, 'public': True, "page": "rated",
"show_text": _('Show best rated books'), "config_show":True})
sidebar.append({"glyph": "glyphicon-eye-open", "text": _('Read Books'), "link": 'web.books_list', "id": "read",
- "visibility": SIDEBAR_READ_AND_UNREAD, 'public': (not g.user.is_anonymous), "page": "read",
+ "visibility": constants.SIDEBAR_READ_AND_UNREAD, 'public': (not g.user.is_anonymous), "page": "read",
"show_text": _('Show read and unread'), "config_show": content})
sidebar.append(
{"glyph": "glyphicon-eye-close", "text": _('Unread Books'), "link": 'web.books_list', "id": "unread",
- "visibility": SIDEBAR_READ_AND_UNREAD, 'public': (not g.user.is_anonymous), "page": "unread",
+ "visibility": constants.SIDEBAR_READ_AND_UNREAD, 'public': (not g.user.is_anonymous), "page": "unread",
"show_text": _('Show unread'), "config_show":False})
sidebar.append({"glyph": "glyphicon-random", "text": _('Discover'), "link": 'web.books_list', "id": "rand",
- "visibility": SIDEBAR_RANDOM, 'public': True, "page": "discover",
+ "visibility": constants.SIDEBAR_RANDOM, 'public': True, "page": "discover",
"show_text": _('Show random books'), "config_show":True})
sidebar.append({"glyph": "glyphicon-inbox", "text": _('Categories'), "link": 'web.category_list', "id": "cat",
- "visibility": SIDEBAR_CATEGORY, 'public': True, "page": "category",
+ "visibility": constants.SIDEBAR_CATEGORY, 'public': True, "page": "category",
"show_text": _('Show category selection'), "config_show":True})
sidebar.append({"glyph": "glyphicon-bookmark", "text": _('Series'), "link": 'web.series_list', "id": "serie",
- "visibility": SIDEBAR_SERIES, 'public': True, "page": "series",
+ "visibility": constants.SIDEBAR_SERIES, 'public': True, "page": "series",
"show_text": _('Show series selection'), "config_show":True})
sidebar.append({"glyph": "glyphicon-user", "text": _('Authors'), "link": 'web.author_list', "id": "author",
- "visibility": SIDEBAR_AUTHOR, 'public': True, "page": "author",
+ "visibility": constants.SIDEBAR_AUTHOR, 'public': True, "page": "author",
"show_text": _('Show author selection'), "config_show":True})
sidebar.append(
{"glyph": "glyphicon-text-size", "text": _('Publishers'), "link": 'web.publisher_list', "id": "publisher",
- "visibility": SIDEBAR_PUBLISHER, 'public': True, "page": "publisher",
+ "visibility": constants.SIDEBAR_PUBLISHER, 'public': True, "page": "publisher",
"show_text": _('Show publisher selection'), "config_show":True})
sidebar.append({"glyph": "glyphicon-flag", "text": _('Languages'), "link": 'web.language_overview', "id": "lang",
- "visibility": SIDEBAR_LANGUAGE, 'public': (g.user.filter_language() == 'all'),
+ "visibility": constants.SIDEBAR_LANGUAGE, 'public': (g.user.filter_language() == 'all'),
"page": "language",
"show_text": _('Show language selection'), "config_show":True})
sidebar.append({"glyph": "glyphicon-star-empty", "text": _('Ratings'), "link": 'web.ratings_list', "id": "rate",
- "visibility": SIDEBAR_RATING, 'public': True,
+ "visibility": constants.SIDEBAR_RATING, 'public': True,
"page": "rating", "show_text": _('Show ratings selection'), "config_show":True})
sidebar.append({"glyph": "glyphicon-file", "text": _('File formats'), "link": 'web.formats_list', "id": "format",
- "visibility": SIDEBAR_FORMAT, 'public': True,
+ "visibility": constants.SIDEBAR_FORMAT, 'public': True,
"page": "format", "show_text": _('Show file formats selection'), "config_show":True})
return sidebar
@@ -161,51 +118,35 @@ class UserBase:
def is_authenticated(self):
return True
+ def _has_role(self, role_flag):
+ return constants.has_flag(self.role, role_flag)
+
def role_admin(self):
- if self.role is not None:
- return True if self.role & ROLE_ADMIN == ROLE_ADMIN else False
- else:
- return False
+ return self._has_role(constants.ROLE_ADMIN)
def role_download(self):
- if self.role is not None:
- return True if self.role & ROLE_DOWNLOAD == ROLE_DOWNLOAD else False
- else:
- return False
+ return self._has_role(constants.ROLE_DOWNLOAD)
def role_upload(self):
- return bool((self.role is not None)and(self.role & ROLE_UPLOAD == ROLE_UPLOAD))
+ return self._has_role(constants.ROLE_UPLOAD)
def role_edit(self):
- if self.role is not None:
- return True if self.role & ROLE_EDIT == ROLE_EDIT else False
- else:
- return False
+ return self._has_role(constants.ROLE_EDIT)
def role_passwd(self):
- if self.role is not None:
- return True if self.role & ROLE_PASSWD == ROLE_PASSWD else False
- else:
- return False
+ return self._has_role(constants.ROLE_PASSWD)
def role_anonymous(self):
- if self.role is not None:
- return True if self.role & ROLE_ANONYMOUS == ROLE_ANONYMOUS else False
- else:
- return False
+ return self._has_role(constants.ROLE_ANONYMOUS)
def role_edit_shelfs(self):
- if self.role is not None:
- return True if self.role & ROLE_EDIT_SHELFS == ROLE_EDIT_SHELFS else False
- else:
- return False
+ return self._has_role(constants.ROLE_EDIT_SHELFS)
def role_delete_books(self):
- return bool((self.role is not None)and(self.role & ROLE_DELETE_BOOKS == ROLE_DELETE_BOOKS))
-
+ return self._has_role(constants.ROLE_DELETE_BOOKS)
def role_viewer(self):
- return bool((self.role is not None)and(self.role & ROLE_VIEWER == ROLE_VIEWER))
+ return self._has_role(constants.ROLE_VIEWER)
@property
def is_active(self):
@@ -222,10 +163,10 @@ class UserBase:
return self.default_language
def check_visibility(self, value):
- return bool((self.sidebar_view is not None) and (self.sidebar_view & value == value))
+ return constants.has_flag(self.sidebar_view, value)
def show_detail_random(self):
- return bool((self.sidebar_view is not None)and(self.sidebar_view & DETAIL_RANDOM == DETAIL_RANDOM))
+ return self.check_visibility(constants.DETAIL_RANDOM)
def __repr__(self):
return '' % self.nickname
@@ -246,7 +187,7 @@ class User(UserBase, Base):
id = Column(Integer, primary_key=True)
nickname = Column(String(64), unique=True)
email = Column(String(120), unique=True, default="")
- role = Column(SmallInteger, default=ROLE_USER)
+ role = Column(SmallInteger, default=constants.ROLE_USER)
password = Column(String)
kindle_mail = Column(String(120), default="")
shelf = relationship('Shelf', backref='user', lazy='dynamic', order_by='Shelf.name')
@@ -270,7 +211,7 @@ class Anonymous(AnonymousUserMixin, UserBase):
self.loadSettings()
def loadSettings(self):
- data = session.query(User).filter(User.role.op('&')(ROLE_ANONYMOUS) == ROLE_ANONYMOUS).first() # type: User
+ data = session.query(User).filter(User.role.op('&')(constants.ROLE_ANONYMOUS) == constants.ROLE_ANONYMOUS).first() # type: User
settings = session.query(Settings).first()
self.nickname = data.nickname
self.role = data.role
@@ -308,7 +249,7 @@ class Shelf(Base):
user_id = Column(Integer, ForeignKey('user.id'))
def __repr__(self):
- return '' % self.name
+ return '' % (self.id, self.name)
# Baseclass representing Relationship between books and Shelfs in Calibre-Web in app.db (N:M)
@@ -379,7 +320,7 @@ class Settings(Base):
mail_password = Column(String)
mail_from = Column(String)
config_calibre_dir = Column(String)
- config_port = Column(Integer, default=DEFAULT_PORT)
+ config_port = Column(Integer, default=constants.DEFAULT_PORT)
config_certfile = Column(String)
config_keyfile = Column(String)
config_calibre_web_title = Column(String, default=u'Calibre-Web')
@@ -388,7 +329,7 @@ class Settings(Base):
config_authors_max = Column(Integer, default=0)
config_read_column = Column(Integer, default=0)
config_title_regex = Column(String, default=u'^(A|The|An|Der|Die|Das|Den|Ein|Eine|Einen|Dem|Des|Einem|Eines)\s+')
- config_log_level = Column(SmallInteger, default=logging.INFO)
+ config_log_level = Column(SmallInteger, default=logger.DEFAULT_LOG_LEVEL)
config_uploading = Column(SmallInteger, default=0)
config_anonbrowse = Column(SmallInteger, default=0)
config_public_reg = Column(SmallInteger, default=0)
@@ -445,8 +386,6 @@ class RemoteAuthToken(Base):
# Class holds all application specific settings in calibre-web
class Config:
def __init__(self):
- self.config_main_dir = os.path.join(os.path.normpath(os.path.dirname(
- os.path.realpath(__file__)) + os.sep + ".." + os.sep))
self.db_configured = None
self.config_logfile = None
self.loadSettings()
@@ -497,19 +436,12 @@ class Config:
# self.config_use_google_oauth = data.config_use_google_oauth
self.config_google_oauth_client_id = data.config_google_oauth_client_id
self.config_google_oauth_client_secret = data.config_google_oauth_client_secret
- if data.config_mature_content_tags:
- self.config_mature_content_tags = data.config_mature_content_tags
- else:
- self.config_mature_content_tags = u''
- if data.config_logfile:
- self.config_logfile = data.config_logfile
+ self.config_mature_content_tags = data.config_mature_content_tags or u''
+ self.config_logfile = data.config_logfile or u''
self.config_rarfile_location = data.config_rarfile_location
self.config_theme = data.config_theme
self.config_updatechannel = data.config_updatechannel
-
- @property
- def get_main_dir(self):
- return self.config_main_dir
+ logger.setup(self.config_logfile, self.config_log_level)
@property
def get_update_channel(self):
@@ -533,72 +465,41 @@ class Config:
else:
return self.config_keyfile
- def get_config_logfile(self):
- if not self.config_logfile:
- return os.path.join(self.get_main_dir, "calibre-web.log")
- else:
- if os.path.dirname(self.config_logfile):
- return self.config_logfile
- else:
- return os.path.join(self.get_main_dir, self.config_logfile)
+ def _has_role(self, role_flag):
+ return constants.has_flag(self.config_default_role, role_flag)
def role_admin(self):
- if self.config_default_role is not None:
- return True if self.config_default_role & ROLE_ADMIN == ROLE_ADMIN else False
- else:
- return False
+ return self._has_role(constants.ROLE_ADMIN)
def role_download(self):
- if self.config_default_role is not None:
- return True if self.config_default_role & ROLE_DOWNLOAD == ROLE_DOWNLOAD else False
- else:
- return False
+ return self._has_role(constants.ROLE_DOWNLOAD)
def role_viewer(self):
- if self.config_default_role is not None:
- return True if self.config_default_role & ROLE_VIEWER == ROLE_VIEWER else False
- else:
- return False
+ return self._has_role(constants.ROLE_VIEWER)
def role_upload(self):
- if self.config_default_role is not None:
- return True if self.config_default_role & ROLE_UPLOAD == ROLE_UPLOAD else False
- else:
- return False
+ return self._has_role(constants.ROLE_UPLOAD)
def role_edit(self):
- if self.config_default_role is not None:
- return True if self.config_default_role & ROLE_EDIT == ROLE_EDIT else False
- else:
- return False
+ return self._has_role(constants.ROLE_EDIT)
def role_passwd(self):
- if self.config_default_role is not None:
- return True if self.config_default_role & ROLE_PASSWD == ROLE_PASSWD else False
- else:
- return False
+ return self._has_role(constants.ROLE_PASSWD)
def role_edit_shelfs(self):
- if self.config_default_role is not None:
- return True if self.config_default_role & ROLE_EDIT_SHELFS == ROLE_EDIT_SHELFS else False
- else:
- return False
+ return self._has_role(constants.ROLE_EDIT_SHELFS)
def role_delete_books(self):
- return bool((self.config_default_role is not None) and
- (self.config_default_role & ROLE_DELETE_BOOKS == ROLE_DELETE_BOOKS))
-
- def show_detail_random(self):
- return bool((self.config_default_show is not None) and
- (self.config_default_show & DETAIL_RANDOM == DETAIL_RANDOM))
+ return self._has_role(constants.ROLE_DELETE_BOOKS)
def show_element_new_user(self, value):
- return bool((self.config_default_show is not None) and
- (self.config_default_show & value == value))
+ return constants.has_flag(self.config_default_show, value)
+
+ def show_detail_random(self):
+ return self.show_element_new_user(constants.DETAIL_RANDOM)
def show_mature_content(self):
- return bool((self.config_default_show is not None) and
- (self.config_default_show & MATURE_CONTENT == MATURE_CONTENT))
+ return self.show_element_new_user(constants.MATURE_CONTENT)
def mature_content_tags(self):
if sys.version_info > (3, 0): # Python3 str, Python2 unicode
@@ -608,16 +509,7 @@ class Config:
return list(map(lstrip, self.config_mature_content_tags.split(",")))
def get_Log_Level(self):
- ret_value = ""
- if self.config_log_level == logging.INFO:
- ret_value = 'INFO'
- elif self.config_log_level == logging.DEBUG:
- ret_value = 'DEBUG'
- elif self.config_log_level == logging.WARNING:
- ret_value = 'WARNING'
- elif self.config_log_level == logging.ERROR:
- ret_value = 'ERROR'
- return ret_value
+ return logger.get_level_name(self.config_log_level)
# Migrate database to current version, has to be updated after every database change. Currently migration from
@@ -696,9 +588,9 @@ def migrate_Database():
conn.execute("UPDATE user SET 'sidebar_view' = (random_books* :side_random + language_books * :side_lang "
"+ series_books * :side_series + category_books * :side_category + hot_books * "
":side_hot + :side_autor + :detail_random)"
- ,{'side_random': SIDEBAR_RANDOM, 'side_lang': SIDEBAR_LANGUAGE, 'side_series': SIDEBAR_SERIES,
- 'side_category': SIDEBAR_CATEGORY, 'side_hot': SIDEBAR_HOT, 'side_autor': SIDEBAR_AUTHOR,
- 'detail_random': DETAIL_RANDOM})
+ ,{'side_random': constants.SIDEBAR_RANDOM, 'side_lang': constants.SIDEBAR_LANGUAGE, 'side_series': constants.SIDEBAR_SERIES,
+ 'side_category': constants.SIDEBAR_CATEGORY, 'side_hot': constants.SIDEBAR_HOT, 'side_autor': constants.SIDEBAR_AUTHOR,
+ 'detail_random': constants.DETAIL_RANDOM})
session.commit()
try:
session.query(exists().where(User.mature_content)).scalar()
@@ -706,7 +598,7 @@ def migrate_Database():
conn = engine.connect()
conn.execute("ALTER TABLE user ADD column `mature_content` INTEGER DEFAULT 1")
- if session.query(User).filter(User.role.op('&')(ROLE_ANONYMOUS) == ROLE_ANONYMOUS).first() is None:
+ if session.query(User).filter(User.role.op('&')(constants.ROLE_ANONYMOUS) == constants.ROLE_ANONYMOUS).first() is None:
create_anonymous_user()
try:
session.query(exists().where(Settings.config_remote_login)).scalar()
@@ -850,7 +742,7 @@ def create_anonymous_user():
user = User()
user.nickname = "Guest"
user.email = 'no@email'
- user.role = ROLE_ANONYMOUS
+ user.role = constants.ROLE_ANONYMOUS
user.password = ''
session.add(user)
@@ -864,13 +756,10 @@ def create_anonymous_user():
def create_admin_user():
user = User()
user.nickname = "admin"
- user.role = ROLE_USER + ROLE_ADMIN + ROLE_DOWNLOAD + ROLE_UPLOAD + ROLE_EDIT + ROLE_DELETE_BOOKS + ROLE_PASSWD +\
- ROLE_VIEWER
- user.sidebar_view = DETAIL_RANDOM + SIDEBAR_LANGUAGE + SIDEBAR_SERIES + SIDEBAR_CATEGORY + SIDEBAR_HOT + \
- SIDEBAR_RANDOM + SIDEBAR_AUTHOR + SIDEBAR_BEST_RATED + SIDEBAR_READ_AND_UNREAD + SIDEBAR_RECENT + \
- SIDEBAR_SORTED + MATURE_CONTENT + SIDEBAR_PUBLISHER + SIDEBAR_RATING + SIDEBAR_FORMAT
+ user.role = constants.ADMIN_USER_ROLES
+ user.sidebar_view = constants.ADMIN_USER_SIDEBAR
- user.password = generate_password_hash(DEFAULT_PASS)
+ user.password = generate_password_hash(constants.DEFAULT_PASSWORD)
session.add(user)
try:
diff --git a/cps/updater.py b/cps/updater.py
index d7ec63b0..020d7c44 100644
--- a/cps/updater.py
+++ b/cps/updater.py
@@ -17,22 +17,27 @@
# You should have received a copy of the GNU General Public License
# along with this program. If not, see .
-
-from . import config, get_locale, Server, app
-import threading
-import zipfile
-import requests
-import time
-from io import BytesIO
-import os
+from __future__ import division, print_function, unicode_literals
import sys
-import shutil
-from ub import UPDATE_STABLE
-from tempfile import gettempdir
+import os
import datetime
import json
-from flask_babel import gettext as _
+import requests
+import shutil
+import threading
+import time
+import zipfile
+from io import BytesIO
+from tempfile import gettempdir
+
from babel.dates import format_datetime
+from flask_babel import gettext as _
+
+from . import constants, logger, config, get_locale, Server
+
+
+log = logger.create()
+_REPOSITORY_API_URL = 'https://api.github.com/repos/janeczku/calibre-web'
def is_sha1(sha1):
@@ -53,13 +58,13 @@ class Updater(threading.Thread):
self.updateIndex = None
def get_current_version_info(self):
- if config.get_update_channel == UPDATE_STABLE:
+ if config.get_update_channel == constants.UPDATE_STABLE:
return self._stable_version_info()
else:
return self._nightly_version_info()
def get_available_updates(self, request_method):
- if config.get_update_channel == UPDATE_STABLE:
+ if config.get_update_channel == constants.UPDATE_STABLE:
return self._stable_available_updates(request_method)
else:
return self._nightly_available_updates(request_method)
@@ -67,45 +72,45 @@ class Updater(threading.Thread):
def run(self):
try:
self.status = 1
- app.logger.debug(u'Download update file')
+ log.debug(u'Download update file')
headers = {'Accept': 'application/vnd.github.v3+json'}
r = requests.get(self._get_request_path(), stream=True, headers=headers)
r.raise_for_status()
self.status = 2
- app.logger.debug(u'Opening zipfile')
+ log.debug(u'Opening zipfile')
z = zipfile.ZipFile(BytesIO(r.content))
self.status = 3
- app.logger.debug(u'Extracting zipfile')
+ log.debug(u'Extracting zipfile')
tmp_dir = gettempdir()
z.extractall(tmp_dir)
foldername = os.path.join(tmp_dir, z.namelist()[0])[:-1]
if not os.path.isdir(foldername):
self.status = 11
- app.logger.info(u'Extracted contents of zipfile not found in temp folder')
+ log.info(u'Extracted contents of zipfile not found in temp folder')
return
self.status = 4
- app.logger.debug(u'Replacing files')
- self.update_source(foldername, config.get_main_dir)
+ log.debug(u'Replacing files')
+ self.update_source(foldername, constants.BASE_DIR)
self.status = 6
- app.logger.debug(u'Preparing restart of server')
+ log.debug(u'Preparing restart of server')
time.sleep(2)
Server.setRestartTyp(True)
Server.stopServer()
self.status = 7
time.sleep(2)
except requests.exceptions.HTTPError as ex:
- app.logger.info( u'HTTP Error' + ' ' + str(ex))
+ log.info(u'HTTP Error %s', ex)
self.status = 8
except requests.exceptions.ConnectionError:
- app.logger.info(u'Connection error')
+ log.info(u'Connection error')
self.status = 9
except requests.exceptions.Timeout:
- app.logger.info(u'Timeout while establishing connection')
+ log.info(u'Timeout while establishing connection')
self.status = 10
except requests.exceptions.RequestException:
self.status = 11
- app.logger.info(u'General error')
+ log.info(u'General error')
def get_update_status(self):
return self.status
@@ -153,14 +158,14 @@ class Updater(threading.Thread):
if sys.platform == "win32" or sys.platform == "darwin":
change_permissions = False
else:
- app.logger.debug('Update on OS-System : ' + sys.platform)
+ log.debug('Update on OS-System : %s', sys.platform)
new_permissions = os.stat(root_dst_dir)
# print new_permissions
for src_dir, __, files in os.walk(root_src_dir):
dst_dir = src_dir.replace(root_src_dir, root_dst_dir, 1)
if not os.path.exists(dst_dir):
os.makedirs(dst_dir)
- app.logger.debug('Create-Dir: '+dst_dir)
+ log.debug('Create-Dir: %s', dst_dir)
if change_permissions:
# print('Permissions: User '+str(new_permissions.st_uid)+' Group '+str(new_permissions.st_uid))
os.chown(dst_dir, new_permissions.st_uid, new_permissions.st_gid)
@@ -170,22 +175,22 @@ class Updater(threading.Thread):
if os.path.exists(dst_file):
if change_permissions:
permission = os.stat(dst_file)
- app.logger.debug('Remove file before copy: '+dst_file)
+ log.debug('Remove file before copy: %s', dst_file)
os.remove(dst_file)
else:
if change_permissions:
permission = new_permissions
shutil.move(src_file, dst_dir)
- app.logger.debug('Move File '+src_file+' to '+dst_dir)
+ log.debug('Move File %s to %s', src_file, dst_dir)
if change_permissions:
try:
os.chown(dst_file, permission.st_uid, permission.st_gid)
except (Exception) as e:
# ex = sys.exc_info()
old_permissions = os.stat(dst_file)
- app.logger.debug('Fail change permissions of ' + str(dst_file) + '. Before: '
- + str(old_permissions.st_uid) + ':' + str(old_permissions.st_gid) + ' After: '
- + str(permission.st_uid) + ':' + str(permission.st_gid) + ' error: '+str(e))
+ log.debug('Fail change permissions of %s. Before: %s:%s After %s:%s error: %s',
+ dst_file, old_permissions.st_uid, old_permissions.st_gid,
+ permission.st_uid, permission.st_gid, e)
return
def update_source(self, source, destination):
@@ -219,15 +224,15 @@ class Updater(threading.Thread):
for item in remove_items:
item_path = os.path.join(destination, item[1:])
if os.path.isdir(item_path):
- app.logger.debug("Delete dir " + item_path)
+ log.debug("Delete dir %s", item_path)
shutil.rmtree(item_path, ignore_errors=True)
else:
try:
- app.logger.debug("Delete file " + item_path)
+ log.debug("Delete file %s", item_path)
# log_from_thread("Delete file " + item_path)
os.remove(item_path)
except Exception:
- app.logger.debug("Could not remove:" + item_path)
+ log.debug("Could not remove: %s", item_path)
shutil.rmtree(source, ignore_errors=True)
@classmethod
@@ -248,7 +253,7 @@ class Updater(threading.Thread):
def _nightly_available_updates(self, request_method):
tz = datetime.timedelta(seconds=time.timezone if (time.localtime().tm_isdst == 0) else time.altzone)
if request_method == "GET":
- repository_url = 'https://api.github.com/repos/janeczku/calibre-web'
+ repository_url = _REPOSITORY_API_URL
status, commit = self._load_remote_data(repository_url +'/git/refs/heads/master')
parents = []
if status['message'] != '':
@@ -348,7 +353,7 @@ class Updater(threading.Thread):
if request_method == "GET":
parents = []
# repository_url = 'https://api.github.com/repos/flatpak/flatpak/releases' # test URL
- repository_url = 'https://api.github.com/repos/janeczku/calibre-web/releases?per_page=100'
+ repository_url = _REPOSITORY_API_URL + '/releases?per_page=100'
status, commit = self._load_remote_data(repository_url)
if status['message'] != '':
return json.dumps(status)
@@ -434,10 +439,10 @@ class Updater(threading.Thread):
return json.dumps(status)
def _get_request_path(self):
- if config.get_update_channel == UPDATE_STABLE:
+ if config.get_update_channel == constants.UPDATE_STABLE:
return self.updateFile
else:
- return 'https://api.github.com/repos/janeczku/calibre-web/zipball/master'
+ return _REPOSITORY_API_URL + '/zipball/master'
def _load_remote_data(self, repository_url):
status = {
diff --git a/cps/uploader.py b/cps/uploader.py
index 235c215c..17816d0a 100644
--- a/cps/uploader.py
+++ b/cps/uploader.py
@@ -17,13 +17,19 @@
# You should have received a copy of the GNU General Public License
# along with this program. If not, see .
-
-from tempfile import gettempdir
-import hashlib
+from __future__ import division, print_function, unicode_literals
import os
+import hashlib
+from tempfile import gettempdir
+
from flask_babel import gettext as _
-import comic
-from . import app
+
+from . import logger, comic
+from .constants import BookMeta
+
+
+log = logger.create()
+
try:
from lxml.etree import LXML_VERSION as lxmlversion
@@ -36,7 +42,7 @@ try:
from wand.exceptions import PolicyError
use_generic_pdf_cover = False
except (ImportError, RuntimeError) as e:
- app.logger.warning('cannot import Image, generating pdf covers for pdf uploads will not work: %s', e)
+ log.warning('cannot import Image, generating pdf covers for pdf uploads will not work: %s', e)
use_generic_pdf_cover = True
try:
@@ -44,29 +50,29 @@ try:
from PyPDF2 import __version__ as PyPdfVersion
use_pdf_meta = True
except ImportError as e:
- app.logger.warning('cannot import PyPDF2, extracting pdf metadata will not work: %s', e)
+ log.warning('cannot import PyPDF2, extracting pdf metadata will not work: %s', e)
use_pdf_meta = False
try:
- import epub
+ from . import epub
use_epub_meta = True
except ImportError as e:
- app.logger.warning('cannot import epub, extracting epub metadata will not work: %s', e)
+ log.warning('cannot import epub, extracting epub metadata will not work: %s', e)
use_epub_meta = False
try:
- import fb2
+ from . import fb2
use_fb2_meta = True
except ImportError as e:
- app.logger.warning('cannot import fb2, extracting fb2 metadata will not work: %s', e)
+ log.warning('cannot import fb2, extracting fb2 metadata will not work: %s', e)
use_fb2_meta = False
try:
from PIL import Image
from PIL import __version__ as PILversion
use_PIL = True
-except ImportError:
- app.logger.warning('cannot import Pillow, using png and webp images as cover will not work: %s', e)
+except ImportError as e:
+ log.warning('cannot import Pillow, using png and webp images as cover will not work: %s', e)
use_generic_pdf_cover = True
use_PIL = False
@@ -88,7 +94,7 @@ def process(tmp_file_path, original_file_name, original_file_extension):
meta = comic.get_comic_info(tmp_file_path, original_file_name, original_file_extension)
except Exception as ex:
- app.logger.warning('cannot parse metadata, using default: %s', ex)
+ log.warning('cannot parse metadata, using default: %s', ex)
if meta and meta.title.strip() and meta.author.strip():
return meta
@@ -192,10 +198,10 @@ def pdf_preview(tmp_file_path, tmp_dir):
img.save(filename=os.path.join(tmp_dir, cover_file_name))
return cover_file_name
except PolicyError as ex:
- app.logger.warning('Pdf extraction forbidden by Imagemagick policy: %s', ex)
+ log.warning('Pdf extraction forbidden by Imagemagick policy: %s', ex)
return None
except Exception as ex:
- app.logger.warning('Cannot extract cover image, using default: %s', ex)
+ log.warning('Cannot extract cover image, using default: %s', ex)
return None
diff --git a/cps/web.py b/cps/web.py
index f02c6006..5c2151f6 100644
--- a/cps/web.py
+++ b/cps/web.py
@@ -21,35 +21,39 @@
# You should have received a copy of the GNU General Public License
# along with this program. If not, see .
-from . import mimetypes, global_WorkerThread, searched_ids, lm, babel, ub, config, get_locale, language_table, app, db
-from .helper import common_filters, get_search_results, fill_indexpage, speaking_language, check_valid_domain, \
- order_authors, get_typeahead, render_task_status, json_serial, get_unique_other_books, 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
-from flask import render_template, request, redirect, send_from_directory, make_response, g, flash, abort, url_for
-from flask_login import login_user, logout_user, login_required, current_user
-from werkzeug.exceptions import default_exceptions
-from werkzeug.security import generate_password_hash, check_password_hash
-from werkzeug.datastructures import Headers
-from redirect import redirect_back
-from pagination import Pagination
+from __future__ import division, print_function, unicode_literals
+import os
+import base64
+import datetime
+import json
+import mimetypes
+
from babel import Locale as LC
from babel.dates import format_date
from babel.core import UnknownLocaleError
+from flask import Blueprint
+from flask import render_template, request, redirect, send_from_directory, make_response, g, flash, abort, url_for
from flask_babel import gettext as _
-from sqlalchemy.sql.expression import text, func, true, false, not_
+from flask_login import login_user, logout_user, login_required, current_user
from sqlalchemy.exc import IntegrityError
-import base64
-import os.path
-import json
-import datetime
-import isoLanguages
-from .gdriveutils import getFileFromEbooksFolder, do_gdrive_download
+from sqlalchemy.sql.expression import text, func, true, false, not_, and_
+from werkzeug.exceptions import default_exceptions
+from werkzeug.datastructures import Headers
+from werkzeug.security import generate_password_hash, check_password_hash
+from . import constants, logger, isoLanguages
+from . import global_WorkerThread, searched_ids, lm, babel, db, ub, config, get_locale, app, language_table
+from .gdriveutils import getFileFromEbooksFolder, do_gdrive_download
+from .helper import common_filters, get_search_results, fill_indexpage, speaking_language, check_valid_domain, \
+ order_authors, get_typeahead, render_task_status, json_serial, get_unique_other_books, 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
+from .pagination import Pagination
+from .redirect import redirect_back
feature_support = dict()
try:
- from oauth_bb import oauth_check, register_user_with_oauth, logout_oauth_user, get_oauth_status
+ from .oauth_bb import oauth_check, register_user_with_oauth, logout_oauth_user, get_oauth_status
feature_support['oauth'] = True
except ImportError:
feature_support['oauth'] = False
@@ -72,32 +76,17 @@ try:
except ImportError:
pass # We're not using Python 3
-try:
- import rarfile
- feature_support['rar'] = True
-except ImportError:
- feature_support['rar'] = False
+# try:
+# import rarfile
+# feature_support['rar'] = True
+# except ImportError:
+# feature_support['rar'] = False
try:
from natsort import natsorted as sort
except ImportError:
sort = sorted # Just use regular sort then, may cause issues with badly named pages in cbz/cbr files
-from flask import Blueprint
-
-# Global variables
-
-EXTENSIONS_AUDIO = {'mp3', 'm4a', 'm4b'}
-
-EXTENSIONS_UPLOAD = {'txt', 'pdf', 'epub', 'mobi', 'azw', 'azw3', 'cbr', 'cbz', 'cbt', 'djvu', 'prc', 'doc', 'docx',
- 'fb2', 'html', 'rtf', 'odt', 'mp3', 'm4a', 'm4b'}
-
-
-'''EXTENSIONS_READER = set(['txt', 'pdf', 'epub', 'zip', 'cbz', 'tar', 'cbt'] +
- (['rar','cbr'] if feature_support['rar'] else []))'''
-
-
-# with app.app_context():
# custom error page
def error_http(error):
@@ -116,6 +105,7 @@ for ex in default_exceptions:
web = Blueprint('web', __name__)
+log = logger.create()
# ################################### Login logic and rights management ###############################################
@@ -238,7 +228,7 @@ def edit_required(f):
# Returns the template for rendering and includes the instance name
def render_title_template(*args, **kwargs):
sidebar=ub.get_sidebar_config(kwargs)
- return render_template(instance=config.config_calibre_web_title, sidebar=sidebar, accept=EXTENSIONS_UPLOAD,
+ return render_template(instance=config.config_calibre_web_title, sidebar=sidebar, accept=constants.EXTENSIONS_UPLOAD,
*args, **kwargs)
@@ -272,9 +262,9 @@ def get_email_status_json():
@login_required
def bookmark(book_id, book_format):
bookmark_key = request.form["bookmark"]
- ub.session.query(ub.Bookmark).filter(ub.and_(ub.Bookmark.user_id == int(current_user.id),
- ub.Bookmark.book_id == book_id,
- ub.Bookmark.format == book_format)).delete()
+ ub.session.query(ub.Bookmark).filter(and_(ub.Bookmark.user_id == int(current_user.id),
+ ub.Bookmark.book_id == book_id,
+ ub.Bookmark.format == book_format)).delete()
if not bookmark_key:
ub.session.commit()
return "", 204
@@ -292,8 +282,8 @@ def bookmark(book_id, book_format):
@login_required
def toggle_read(book_id):
if not config.config_read_column:
- book = ub.session.query(ub.ReadBook).filter(ub.and_(ub.ReadBook.user_id == int(current_user.id),
- ub.ReadBook.book_id == book_id)).first()
+ book = ub.session.query(ub.ReadBook).filter(and_(ub.ReadBook.user_id == int(current_user.id),
+ ub.ReadBook.book_id == book_id)).first()
if book:
book.is_read = not book.is_read
else:
@@ -318,8 +308,7 @@ def toggle_read(book_id):
db.session.add(new_cc)
db.session.commit()
except KeyError:
- app.logger.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 ""
'''
@@ -342,10 +331,10 @@ def get_comic_book(book_id, book_format, page):
extract = lambda page: rf.read(names[page])
except:
# rarfile not valid
- app.logger.error('Unrar binary not found, or unable to decompress file ' + cbr_file)
+ log.error('Unrar binary not found, or unable to decompress file %s', cbr_file)
return "", 204
else:
- app.logger.info('Unrar is not supported please install python rarfile extension')
+ log.info('Unrar is not supported please install python rarfile extension')
# no support means return nothing
return "", 204
elif book_format in ("cbz", "zip"):
@@ -357,7 +346,7 @@ def get_comic_book(book_id, book_format, page):
names=sort(tf.getnames())
extract = lambda page: tf.extractfile(names[page]).read()
else:
- app.logger.error('unsupported comic format')
+ log.error('unsupported comic format')
return "", 204
if sys.version_info.major >= 3:
@@ -477,7 +466,7 @@ def books_list(data, sort, book_id, page):
order = [db.Books.timestamp]
if data == "rated":
- if current_user.check_visibility(ub.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),
order)
return render_title_template('index.html', random=random, entries=entries, pagination=pagination,
@@ -485,7 +474,7 @@ def books_list(data, sort, book_id, page):
else:
abort(404)
elif data == "discover":
- if current_user.check_visibility(ub.SIDEBAR_RANDOM):
+ if current_user.check_visibility(constants.SIDEBAR_RANDOM):
entries, __, pagination = fill_indexpage(page, db.Books, True, [func.randomblob(2)])
pagination = Pagination(1, config.config_books_per_page, config.config_books_per_page)
return render_title_template('discover.html', entries=entries, pagination=pagination,
@@ -517,7 +506,7 @@ def books_list(data, sort, book_id, page):
def render_hot_books(page):
- if current_user.check_visibility(ub.SIDEBAR_HOT):
+ if current_user.check_visibility(constants.SIDEBAR_HOT):
if current_user.show_detail_random():
random = db.session.query(db.Books).filter(common_filters()) \
.order_by(func.random()).limit(config.config_random_books)
@@ -564,7 +553,7 @@ def render_author_books(page, book_id, order):
other_books = get_unique_other_books(entries.all(), author_info.books)
except Exception:
# Skip goodreads, if site is down/inaccessible
- app.logger.error('Goodreads website is down/inaccessible')
+ log.error('Goodreads website is down/inaccessible')
return render_title_template('author.html', entries=entries, pagination=pagination,
title=name, author=author_info, other_books=other_books, page="author")
@@ -630,7 +619,7 @@ def render_category_books(page, book_id, order):
@web.route("/author")
@login_required_if_no_ano
def author_list():
- if current_user.check_visibility(ub.SIDEBAR_AUTHOR):
+ if current_user.check_visibility(constants.SIDEBAR_AUTHOR):
entries = db.session.query(db.Authors, func.count('books_authors_link.book').label('count'))\
.join(db.books_authors_link).join(db.Books).filter(common_filters())\
.group_by(text('books_authors_link.author')).order_by(db.Authors.sort).all()
@@ -648,7 +637,7 @@ def author_list():
@web.route("/publisher")
@login_required_if_no_ano
def publisher_list():
- if current_user.check_visibility(ub.SIDEBAR_PUBLISHER):
+ if current_user.check_visibility(constants.SIDEBAR_PUBLISHER):
entries = db.session.query(db.Publishers, func.count('books_publishers_link.book').label('count'))\
.join(db.books_publishers_link).join(db.Books).filter(common_filters())\
.group_by(text('books_publishers_link.publisher')).order_by(db.Publishers.sort).all()
@@ -664,7 +653,7 @@ def publisher_list():
@web.route("/series")
@login_required_if_no_ano
def series_list():
- if current_user.check_visibility(ub.SIDEBAR_SERIES):
+ if current_user.check_visibility(constants.SIDEBAR_SERIES):
entries = db.session.query(db.Series, func.count('books_series_link.book').label('count'))\
.join(db.books_series_link).join(db.Books).filter(common_filters())\
.group_by(text('books_series_link.series')).order_by(db.Series.sort).all()
@@ -680,7 +669,7 @@ def series_list():
@web.route("/ratings")
@login_required_if_no_ano
def ratings_list():
- if current_user.check_visibility(ub.SIDEBAR_RATING):
+ if current_user.check_visibility(constants.SIDEBAR_RATING):
entries = db.session.query(db.Ratings, func.count('books_ratings_link.book').label('count'),
(db.Ratings.rating/2).label('name'))\
.join(db.books_ratings_link).join(db.Books).filter(common_filters())\
@@ -694,7 +683,7 @@ def ratings_list():
@web.route("/formats")
@login_required_if_no_ano
def formats_list():
- if current_user.check_visibility(ub.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'))\
.join(db.Books).filter(common_filters())\
.group_by(db.Data.format).order_by(db.Data.format).all()
@@ -707,7 +696,7 @@ def formats_list():
@web.route("/language")
@login_required_if_no_ano
def language_overview():
- if current_user.check_visibility(ub.SIDEBAR_LANGUAGE):
+ if current_user.check_visibility(constants.SIDEBAR_LANGUAGE):
charlist = list()
if current_user.filter_language() == u"all":
languages = speaking_language()
@@ -753,7 +742,7 @@ def language(name, page):
@web.route("/category")
@login_required_if_no_ano
def category_list():
- if current_user.check_visibility(ub.SIDEBAR_CATEGORY):
+ if current_user.check_visibility(constants.SIDEBAR_CATEGORY):
entries = 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())\
.group_by(text('books_tags_link.tag')).all()
@@ -945,7 +934,7 @@ def render_read_books(page, are_read, as_xml=False, order=None):
.filter(db.cc_classes[config.config_read_column].value is True).all()
readBookIds = [x.book for x in readBooks]
except KeyError:
- app.logger.error(u"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)
readBookIds = []
if are_read:
@@ -988,7 +977,7 @@ def serve_book(book_id, book_format):
book = db.session.query(db.Books).filter(db.Books.id == book_id).first()
data = db.session.query(db.Data).filter(db.Data.book == book.id).filter(db.Data.format == book_format.upper())\
.first()
- app.logger.info('Serving book: %s', data.name)
+ log.info('Serving book: %s', data.name)
if config.config_use_google_drive:
headers = Headers()
try:
@@ -1058,7 +1047,7 @@ def register():
content.password = generate_password_hash(password)
content.role = config.config_default_role
content.sidebar_view = config.config_default_show
- content.mature_content = bool(config.config_default_show & ub.MATURE_CONTENT)
+ content.mature_content = bool(config.config_default_show & constants.MATURE_CONTENT)
try:
ub.session.add(content)
ub.session.commit()
@@ -1071,8 +1060,7 @@ def register():
return render_title_template('register.html', title=_(u"register"), page="register")
else:
flash(_(u"Your e-mail is not allowed to register"), category="error")
- app.logger.info('Registering failed for user "' + to_save['nickname'] + '" e-mail adress: ' +
- to_save["email"])
+ log.info('Registering failed for user "%s" e-mail adress: %s', to_save['nickname'], to_save["email"])
return render_title_template('register.html', title=_(u"register"), page="register")
flash(_(u"Confirmation e-mail was send to your e-mail account."), category="success")
return redirect(url_for('web.login'))
@@ -1104,10 +1092,10 @@ def login():
return redirect_back(url_for("web.index"))
except ldap.INVALID_CREDENTIALS:
ipAdress = request.headers.get('X-Forwarded-For', request.remote_addr)
- app.logger.info('LDAP Login failed for user "' + form['username'] + '" IP-adress: ' + ipAdress)
+ log.info('LDAP Login failed for user "%s" IP-adress: %s', form['username'], ipAdress)
flash(_(u"Wrong Username or Password"), category="error")
except ldap.SERVER_DOWN:
- app.logger.info('LDAP Login failed, LDAP Server down')
+ log.info('LDAP Login failed, LDAP Server down')
flash(_(u"Could not login. LDAP server down, please contact your administrator"), category="error")
else:
if user and check_password_hash(user.password, form['password']) and user.nickname is not "Guest":
@@ -1116,7 +1104,7 @@ def login():
return redirect_back(url_for("web.index"))
else:
ipAdress = request.headers.get('X-Forwarded-For', request.remote_addr)
- app.logger.info('Login failed for user "' + form['username'] + '" IP-adress: ' + ipAdress)
+ log.info('Login failed for user "%s" IP-adress: %s', form['username'], ipAdress)
flash(_(u"Wrong Username or Password"), category="error")
next_url = url_for('web.index')
@@ -1263,7 +1251,7 @@ def profile():
val += int(key[5:])
current_user.sidebar_view = val
if "Show_detail_random" in to_save:
- current_user.sidebar_view += ub.DETAIL_RANDOM
+ current_user.sidebar_view += constants.DETAIL_RANDOM
current_user.mature_content = "Show_mature_content" in to_save
@@ -1297,9 +1285,9 @@ def read_book(book_id, book_format):
# check if book has bookmark
bookmark = None
if current_user.is_authenticated:
- bookmark = ub.session.query(ub.Bookmark).filter(ub.and_(ub.Bookmark.user_id == int(current_user.id),
- ub.Bookmark.book_id == book_id,
- ub.Bookmark.format == book_format.upper())).first()
+ bookmark = ub.session.query(ub.Bookmark).filter(and_(ub.Bookmark.user_id == int(current_user.id),
+ ub.Bookmark.book_id == book_id,
+ ub.Bookmark.format == book_format.upper())).first()
if book_format.lower() == "epub":
return render_title_template('read.html', bookid=book_id, title=_(u"Read a Book"), bookmark=bookmark)
elif book_format.lower() == "pdf":
@@ -1350,15 +1338,14 @@ def show_book(book_id):
if not current_user.is_anonymous:
if not config.config_read_column:
matching_have_read_book = ub.session.query(ub.ReadBook).\
- filter(ub.and_(ub.ReadBook.user_id == int(current_user.id), ub.ReadBook.book_id == book_id)).all()
+ filter(and_(ub.ReadBook.user_id == int(current_user.id), ub.ReadBook.book_id == book_id)).all()
have_read = len(matching_have_read_book) > 0 and matching_have_read_book[0].is_read
else:
try:
matching_have_read_book = getattr(entries, 'custom_column_'+str(config.config_read_column))
have_read = len(matching_have_read_book) > 0 and matching_have_read_book[0].value
except KeyError:
- app.logger.error(
- u"Custom Column No.%d is not exisiting in calibre database" % config.config_read_column)
+ log.error("Custom Column No.%d is not exisiting in calibre database", config.config_read_column)
have_read = None
else:
@@ -1373,7 +1360,7 @@ def show_book(book_id):
audioentries = []
for media_format in entries.data:
- if media_format.format.lower() in EXTENSIONS_AUDIO:
+ if media_format.format.lower() in constants.EXTENSIONS_AUDIO:
audioentries.append(media_format.format.lower())
return render_title_template('detail.html', entry=entries, audioentries=audioentries, cc=cc,
diff --git a/cps/worker.py b/cps/worker.py
index 8da30885..3d5f058a 100644
--- a/cps/worker.py
+++ b/cps/worker.py
@@ -17,21 +17,15 @@
# You should have received a copy of the GNU General Public License
# along with this program. If not, see .
-from __future__ import print_function
-import smtplib
-import threading
-from datetime import datetime
-import logging
-import time
-import socket
+from __future__ import division, print_function, unicode_literals
import sys
import os
-from email.generator import Generator
-from . import config, db, app
-from flask_babel import gettext as _
import re
-from .gdriveutils import getFileFromEbooksFolder, updateGdriveCalibreFromLocal
-from .subproc_wrapper import process_open
+import smtplib
+import socket
+import time
+import threading
+from datetime import datetime
try:
from StringIO import StringIO
@@ -47,6 +41,14 @@ except ImportError:
from email import encoders
from email.utils import formatdate
from email.utils import make_msgid
+from email.generator import Generator
+from flask_babel import gettext as _
+
+from . import logger, config, db, gdriveutils
+from .subproc_wrapper import process_open
+
+
+log = logger.create()
chunksize = 8192
# task 'status' consts
@@ -70,7 +72,7 @@ def get_attachment(bookpath, filename):
"""Get file as MIMEBase message"""
calibrepath = config.config_calibre_dir
if config.config_use_google_drive:
- df = getFileFromEbooksFolder(bookpath, filename)
+ df = gdriveutils.getFileFromEbooksFolder(bookpath, filename)
if df:
datafile = os.path.join(calibrepath, bookpath, filename)
if not os.path.exists(os.path.join(calibrepath, bookpath)):
@@ -88,8 +90,8 @@ def get_attachment(bookpath, filename):
data = file_.read()
file_.close()
except IOError as e:
- app.logger.exception(e) # traceback.print_exc()
- app.logger.error(u'The requested file could not be read. Maybe wrong permissions?')
+ log.exception(e) # traceback.print_exc()
+ log.error(u'The requested file could not be read. Maybe wrong permissions?')
return None
attachment = MIMEBase('application', 'octet-stream')
@@ -114,7 +116,7 @@ class emailbase():
def send(self, strg):
"""Send `strg' to the server."""
- app.logger.debug('send:' + repr(strg[:300]))
+ log.debug('send: %r', strg[:300])
if hasattr(self, 'sock') and self.sock:
try:
if self.transferSize:
@@ -139,7 +141,7 @@ class emailbase():
raise smtplib.SMTPServerDisconnected('please run connect() first')
def _print_debug(self, *args):
- app.logger.debug(args)
+ log.debug(args)
def getTransferStatus(self):
if self.transferSize:
@@ -236,7 +238,7 @@ class WorkerThread(threading.Thread):
filename = self._convert_ebook_format()
if filename:
if config.config_use_google_drive:
- updateGdriveCalibreFromLocal()
+ gdriveutils.updateGdriveCalibreFromLocal()
if curr_task == TASK_CONVERT:
self.add_email(self.queue[self.current]['settings']['subject'], self.queue[self.current]['path'],
filename, self.queue[self.current]['settings'], self.queue[self.current]['kindle'],
@@ -254,14 +256,14 @@ class WorkerThread(threading.Thread):
# if it does - mark the conversion task as complete and return a success
# this will allow send to kindle workflow to continue to work
if os.path.isfile(file_path + format_new_ext):
- app.logger.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()
self.queue[self.current]['path'] = file_path
self.queue[self.current]['title'] = cur_book.title
self._handleSuccess()
return file_path + format_new_ext
else:
- app.logger.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.", bookid, format_new_ext)
# check if converter-executable is existing
if not os.path.exists(config.config_converterpath):
@@ -317,13 +319,13 @@ class WorkerThread(threading.Thread):
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())
- app.logger.debug("convert_kindlegen: " + nextline)
+ log.debug("convert_kindlegen: %s", nextline)
else:
while p.poll() is None:
nextline = p.stdout.readline()
if os.name == 'nt' and sys.version_info < (3, 0):
nextline = nextline.decode('windows-1252')
- app.logger.debug(nextline.strip('\r\n'))
+ log.debug(nextline.strip('\r\n'))
# parse progress string from calibre-converter
progress = re.search("(\d+)%\s.*", nextline)
if progress:
@@ -353,7 +355,7 @@ class WorkerThread(threading.Thread):
return file_path + format_new_ext
else:
error_message = format_new_ext.upper() + ' format not found on disk'
- app.logger.info("ebook converter failed with error while converting book")
+ log.info("ebook converter failed with error while converting book")
if not error_message:
error_message = 'Ebook converter failed with unknown error'
self._handleError(error_message)
@@ -449,7 +451,7 @@ class WorkerThread(threading.Thread):
# _print_debug function
if sys.version_info < (3, 0):
org_smtpstderr = smtplib.stderr
- smtplib.stderr = StderrLogger()
+ smtplib.stderr = logger.StderrLogger('worker.smtp')
if use_ssl == 2:
self.asyncSMTP = email_SSL(obj['settings']["mail_server"], obj['settings']["mail_port"], timeout)
@@ -457,9 +459,7 @@ class WorkerThread(threading.Thread):
self.asyncSMTP = email(obj['settings']["mail_server"], obj['settings']["mail_port"], timeout)
# link to logginglevel
- if config.config_log_level != logging.DEBUG:
- self.asyncSMTP.set_debuglevel(0)
- else:
+ if logger.is_debug_enabled():
self.asyncSMTP.set_debuglevel(1)
if use_ssl == 1:
self.asyncSMTP.starttls()
@@ -501,7 +501,7 @@ class WorkerThread(threading.Thread):
return retVal
def _handleError(self, error_message):
- app.logger.error(error_message)
+ log.error(error_message)
self.UIqueue[self.current]['stat'] = STAT_FAIL
self.UIqueue[self.current]['progress'] = "100 %"
self.UIqueue[self.current]['runtime'] = self._formatRuntime(
@@ -513,22 +513,3 @@ class WorkerThread(threading.Thread):
self.UIqueue[self.current]['progress'] = "100 %"
self.UIqueue[self.current]['runtime'] = self._formatRuntime(
datetime.now() - self.queue[self.current]['starttime'])
-
-
-# Enable logging of smtp lib debug output
-class StderrLogger(object):
-
- buffer = ''
-
- def __init__(self):
- self.logger = app.logger
-
- def write(self, message):
- try:
- if message == '\n':
- self.logger.debug(self.buffer.replace("\n","\\n"))
- self.buffer = ''
- else:
- self.buffer += message
- except:
- self.logger.debug("Logging Error")