Merge remote-tracking branch 'constants/Develop-logging-cleanup' into Develop
This commit is contained in:
commit
50973ffb72
10
cps.py
10
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 <http://www.gnu.org/licenses/>.
|
||||
|
||||
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
|
||||
|
|
|
@ -20,29 +20,28 @@
|
|||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
__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']
|
||||
|
|
37
cps/about.py
37
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 <http://www.gnu.org/licenses/>.
|
||||
|
||||
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,
|
||||
|
|
198
cps/admin.py
198
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 <http://www.gnu.org/licenses/>.
|
||||
|
||||
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
|
||||
|
||||
|
|
|
@ -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, "")
|
||||
|
|
21
cps/cli.py
21
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 <http://www.gnu.org/licenses/>.
|
||||
|
||||
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
|
||||
|
|
12
cps/comic.py
12
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 <http://www.gnu.org/licenses/>.
|
||||
|
||||
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
|
||||
|
|
|
@ -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,101 @@
|
|||
# You should have received a copy of the GNU General Public License
|
||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
from __future__ import division, print_function, unicode_literals
|
||||
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')
|
||||
|
||||
STABLE_VERSION = {'version': '0.6.4 Beta'}
|
||||
|
||||
# clean-up the module namespace
|
||||
del sys, os, namedtuple
|
||||
|
||||
|
|
|
@ -17,14 +17,14 @@
|
|||
# You should have received a copy of the GNU General Public License
|
||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
|
||||
from __future__ import division, print_function, unicode_literals
|
||||
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():
|
||||
|
|
20
cps/db.py
20
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 <http://www.gnu.org/licenses/>.
|
||||
|
||||
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']
|
||||
|
@ -385,7 +389,7 @@ def setup_db():
|
|||
ccdict = {'__tablename__': 'custom_column_' + str(row.id),
|
||||
'id': Column(Integer, primary_key=True),
|
||||
'value': Column(String)}
|
||||
cc_classes[row.id] = type('Custom_Column_' + str(row.id), (Base,), ccdict)
|
||||
cc_classes[row.id] = type(str('Custom_Column_' + str(row.id)), (Base,), ccdict)
|
||||
|
||||
for cc_id in cc_ids:
|
||||
if (cc_id[1] == 'bool') or (cc_id[1] == 'int'):
|
||||
|
|
|
@ -21,28 +21,25 @@
|
|||
# You should have received a copy of the GNU General Public License
|
||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
# 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)
|
||||
|
||||
|
|
|
@ -17,11 +17,13 @@
|
|||
# You should have received a copy of the GNU General Public License
|
||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
from __future__ import division, print_function, unicode_literals
|
||||
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'),
|
||||
|
|
|
@ -17,7 +17,9 @@
|
|||
# You should have received a copy of the GNU General Public License
|
||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
from __future__ import division, print_function, unicode_literals
|
||||
from lxml import etree
|
||||
|
||||
from .constants import BookMeta
|
||||
|
||||
|
||||
|
|
|
@ -20,26 +20,31 @@
|
|||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
from __future__ import division, print_function, unicode_literals
|
||||
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 ''
|
||||
|
|
|
@ -17,23 +17,35 @@
|
|||
# You should have received a copy of the GNU General Public License
|
||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
from __future__ import division, print_function, unicode_literals
|
||||
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)
|
||||
|
|
130
cps/helper.py
130
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 <http://www.gnu.org/licenses/>.
|
||||
|
||||
|
||||
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
|
||||
|
|
|
@ -17,6 +17,9 @@
|
|||
# You should have received a copy of the GNU General Public License
|
||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
from __future__ import division, print_function, unicode_literals
|
||||
|
||||
|
||||
try:
|
||||
from iso639 import languages, __version__
|
||||
get = languages.get
|
||||
|
|
|
@ -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')
|
||||
|
|
125
cps/logger.py
Normal file
125
cps/logger.py
Normal file
|
@ -0,0 +1,125 @@
|
|||
# -*- 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 <http://www.gnu.org/licenses/>.
|
||||
|
||||
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} %(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")
|
|
@ -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
|
||||
|
|
|
@ -21,30 +21,34 @@
|
|||
# You should have received a copy of the GNU General Public License
|
||||
# along with this program. If not, see <http://www.gnu.org/licenses/>
|
||||
|
||||
from 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()
|
||||
|
||||
|
||||
|
@ -133,9 +137,9 @@ if ub.oauth_support:
|
|||
google_blueprint.backend = OAuthBackend(ub.OAuth, ub.session, user=current_user, user_required=True)
|
||||
|
||||
|
||||
if config.config_login_type == ub.LOGIN_OAUTH_GITHUB:
|
||||
if config.config_login_type == constants.LOGIN_OAUTH_GITHUB:
|
||||
register_oauth_blueprint(github_blueprint, 'GitHub')
|
||||
if config.config_login_type == ub.LOGIN_OAUTH_GOOGLE:
|
||||
if config.config_login_type == constants.LOGIN_OAUTH_GOOGLE:
|
||||
register_oauth_blueprint(google_blueprint, 'Google')
|
||||
|
||||
|
||||
|
@ -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'))
|
||||
|
||||
|
|
36
cps/opds.py
36
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 <http://www.gnu.org/licenses/>.
|
||||
|
||||
# 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:
|
||||
|
|
|
@ -21,6 +21,7 @@
|
|||
# You should have received a copy of the GNU General Public License
|
||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
from __future__ import division, print_function, unicode_literals
|
||||
from math import ceil
|
||||
|
||||
|
||||
|
|
|
@ -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
|
||||
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
129
cps/server.py
129
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 <http://www.gnu.org/licenses/>.
|
||||
|
||||
|
||||
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)
|
||||
|
||||
|
|
60
cps/shelf.py
60
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 <http://www.gnu.org/licenses/>.
|
||||
|
||||
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/<int:shelf_id>/<int:book_id>")
|
||||
@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/<int:shelf_id>")
|
||||
|
@ -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) \
|
||||
|
|
|
@ -1,3 +1,6 @@
|
|||
|
||||
.tooltip.bottom .tooltip-inner{font-size:13px;font-family:Open Sans Semibold,Helvetica Neue,Helvetica,Arial,sans-serif;-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale;padding:3px 10px;border-radius:4px;background-color:#fff;-webkit-box-shadow:0 4px 10px 0 rgba(0,0,0,.35);box-shadow:0 4px 10px 0 rgba(0,0,0,.35);opacity:1;white-space:nowrap;margin-top:-16px!important;line-height:1.71428571;color:#ddd}
|
||||
|
||||
@font-face {
|
||||
font-family: 'Grand Hotel';
|
||||
font-style: normal;
|
||||
|
|
|
@ -16,9 +16,11 @@
|
|||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
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):
|
||||
|
|
|
@ -54,16 +54,16 @@
|
|||
<div class="discover load-more">
|
||||
<h2 class="{{title}}">{{_(title)}}</h2>
|
||||
<div class="filterheader hidden-xs hidden-sm">
|
||||
<a id="new" class="btn btn-primary" href="{{url_for('web.books_list', data=page, sort='new')}}"><span class="glyphicon glyphicon-sort-by-order"></span></a>
|
||||
<a id="old" class="btn btn-primary" href="{{url_for('web.books_list', data=page, sort='old')}}"><span class="glyphicon glyphicon-sort-by-order-alt"></span></a>
|
||||
<a data-toggle="tooltip" id="new" class="btn btn-primary" href="{{url_for('web.books_list', data=page, sort='new')}}"><span class="glyphicon glyphicon-sort-by-attributes"></span></a>
|
||||
<a id="old" class="btn btn-primary" href="{{url_for('web.books_list', data=page, sort='old')}}"><span class="glyphicon glyphicon-sort-by-attributes-alt"></span></a>
|
||||
<a id="asc" class="btn btn-primary" href="{{url_for('web.books_list', data=page, sort='abc')}}"><span class="glyphicon glyphicon-font"></span><span class="glyphicon glyphicon-sort-by-alphabet"></span></a>
|
||||
<a id="desc" class="btn btn-primary" href="{{url_for('web.books_list', data=page, sort='zyx')}}"><span class="glyphicon glyphicon-font"></span><span class="glyphicon glyphicon-sort-by-alphabet-alt"></span></a>
|
||||
<a id="pub_new" class="btn btn-primary" href="{{url_for('web.books_list', data=page, sort='pubnew')}}"><span class="glyphicon glyphicon-calendar"></span><span class="glyphicon glyphicon-sort-by-order"></span></a>
|
||||
<a id="pub_old" class="btn btn-primary" href="{{url_for('web.books_list', data=page, sort='pubold')}}"><span class="glyphicon glyphicon-calendar"></span><span class="glyphicon glyphicon-sort-by-order-alt"></span></a>
|
||||
</div>
|
||||
<div class="btn-group character">
|
||||
<a id="no_shelf" class="btn btn-primary" href="{{url_for('web.books_list', data=page, sort='pubold')}}"><span class="glyphicon glyphicon-list"></span><b>{{_('Group by series')}}</b></a>
|
||||
<div class="btn-group character">
|
||||
<a id="no_shelf" class="btn btn-primary" href="{{url_for('web.books_list', data=page, sort='pubold')}}"><span class="glyphicon glyphicon-list"></span> <b>{{_('Group by series')}}</b></a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row">
|
||||
{% if entries[0] %}
|
||||
|
|
251
cps/ub.py
251
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 <http://www.gnu.org/licenses/>.
|
||||
|
||||
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 '<User %r>' % 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 '<Shelf %r>' % self.name
|
||||
return '<Shelf %d:%r>' % (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:
|
||||
|
|
|
@ -17,22 +17,28 @@
|
|||
# You should have received a copy of the GNU General Public License
|
||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
|
||||
from . 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 +59,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 +73,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 +159,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 +176,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 +225,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
|
||||
|
@ -243,12 +249,12 @@ class Updater(threading.Thread):
|
|||
|
||||
@classmethod
|
||||
def _stable_version_info(self):
|
||||
return {'version': '0.6.4 Beta'} # Current version
|
||||
return constants.STABLE_VERSION # Current version
|
||||
|
||||
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 +354,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 +440,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 = {
|
||||
|
|
|
@ -17,13 +17,19 @@
|
|||
# You should have received a copy of the GNU General Public License
|
||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
|
||||
from 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
|
||||
|
||||
|
||||
|
|
143
cps/web.py
143
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 <http://www.gnu.org/licenses/>.
|
||||
|
||||
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,
|
||||
|
|
|
@ -17,21 +17,15 @@
|
|||
# You should have received a copy of the GNU General Public License
|
||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
from __future__ import 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")
|
||||
|
|
Loading…
Reference in New Issue
Block a user