Merge remote-tracking branch 'upstream/Develop' into Develop

Conflicts:
	cps/web.py
pull/956/head
Krakinou 5 years ago
commit 11c8b47c39

@ -23,13 +23,17 @@ import os
# Insert local directories into path # Insert local directories into path
sys.path.append(os.path.dirname(os.path.abspath(__file__))) if sys.version_info < (3, 0):
sys.path.append(os.path.join(os.path.dirname(os.path.abspath(__file__)), 'vendor')) sys.path.append(os.path.dirname(os.path.abspath(__file__.decode('utf-8'))))
sys.path.append(os.path.join(os.path.dirname(os.path.abspath(__file__.decode('utf-8'))), 'vendor'))
else:
sys.path.append(os.path.dirname(os.path.abspath(__file__)))
sys.path.append(os.path.join(os.path.dirname(os.path.abspath(__file__)), 'vendor'))
from cps import create_app from cps import create_app
from cps import web_server
from cps.opds import opds from cps.opds import opds
from cps import Server
from cps.web import web from cps.web import web
from cps.jinjia import jinjia from cps.jinjia import jinjia
from cps.about import about from cps.about import about
@ -56,7 +60,8 @@ def main():
app.register_blueprint(editbook) app.register_blueprint(editbook)
if oauth_available: if oauth_available:
app.register_blueprint(oauth) app.register_blueprint(oauth)
Server.startServer() success = web_server.start()
sys.exit(0 if success else 1)
if __name__ == '__main__': if __name__ == '__main__':

@ -84,11 +84,11 @@ searched_ids = {}
from .worker import WorkerThread from .worker import WorkerThread
global_WorkerThread = WorkerThread() global_WorkerThread = WorkerThread()
from .server import server from .server import WebServer
Server = server() web_server = WebServer()
from .ldap import Ldap from .ldap_login import Ldap
ldap = Ldap() ldap1 = Ldap()
babel = Babel() babel = Babel()
@ -97,16 +97,22 @@ log = logger.create()
def create_app(): def create_app():
app.wsgi_app = ReverseProxied(app.wsgi_app) app.wsgi_app = ReverseProxied(app.wsgi_app)
# For python2 convert path to unicode
if sys.version_info < (3, 0):
app.static_folder = app.static_folder.decode('utf-8')
app.root_path = app.root_path.decode('utf-8')
app.instance_path = app.instance_path .decode('utf-8')
cache_buster.init_cache_busting(app) cache_buster.init_cache_busting(app)
log.info('Starting Calibre Web...') log.info('Starting Calibre Web...')
Principal(app) Principal(app)
lm.init_app(app) lm.init_app(app)
app.secret_key = os.getenv('SECRET_KEY', 'A0Zr98j/3yX R~XHH!jmN]LWX/,?RT') app.secret_key = os.getenv('SECRET_KEY', 'A0Zr98j/3yX R~XHH!jmN]LWX/,?RT')
Server.init_app(app) web_server.init_app(app, config)
db.setup_db() db.setup_db()
babel.init_app(app) babel.init_app(app)
ldap.init_app(app) ldap1.init_app(app)
global_WorkerThread.start() global_WorkerThread.start()
return app return app

@ -41,8 +41,9 @@ from jinja2 import __version__ as jinja2Version
from pytz import __version__ as pytzVersion from pytz import __version__ as pytzVersion
from sqlalchemy import __version__ as sqlalchemyVersion from sqlalchemy import __version__ as sqlalchemyVersion
from . import db, converter, Server, uploader from . import db, converter, uploader
from .isoLanguages import __version__ as iso639Version from .isoLanguages import __version__ as iso639Version
from .server import VERSION as serverVersion
from .web import render_title_template from .web import render_title_template
@ -71,7 +72,7 @@ def stats():
versions['pySqlite'] = 'v' + db.engine.dialect.dbapi.version versions['pySqlite'] = 'v' + db.engine.dialect.dbapi.version
versions['Sqlite'] = 'v' + db.engine.dialect.dbapi.sqlite_version versions['Sqlite'] = 'v' + db.engine.dialect.dbapi.sqlite_version
versions.update(converter.versioncheck()) versions.update(converter.versioncheck())
versions.update(Server.getNameVersion()) versions.update(serverVersion)
versions['Python'] = sys.version versions['Python'] = sys.version
return render_title_template('stats.html', bookcounter=counter, authorcounter=authors, versions=versions, return render_title_template('stats.html', bookcounter=counter, authorcounter=authors, versions=versions,
categorycounter=categorys, seriecounter=series, title=_(u"Statistics"), page="stat") categorycounter=categorys, seriecounter=series, title=_(u"Statistics"), page="stat")

@ -41,15 +41,15 @@ from sqlalchemy import and_
from sqlalchemy.exc import IntegrityError from sqlalchemy.exc import IntegrityError
from werkzeug.security import generate_password_hash from werkzeug.security import generate_password_hash
from . import constants, logger, ldap from . import constants, logger, ldap1
from . import db, ub, Server, get_locale, config, updater_thread, babel, gdriveutils from . import db, ub, web_server, get_locale, config, updater_thread, babel, gdriveutils
from .helper import speaking_language, check_valid_domain, check_unrar, send_test_mail, generate_random_password, \ from .helper import speaking_language, check_valid_domain, check_unrar, send_test_mail, generate_random_password, \
send_registration_mail send_registration_mail
from .gdriveutils import is_gdrive_ready, gdrive_support, downloadFile, deleteDatabaseOnChange, listRootFolders 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 from .web import admin_required, render_title_template, before_request, unconfigured, login_required_if_no_ano
feature_support = dict() feature_support = dict()
feature_support['ldap'] = ldap.ldap_supported() feature_support['ldap'] = ldap1.ldap_supported()
try: try:
from goodreads.client import GoodreadsClient from goodreads.client import GoodreadsClient
@ -63,12 +63,6 @@ except ImportError:
# except ImportError: # except ImportError:
# feature_support['rar'] = False # feature_support['rar'] = False
'''try:
import ldap
feature_support['ldap'] = True
except ImportError:
feature_support['ldap'] = False'''
try: try:
from oauth_bb import oauth_check from oauth_bb import oauth_check
feature_support['oauth'] = True feature_support['oauth'] = True
@ -103,12 +97,10 @@ def shutdown():
showtext = {} showtext = {}
if task == 0: if task == 0:
showtext['text'] = _(u'Server restarted, please reload page') showtext['text'] = _(u'Server restarted, please reload page')
Server.setRestartTyp(True)
else: else:
showtext['text'] = _(u'Performing shutdown of server, please close window') showtext['text'] = _(u'Performing shutdown of server, please close window')
Server.setRestartTyp(False)
# stop gevent/tornado server # stop gevent/tornado server
Server.stopServer() web_server.stop(task == 0)
return json.dumps(showtext) return json.dumps(showtext)
else: else:
if task == 2: if task == 2:
@ -221,8 +213,7 @@ def view_configuration():
# ub.session.close() # ub.session.close()
# ub.engine.dispose() # ub.engine.dispose()
# stop Server # stop Server
Server.setRestartTyp(True) web_server.stop(True)
Server.stopServer()
log.info('Reboot required, restarting') log.info('Reboot required, restarting')
readColumn = db.session.query(db.Custom_Columns)\ readColumn = db.session.query(db.Custom_Columns)\
.filter(and_(db.Custom_Columns.datatype == 'bool',db.Custom_Columns.mark_for_delete == 0)).all() .filter(and_(db.Custom_Columns.datatype == 'bool',db.Custom_Columns.mark_for_delete == 0)).all()
@ -403,14 +394,14 @@ def configuration_helper(origin):
flash(_(u'Please enter a LDAP provider, port, DN and user object identifier'), category="error") flash(_(u'Please enter a LDAP provider, port, DN and user object identifier'), category="error")
return render_title_template("config_edit.html", content=config, origin=origin, return render_title_template("config_edit.html", content=config, origin=origin,
gdrive=gdriveutils.gdrive_support, gdriveError=gdriveError, gdrive=gdriveutils.gdrive_support, gdriveError=gdriveError,
goodreads=goodreads_support, title=_(u"Basic Configuration"), feature_support=feature_support, title=_(u"Basic Configuration"),
page="config") page="config")
elif not to_save["config_ldap_serv_username"] or not to_save["config_ldap_serv_password"]: elif not to_save["config_ldap_serv_username"] or not to_save["config_ldap_serv_password"]:
ub.session.commit() ub.session.commit()
flash(_(u'Please enter a LDAP service account and password'), category="error") flash(_(u'Please enter a LDAP service account and password'), category="error")
return render_title_template("config_edit.html", content=config, origin=origin, return render_title_template("config_edit.html", content=config, origin=origin,
gdrive=gdriveutils.gdrive_support, gdriveError=gdriveError, gdrive=gdriveutils.gdrive_support, gdriveError=gdriveError,
goodreads=goodreads_support, title=_(u"Basic Configuration"), feature_support=feature_support, title=_(u"Basic Configuration"),
page="config") page="config")
else: else:
content.config_login_type = 1 content.config_login_type = 1
@ -444,7 +435,7 @@ def configuration_helper(origin):
flash(_(u'Certfile location is not valid, please enter correct path'), category="error") flash(_(u'Certfile location is not valid, please enter correct path'), category="error")
return render_title_template("config_edit.html", content=config, origin=origin, return render_title_template("config_edit.html", content=config, origin=origin,
gdrive=gdriveutils.gdrive_support, gdriveError=gdriveError, gdrive=gdriveutils.gdrive_support, gdriveError=gdriveError,
goodreads=goodreads_support, title=_(u"Basic Configuration"), feature_support=feature_support, title=_(u"Basic Configuration"),
page="config") page="config")
# Remote login configuration # Remote login configuration
@ -556,12 +547,11 @@ def configuration_helper(origin):
title=_(u"Basic Configuration"), page="config") title=_(u"Basic Configuration"), page="config")
if reboot_required: if reboot_required:
# stop Server # stop Server
Server.setRestartTyp(True) web_server.stop(True)
Server.stopServer()
log.info('Reboot required, restarting') log.info('Reboot required, restarting')
if origin: if origin:
success = True success = True
if is_gdrive_ready() and feature_support['gdrive'] is True: # and config.config_use_google_drive == True: if is_gdrive_ready() and feature_support['gdrive'] is True and config.config_use_google_drive == True:
gdrivefolders = listRootFolders() gdrivefolders = listRootFolders()
else: else:
gdrivefolders = list() gdrivefolders = list()
@ -582,9 +572,6 @@ def new_user():
to_save = request.form.to_dict() to_save = request.form.to_dict()
content.default_language = to_save["default_language"] content.default_language = to_save["default_language"]
content.mature_content = "Show_mature_content" in to_save content.mature_content = "Show_mature_content" in to_save
dat = datetime.strptime("1.1.2019", "%d.%m.%Y")
content.id = int(time.time()*100)
# val= int(uuid.uuid4())
if "locale" in to_save: if "locale" in to_save:
content.locale = to_save["locale"] content.locale = to_save["locale"]
@ -806,19 +793,27 @@ def reset_password(user_id):
@login_required @login_required
@admin_required @admin_required
def view_logfile(): def view_logfile():
perpage_p = {0:"30",1:"40",2:"100"} logfiles = {}
for key, value in perpage_p.items(): logfiles[0] = logger.get_logfile(config.config_logfile)
print(key) logfiles[1] = logger.get_accesslogfile(config.config_access_logfile)
print(value) return render_title_template("logviewer.html",title=_(u"Logfile viewer"), accesslog_enable=config.config_access_log,
return render_title_template("logviewer.html",title=_(u"Logfile viewer"), perpage_p=perpage_p, perpage = 30, logfiles=logfiles, page="logfile")
page="logfile")
@admi.route("/ajax/accesslog") @admi.route("/ajax/log/<int:logtype>")
@login_required @login_required
@admin_required @admin_required
def send_logfile(): def send_logfile(logtype):
return send_from_directory(constants.BASE_DIR,"access.log") if logtype == 1:
logfile = logger.get_accesslogfile(config.config_access_logfile)
return send_from_directory(os.path.dirname(logfile),
os.path.basename(logfile))
if logtype == 0:
logfile = logger.get_logfile(config.config_logfile)
return send_from_directory(os.path.dirname(logfile),
os.path.basename(logfile))
else:
return ""
@admi.route("/get_update_status", methods=['GET']) @admi.route("/get_update_status", methods=['GET'])

@ -82,6 +82,18 @@ parser.add_argument('-i', metavar='ip-adress', help='Server IP-Adress to listen'
parser.add_argument('-s', metavar='user:pass', help='Sets specific username to new password') parser.add_argument('-s', metavar='user:pass', help='Sets specific username to new password')
args = parser.parse_args() args = parser.parse_args()
if sys.version_info < (3, 0):
if args.p:
args.p = args.p.decode('utf-8')
if args.g:
args.g = args.g.decode('utf-8')
if args.k:
args.k = args.k.decode('utf-8')
if args.c:
args.c = args.c.decode('utf-8')
if args.s:
args.s = args.s.decode('utf-8')
settingspath = args.p or os.path.join(_CONFIG_DIR, "app.db") settingspath = args.p or os.path.join(_CONFIG_DIR, "app.db")
gdpath = args.g or os.path.join(_CONFIG_DIR, "gdrive.db") gdpath = args.g or os.path.join(_CONFIG_DIR, "gdrive.db")

@ -24,7 +24,12 @@ from collections import namedtuple
# Base dir is parent of current file, necessary if called from different folder # Base dir is parent of current file, necessary if called from different folder
BASE_DIR = os.path.abspath(os.path.join(os.path.dirname(os.path.abspath(__file__)),os.pardir)) if sys.version_info < (3, 0):
BASE_DIR = os.path.abspath(os.path.join(
os.path.dirname(os.path.abspath(__file__)),os.pardir)).decode('utf-8')
else:
BASE_DIR = os.path.abspath(os.path.join(
os.path.dirname(os.path.abspath(__file__)),os.pardir))
STATIC_DIR = os.path.join(BASE_DIR, 'cps', 'static') STATIC_DIR = os.path.join(BASE_DIR, 'cps', 'static')
TEMPLATES_DIR = os.path.join(BASE_DIR, 'cps', 'templates') TEMPLATES_DIR = os.path.join(BASE_DIR, 'cps', 'templates')
TRANSLATIONS_DIR = os.path.join(BASE_DIR, 'cps', 'translations') TRANSLATIONS_DIR = os.path.join(BASE_DIR, 'cps', 'translations')

@ -342,7 +342,7 @@ def setup_db():
try: try:
if not os.path.exists(dbpath): if not os.path.exists(dbpath):
raise raise
engine = create_engine('sqlite:///' + dbpath, engine = create_engine('sqlite:///{0}'.format(dbpath),
echo=False, echo=False,
isolation_level="SERIALIZABLE", isolation_level="SERIALIZABLE",
connect_args={'check_same_thread': False}) connect_args={'check_same_thread': False})

@ -16,10 +16,11 @@
# You should have received a copy of the GNU General Public License # You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>. # along with this program. If not, see <http://www.gnu.org/licenses/>.
from __future__ import division, print_function, unicode_literals
import base64 import base64
try: try:
from flask_simpleldap import LDAP, LDAPException from flask_simpleldap import LDAP # , LDAPException
ldap_support = True ldap_support = True
except ImportError: except ImportError:
ldap_support = False ldap_support = False

@ -25,6 +25,7 @@ from logging.handlers import RotatingFileHandler
from .constants import BASE_DIR as _BASE_DIR from .constants import BASE_DIR as _BASE_DIR
ACCESS_FORMATTER_GEVENT = Formatter("%(message)s") ACCESS_FORMATTER_GEVENT = Formatter("%(message)s")
ACCESS_FORMATTER_TORNADO = Formatter("[%(asctime)s] %(message)s") ACCESS_FORMATTER_TORNADO = Formatter("[%(asctime)s] %(message)s")
@ -33,7 +34,6 @@ DEFAULT_LOG_LEVEL = logging.INFO
DEFAULT_LOG_FILE = os.path.join(_BASE_DIR, "calibre-web.log") DEFAULT_LOG_FILE = os.path.join(_BASE_DIR, "calibre-web.log")
DEFAULT_ACCESS_LOG = os.path.join(_BASE_DIR, "access.log") DEFAULT_ACCESS_LOG = os.path.join(_BASE_DIR, "access.log")
LOG_TO_STDERR = '/dev/stderr' LOG_TO_STDERR = '/dev/stderr'
DEFAULT_ACCESS_LEVEL= logging.INFO
logging.addLevelName(logging.WARNING, "WARN") logging.addLevelName(logging.WARNING, "WARN")
logging.addLevelName(logging.CRITICAL, "CRIT") logging.addLevelName(logging.CRITICAL, "CRIT")
@ -73,35 +73,34 @@ def is_valid_logfile(file_path):
return (not log_dir) or os.path.isdir(log_dir) return (not log_dir) or os.path.isdir(log_dir)
def setup(log_file, log_level=None, logger=None): def _absolute_log_file(log_file, default_log_file):
if logger != "access" and logger != "tornado.access":
formatter = FORMATTER
default_file = DEFAULT_LOG_FILE
else:
if logger == "tornado.access":
formatter = ACCESS_FORMATTER_TORNADO
else:
formatter = ACCESS_FORMATTER_GEVENT
default_file = DEFAULT_ACCESS_LOG
if log_file: if log_file:
if not os.path.dirname(log_file): if not os.path.dirname(log_file):
log_file = os.path.join(_BASE_DIR, log_file) log_file = os.path.join(_BASE_DIR, log_file)
log_file = os.path.abspath(log_file) return os.path.abspath(log_file)
else:
log_file = LOG_TO_STDERR
# log_file = default_file
# print ('%r -- %r' % (log_level, log_file)) return default_log_file
if logger != "access" and logger != "tornado.access":
r = logging.root
else: def get_logfile(log_file):
r = logging.getLogger(logger) return _absolute_log_file(log_file, DEFAULT_LOG_FILE)
r.propagate = False
def get_accesslogfile(log_file):
return _absolute_log_file(log_file, DEFAULT_ACCESS_LOG)
def setup(log_file, log_level=None):
'''
Configure the logging output.
May be called multiple times.
'''
log_file = _absolute_log_file(log_file, DEFAULT_LOG_FILE)
r = logging.root
r.setLevel(log_level or DEFAULT_LOG_LEVEL) r.setLevel(log_level or DEFAULT_LOG_LEVEL)
previous_handler = r.handlers[0] if r.handlers else None previous_handler = r.handlers[0] if r.handlers else None
# print ('previous %r' % previous_handler)
if previous_handler: if previous_handler:
# if the log_file has not changed, don't create a new handler # if the log_file has not changed, don't create a new handler
if getattr(previous_handler, 'baseFilename', None) == log_file: if getattr(previous_handler, 'baseFilename', None) == log_file:
@ -115,16 +114,32 @@ def setup(log_file, log_level=None, logger=None):
try: try:
file_handler = RotatingFileHandler(log_file, maxBytes=50000, backupCount=2) file_handler = RotatingFileHandler(log_file, maxBytes=50000, backupCount=2)
except IOError: except IOError:
if log_file == default_file: if log_file == DEFAULT_LOG_FILE:
raise raise
file_handler = RotatingFileHandler(default_file, maxBytes=50000, backupCount=2) file_handler = RotatingFileHandler(DEFAULT_LOG_FILE, maxBytes=50000, backupCount=2)
file_handler.setFormatter(formatter) file_handler.setFormatter(FORMATTER)
for h in r.handlers: for h in r.handlers:
r.removeHandler(h) r.removeHandler(h)
h.close() h.close()
r.addHandler(file_handler) r.addHandler(file_handler)
# print ('new handler %r' % file_handler)
def create_access_log(log_file, log_name, formatter):
'''
One-time configuration for the web server's access log.
'''
log_file = _absolute_log_file(log_file, DEFAULT_ACCESS_LOG)
logging.debug("access log: %s", log_file)
access_log = logging.getLogger(log_name)
access_log.propagate = False
access_log.setLevel(logging.INFO)
file_handler = RotatingFileHandler(log_file, maxBytes=50000, backupCount=2)
file_handler.setFormatter(formatter)
access_log.addHandler(file_handler)
return access_log
# Enable logging of smtp lib debug output # Enable logging of smtp lib debug output

@ -88,7 +88,7 @@ def register_user_with_oauth(user=None):
if len(all_oauth.keys()) == 0: if len(all_oauth.keys()) == 0:
return return
if user is None: if user is None:
flash(_(u"Register with %s" % ", ".join(list(all_oauth.values()))), category="success") flash(_(u"Register with %(provider)s", provider=", ".join(list(all_oauth.values()))), category="success")
else: else:
for oauth in all_oauth.keys(): for oauth in all_oauth.keys():
# Find this OAuth token in the database, or create it # Find this OAuth token in the database, or create it

@ -31,7 +31,7 @@ from flask_login import current_user
from sqlalchemy.sql.expression import func, text, or_, and_ from sqlalchemy.sql.expression import func, text, or_, and_
from werkzeug.security import check_password_hash from werkzeug.security import check_password_hash
from . import logger, config, db, ub, ldap from . import logger, config, db, ub, ldap1
from .helper import fill_indexpage, get_download_link, get_book_cover from .helper import fill_indexpage, get_download_link, get_book_cover
from .pagination import Pagination from .pagination import Pagination
from .web import common_filters, get_search_results, render_read_books, download_required from .web import common_filters, get_search_results, render_read_books, download_required
@ -40,14 +40,14 @@ from .web import common_filters, get_search_results, render_read_books, download
opds = Blueprint('opds', __name__) opds = Blueprint('opds', __name__)
log = logger.create() log = logger.create()
ldap_support = ldap.ldap_supported() ldap_support = ldap1.ldap_supported()
def requires_basic_auth_if_no_ano(f): def requires_basic_auth_if_no_ano(f):
@wraps(f) @wraps(f)
def decorated(*args, **kwargs): def decorated(*args, **kwargs):
if config.config_login_type == 1 and ldap_support: if config.config_login_type == 1 and ldap_support:
return ldap.ldap.basic_auth_required(*args, **kwargs) return ldap1.ldap.basic_auth_required(*args, **kwargs)
auth = request.authorization auth = request.authorization
if config.config_anonbrowse != 1: if config.config_anonbrowse != 1:
if not auth or not check_auth(auth.username, auth.password): if not auth or not check_auth(auth.username, auth.password):
@ -57,15 +57,6 @@ def requires_basic_auth_if_no_ano(f):
return decorated return decorated
'''def basic_auth_required_check(condition):
print("susi")
def decorator(f):
if condition and ldap_support:
return ldap.ldap.basic_auth_required(f)
return requires_basic_auth_if_no_ano(f)
return decorator'''
@opds.route("/opds/") @opds.route("/opds/")
@requires_basic_auth_if_no_ano @requires_basic_auth_if_no_ano
def feed_index(): def feed_index():

@ -20,54 +20,60 @@
from __future__ import division, print_function, unicode_literals from __future__ import division, print_function, unicode_literals
import sys import sys
import os import os
import errno
import signal import signal
import socket import socket
import logging
try: try:
from gevent.pywsgi import WSGIServer from gevent.pywsgi import WSGIServer
from gevent.pool import Pool from gevent.pool import Pool
from gevent import __version__ as geventVersion from gevent import __version__ as _version
gevent_present = True VERSION = {'Gevent': 'v' + _version}
_GEVENT = True
except ImportError: except ImportError:
from tornado.wsgi import WSGIContainer from tornado.wsgi import WSGIContainer
from tornado.httpserver import HTTPServer from tornado.httpserver import HTTPServer
from tornado.ioloop import IOLoop from tornado.ioloop import IOLoop
from tornado import version as tornadoVersion from tornado import version as _version
from tornado import log as tornadoLog VERSION = {'Tornado': 'v' + _version}
from tornado import options as tornadoOptions _GEVENT = False
gevent_present = False
from . import logger, config, global_WorkerThread from . import logger, global_WorkerThread
log = logger.create() log = logger.create()
class server: class WebServer:
wsgiserver = None
restart = False
app = None
access_logger = None
def __init__(self): def __init__(self):
signal.signal(signal.SIGINT, self.killServer) signal.signal(signal.SIGINT, self._killServer)
signal.signal(signal.SIGTERM, self.killServer) signal.signal(signal.SIGTERM, self._killServer)
def init_app(self, application): self.wsgiserver = None
self.app = application
self.port = config.config_port
self.listening = config.get_config_ipaddress(readable=True) + ":" + str(self.port)
self.access_logger = None self.access_logger = None
self.restart = False
self.app = None
self.listen_address = None
self.listen_port = None
self.IPV6 = False
self.unix_socket_file = None
self.ssl_args = None
def init_app(self, application, config):
self.app = application
self.listen_address = config.get_config_ipaddress()
self.IPV6 = config.get_ipaddress_type()
self.listen_port = config.config_port
if config.config_access_log: if config.config_access_log:
if gevent_present: log_name = "gevent.access" if _GEVENT else "tornado.access"
logger.setup(config.config_access_logfile, logger.DEFAULT_ACCESS_LEVEL, "access") formatter = logger.ACCESS_FORMATTER_GEVENT if _GEVENT else logger.ACCESS_FORMATTER_TORNADO
self.access_logger = logging.getLogger("access") self.access_logger = logger.create_access_log(config.config_access_logfile, log_name, formatter)
else: else:
logger.setup(config.config_access_logfile, logger.DEFAULT_ACCESS_LEVEL, "tornado.access") if not _GEVENT:
logger.get('tornado.access').disabled = True
self.ssl_args = None
certfile_path = config.get_config_certfile() certfile_path = config.get_config_certfile()
keyfile_path = config.get_config_keyfile() keyfile_path = config.get_config_keyfile()
if certfile_path and keyfile_path: if certfile_path and keyfile_path:
@ -79,101 +85,121 @@ class server:
log.warning('Cert path: %s', certfile_path) log.warning('Cert path: %s', certfile_path)
log.warning('Key path: %s', keyfile_path) log.warning('Key path: %s', keyfile_path)
def _make_gevent_unix_socket(self, socket_file):
# the socket file must not exist prior to bind()
if os.path.exists(socket_file):
# avoid nuking regular files and symbolic links (could be a mistype or security issue)
if os.path.isfile(socket_file) or os.path.islink(socket_file):
raise OSError(errno.EEXIST, os.strerror(errno.EEXIST), socket_file)
os.remove(socket_file)
unix_sock = WSGIServer.get_listener(socket_file, family=socket.AF_UNIX)
self.unix_socket_file = socket_file
# ensure current user and group have r/w permissions, no permissions for other users
# this way the socket can be shared in a semi-secure manner
# between the user running calibre-web and the user running the fronting webserver
os.chmod(socket_file, 0o660)
return unix_sock
def _make_gevent_socket(self): def _make_gevent_socket(self):
if config.get_config_ipaddress(): if os.name != 'nt':
return (config.get_config_ipaddress(), self.port) unix_socket_file = os.environ.get("CALIBRE_UNIX_SOCKET")
if unix_socket_file:
output = "socket:" + unix_socket_file + ":" + str(self.listen_port)
return self._make_gevent_unix_socket(unix_socket_file), output
if self.listen_address:
return (self.listen_address, self.listen_port), self._get_readable_listen_address()
if os.name == 'nt': if os.name == 'nt':
return ('0.0.0.0', self.port) self.listen_address = '0.0.0.0'
return (self.listen_address, self.listen_port), self._get_readable_listen_address()
address = ('', self.listen_port)
try: try:
s = WSGIServer.get_listener(('', self.port), family=socket.AF_INET6) sock = WSGIServer.get_listener(address, family=socket.AF_INET6)
output = self._get_readable_listen_address(True)
except socket.error as ex: except socket.error as ex:
log.error('%s', ex) log.error('%s', ex)
log.warning('Unable to listen on \'\', trying on IPv4 only...') log.warning('Unable to listen on "", trying on IPv4 only...')
s = WSGIServer.get_listener(('', self.port), family=socket.AF_INET) output = self._get_readable_listen_address(False)
log.debug("%r %r", s._sock, s._sock.getsockname()) sock = WSGIServer.get_listener(address, family=socket.AF_INET)
return s return sock, output
def start_gevent(self): def _start_gevent(self):
ssl_args = self.ssl_args or {} ssl_args = self.ssl_args or {}
log.info('Starting Gevent server')
try: try:
sock = self._make_gevent_socket() sock, output = self._make_gevent_socket()
log.info('Starting Gevent server on %s', output)
self.wsgiserver = WSGIServer(sock, self.app, log=self.access_logger, spawn=Pool(), **ssl_args) self.wsgiserver = WSGIServer(sock, self.app, log=self.access_logger, spawn=Pool(), **ssl_args)
self.wsgiserver.serve_forever() self.wsgiserver.serve_forever()
except socket.error: finally:
try: if self.unix_socket_file:
log.info('Unable to listen on "", trying on "0.0.0.0" only...') os.remove(self.unix_socket_file)
self.wsgiserver = WSGIServer(('0.0.0.0', config.config_port), self.app, spawn=Pool(), **ssl_args) self.unix_socket_file = None
self.wsgiserver.serve_forever()
except (OSError, socket.error) as e: def _start_tornado(self):
log.info("Error starting server: %s", e.strerror) log.info('Starting Tornado server on %s', self._get_readable_listen_address())
print("Error starting server: %s" % e.strerror)
global_WorkerThread.stop() # Max Buffersize set to 200MB )
sys.exit(1) http_server = HTTPServer(WSGIContainer(self.app),
except Exception: max_buffer_size = 209700000,
log.exception("Unknown error while starting gevent") ssl_options=self.ssl_args)
sys.exit(0) http_server.listen(self.listen_port, self.listen_address)
self.wsgiserver=IOLoop.instance()
def start_tornado(self): self.wsgiserver.start()
log.info('Starting Tornado server on %s', self.listening) # wait for stop signal
self.wsgiserver.close(True)
def _get_readable_listen_address(self, ipV6=False):
if self.listen_address == "":
listen_string = '""'
else:
ipV6 = self.IPV6
listen_string = self.listen_address
if ipV6:
adress = "[" + listen_string + "]"
else:
adress = listen_string
return adress + ":" + str(self.listen_port)
def start(self):
try: try:
# Max Buffersize set to 200MB ) if _GEVENT:
http_server = HTTPServer(WSGIContainer(self.app), # leave subprocess out to allow forking for fetchers and processors
max_buffer_size = 209700000, self._start_gevent()
ssl_options=self.ssl_args) else:
address = config.get_config_ipaddress() self._start_tornado()
http_server.listen(self.port, address) except Exception as ex:
self.wsgiserver=IOLoop.instance() log.error("Error starting server: %s", ex)
self.wsgiserver.start() print("Error starting server: %s" % ex)
# wait for stop signal return False
self.wsgiserver.close(True) finally:
except socket.error as err: self.wsgiserver = None
log.exception("Error starting tornado server")
print("Error starting server: %s" % err.strerror)
global_WorkerThread.stop() global_WorkerThread.stop()
sys.exit(1)
def startServer(self): if not self.restart:
if gevent_present:
# leave subprocess out to allow forking for fetchers and processors
self.start_gevent()
else:
self.start_tornado()
if self.restart is True:
log.info("Performing restart of Calibre-Web")
global_WorkerThread.stop()
if os.name == 'nt':
arguments = ["\"" + sys.executable + "\""]
for e in sys.argv:
arguments.append("\"" + e + "\"")
os.execv(sys.executable, arguments)
else:
os.execl(sys.executable, sys.executable, *sys.argv)
else:
log.info("Performing shutdown of Calibre-Web") log.info("Performing shutdown of Calibre-Web")
global_WorkerThread.stop() return True
sys.exit(0)
def setRestartTyp(self,starttyp): log.info("Performing restart of Calibre-Web")
self.restart = starttyp arguments = list(sys.argv)
arguments.insert(0, sys.executable)
if os.name == 'nt':
arguments = ["\"%s\"" % a for a in arguments]
os.execv(sys.executable, arguments)
return True
def killServer(self, signum, frame): def _killServer(self, signum, frame):
self.stopServer() self.stop()
def stopServer(self): def stop(self, restart=False):
self.restart = restart
if self.wsgiserver: if self.wsgiserver:
if gevent_present: if _GEVENT:
self.wsgiserver.close() self.wsgiserver.close()
else: else:
self.wsgiserver.add_callback(self.wsgiserver.stop) self.wsgiserver.add_callback(self.wsgiserver.stop)
@staticmethod
def getNameVersion():
if gevent_present:
return {'Gevent': 'v' + geventVersion}
else:
return {'Tornado': 'v' + tornadoVersion}

@ -145,3 +145,14 @@ input.pill:not(:checked) + label .glyphicon {
max-height:300px; max-height:300px;
overflow-y: auto; overflow-y: auto;
} }
div.log {
font-family: Courier New;
font-size: 12px;
box-sizing: border-box;
height: 700px;
overflow-y: scroll;
border: 1px solid #ddd;
white-space: nowrap;
padding: 0.5em;
}

@ -20,16 +20,16 @@
* Douban Books api document: https://developers.douban.com/wiki/?title=book_v2 (Chinese Only) * Douban Books api document: https://developers.douban.com/wiki/?title=book_v2 (Chinese Only)
*/ */
/* global _, i18nMsg, tinymce */ /* global _, i18nMsg, tinymce */
var dbResults = []; // var dbResults = [];
var ggResults = []; var ggResults = [];
$(function () { $(function () {
var msg = i18nMsg; var msg = i18nMsg;
/*var douban = "https://api.douban.com"; /*var douban = "https://api.douban.com";
var dbSearch = "/v2/book/search";*/ var dbSearch = "/v2/book/search";*/
var dbDone = true; // var dbDone = true;
var google = "https://www.googleapis.com/"; var google = "https://www.googleapis.com";
var ggSearch = "/books/v1/volumes"; var ggSearch = "/books/v1/volumes";
var ggDone = false; var ggDone = false;
@ -56,11 +56,9 @@ $(function () {
if (showFlag === 1) { if (showFlag === 1) {
$("#meta-info").html("<ul id=\"book-list\" class=\"media-list\"></ul>"); $("#meta-info").html("<ul id=\"book-list\" class=\"media-list\"></ul>");
} }
if (ggDone && dbDone) { if (!ggDone) {
if (!ggResults && !dbResults) { $("#meta-info").html("<p class=\"text-danger\">" + msg.no_result + "</p>");
$("#meta-info").html("<p class=\"text-danger\">" + msg.no_result + "</p>"); return;
return;
}
} }
if (ggDone && ggResults.length > 0) { if (ggDone && ggResults.length > 0) {
ggResults.forEach(function(result) { ggResults.forEach(function(result) {
@ -137,7 +135,10 @@ $(function () {
dataType: "jsonp", dataType: "jsonp",
jsonp: "callback", jsonp: "callback",
success: function success(data) { success: function success(data) {
ggResults = data.items; if ("items" in data) {
ggResults = data.items;
ggDone = true;
}
}, },
complete: function complete() { complete: function complete() {
ggDone = true; ggDone = true;

@ -1,609 +1,74 @@
var threadregexp = /(?:^| - )(\[[^\]]*\]):/; /* This file is part of the Calibre-Web (https://github.com/janeczku/calibre-web)
* Copyright (C) 2018 OzzieIsaacs
var colors = ["#ffa", "#aaf", "#afa", "#aff", "#faf", "#aaa", "#fd8", "#f80", "#4df", "#4fc", "#76973c", "#7e56d8", "#99593d", "#37778a", "#4068fc"]; *
var screenlines = 1; * 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
var file = null; * the Free Software Foundation, either version 3 of the License, or
var text; * (at your option) any later version.
*
var current, nextFilterId = 1; * This program is distributed in the hope that it will be useful,
var shl = null; * but WITHOUT ANY WARRANTY; without even the implied warranty of
var hl = []; * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
var groupwith = false; * GNU General Public License for more details.
var filterswitch = true; *
* You should have received a copy of the GNU General Public License
var selectedlineid = -1; * along with this program. If not, see <http://www.gnu.org/licenses/>.
var selectedthread = null; */
var reachedbottom = false;
var reachedtop = false; // Upon loading load the logfile for the first option (event log)
$(function() {
init(0);
function wheelscroll(event) });
{
renderincremental(event.deltaY); // After change the radio option load the corresponding log file
} $("#log_group input").on("change", function() {
var element = $("#log_group input[type='radio']:checked").val();
function keypress(event) init(element);
{ });
if (event.key == "PageDown") {
_render(screenlines - 1);
event.preventDefault(); // Handle reloading of the log file and display the content
} function init(logType) {
if (event.key == "PageUp") { var d = document.getElementById("renderer");
_render(-(screenlines - 1)); d.innerHTML = "loading ...";
event.preventDefault();
} /*var r = new XMLHttpRequest();
if (event.key == "Home" && event.ctrlKey) { r.open("GET", "/ajax/log/" + logType, true);
selectedlineid = 0; r.responseType = "text";
render(); r.onload = function() {
event.preventDefault(); var text;
} text = (r.responseText).split("\n");
if (event.key == "End" && event.ctrlKey) { $("#renderer").text("");
selectedlineid = text.length - 1; console.log(text.length);
render(); for (var i = 0; i < text.length; i++) {
event.preventDefault(); $("#renderer").append( "<div>" + _sanitize(text[i]) + "</div>" );
} }
if (event.key == "ArrowUp") { };
renderincremental(-1); r.send();*/
event.preventDefault(); $.ajax({
} url: "/ajax/log/" + logType,
if (event.key == "ArrowDown") { datatype: 'text',
renderincremental(1); cache: false
event.preventDefault(); })
} .done( function(data) {
} var text;
$("#renderer").text("");
function init(filename) { text = (data).split("\n");
document.addEventListener("wheel", wheelscroll, false); console.log(text.length);
document.addEventListener("keypress", keypress, false); for (var i = 0; i < text.length; i++) {
window.addEventListener("resize", resize, false); $("#renderer").append( "<div>" + _sanitize(text[i]) + "</div>" );
}
_resize(); });
}
var s = document.getElementById("search");
s.value = "";
selectfilter(0); function _sanitize(t) {
reload(filename); t = t
} .replace(/&/g, "&amp;")
.replace(/ /g, "&nbsp;")
function _resize() .replace(/</g, "&lt;")
{ .replace(/>/g, "&gt;");
var d = document.getElementById("renderer");
var t = document.getElementById("toobar"); return t;
screenlines = Math.floor((window.innerHeight - t.offsetHeight) / d.firstChild.offsetHeight) - 1;
}
function resize()
{
_resize();
repaint();
}
function reload(filename)
{
if (shl) shl.cache = {};
for (_hl of hl) _hl.cache = {};
var q = filename;
document.title = "Log: " + (q || "none loaded");
if (!q)
return;
var d = document.getElementById("renderer");
d.innerHTML = "loading " + q + "...";
var r = new XMLHttpRequest();
r.open("GET", "/ajax/accesslog", true);
r.responseType = 'text';
r.onload = function() {
console.log("prepare");
prepare(r.responseText);
if (selectedlineid > text.length) {
selectedlineid = -1;
}
console.log("render");
render();
};
r.send();
}
function _sanitize(t)
{
t = t
.replace(/&/g, "&amp;")
.replace(/ /g, "&nbsp;")
.replace(/</g, "&lt;")
.replace(/>/g, "&gt;");
return t;
}
function _prepare(t)
{
// sanitization happens in render, since otherwise it eats enormous amount of memory.
/*
var t = t.split('\n');
for (var i in t) {
t[i] = _sanitize(t[i]);
}
return t;
*/
return /*_sanitize*/(t).split('\n');
}
function prepare(t)
{
text = _prepare(t);
}
function render()
{
_render(0, false); // completely redraws the view from the current scroll position
}
function repaint()
{
_render(0, true); // completely redraws the view, but centers the selected line
}
function renderincremental(difference)
{
_render(difference); // "scrolls" the view
}
function _render(increment, repaintonly)
{
var epoch = new Date();
var d = document.getElementById("renderer");
var filter = _gfilteron();
function process(i, append)
{
var t = _sanitize(text[i]);
var lhl = false;
function dohl(_hl)
{
if (_hl.cache[i] === false) {
// lhl is here unaffected
return t;
}
var t2 = t.replace(new RegExp("(" + _hl.text_r + ")", "g"), "<span style='background-color:" + _hl.color + ";'>$1</span>");
var affecting = (t != t2);
_hl.cache[i] = affecting;
lhl = lhl || (affecting && (!filter || _hl.filter));
return t2;
}
for (var h in hl)
t = dohl(hl[h]);
if (shl)
t = dohl(shl);
if (filter && !lhl && i != selectedlineid) {
return false;
}
lhl = lhl && !filter;
var div = document.createElement("div");
div.id = i;
if (lhl) div.className = 'lhl';
div.onclick = function() { selectline(this); };
div.innerHTML = t;
if (t.match(new RegExp(selectedthread, "g"))) div.className += ' thread';
if (append)
d.appendChild(div);
else
d.insertBefore(div, d.firstChild);
return true;
}
var lefttodraw = Math.floor(Math.abs(increment));
if (increment < 0) {
// scroll up
reachedbottom = false;
if (reachedtop) {
_hint("reached top of the file");
return;
}
for (var i = parseInt(d.firstChild.id) - 1; lefttodraw && i >= 0 && i < text.length; --i) {
if (process(i, false)) {
--lefttodraw;
if (d.childNodes.length > screenlines)
d.removeChild(d.lastChild);
}
}
if (lefttodraw) {
_hint("reached top of the file");
reachedtop = true;
}
} else if (increment > 0) {
// scroll down
reachedtop = false;
if (reachedbottom) {
_hint("reached bottom of the file");
return;
}
for (var i = parseInt(d.lastChild.id) + 1; lefttodraw && i < text.length; ++i) {
if (process(i, true)) {
--lefttodraw;
if (d.childNodes.length > screenlines)
d.removeChild(d.firstChild);
}
}
if (lefttodraw) {
_hint("reached bottom of the file");
reachedbottom = true;
}
} else { // == 0
// redraw all
reachedbottom = false;
reachedtop = false;
lefttodraw = screenlines;
var i = repaintonly ? parseInt(d.firstChild.id) : selectedlineid;
if (i < 0) i = 0;
d.innerHTML = "";
for (; lefttodraw && i < text.length; ++i) {
if (process(i, true)) {
--lefttodraw;
}
}
if (!repaintonly && selectedlineid > -1) {
// center the selected line in the middle of screen!
_render(-(screenlines / 2));
}
}
selectline(selectedlineid);
var now = new Date();
console.log("rendered in " + (now.getTime() - epoch.getTime()) + "ms");
var pos = document.getElementById("position");
pos.textContent = Math.round((parseInt(d.firstChild.id) / text.length) * 1000) / 10 + "%";
}
function _hint(h)
{
document.getElementById("hint").innerHTML = h;
}
function _gfilteron()
{
if (!filterswitch)
return false;
if (shl && shl.filter)
return true;
for (var h in hl)
{
if (hl[h].filter)
return true;
}
return false;
}
function _getfilterelement(filter)
{
if (filter == 0)
return document.getElementById("search");
return document.getElementById("filter" + filter);
}
function _setfilterelementstate(p0, _hl)
{
p0.style.textDecoration = _hl.filter ? "underline" : "";
}
function _triminput(t)
{
t = t
.replace(/^\s+/, "")
.replace(/\s+$/, "")
;
return t;
}
function _regexpescape(t)
{
t = t
.replace(/\\/g, "\\\\")
.replace(/\?/g, "\\?")
.replace(/\./g, "\\.")
.replace(/\+/g, "\\+")
.replace(/\*/g, "\\*")
.replace(/\^/g, "\\^")
.replace(/\$/g, "\\$")
.replace(/\(/g, "\\(")
.replace(/\)/g, "\\)")
.replace(/\[/g, "\\[")
.replace(/\]/g, "\\]")
.replace(/\|/g, "\\|")
;
return t;
}
function resetuistate()
{
groupwith = false;
_hint("");
}
function newhl(t, p, persistent)
{
return {
id: persistent ? nextFilterId++ : 0,
text: t,
text_r: _sanitize(_regexpescape(t)),
color: p ? p.color : colors[0],
filter: p ? p.filter : false,
cache: {}
};
}
function selectline(id)
{
var l0 = document.getElementById(selectedlineid);
if (l0)
l0.style.backgroundColor = "";
var l1 = null;
if (typeof(id) == "object") {
l1 = id;
id = parseInt(l1.id);
} else {
l1 = document.getElementById(id);
}
selectedlineid = id;
if (selectedlineid > -1)
_hint("line # " + (selectedlineid + 1));
if (l1) {
l1.style.background = "#faa";
}
var thread = null;
var m = text[selectedlineid].match(threadregexp);
if (m) thread = _regexpescape(_sanitize(m[1]));
if (thread != selectedthread) {
selectedthread = thread;
repaint();
}
return l1;
}
function mouseup(event)
{
if (event.ctrlKey)
return;
resetuistate();
var s = window.getSelection();
var t = _triminput(s.toString());
if (!t)
return;
s = document.getElementById("search");
s.value = t;
t = _prepare(t)[0];
shl = newhl(t, shl);
selectfilter(0);
repaint();
}
function persist()
{
resetuistate();
var dorender = false;
if (!shl)
{
_apply();
dorender = true;
}
if (!shl)
return;
selectfilter(0);
var _hl = newhl(shl.text, shl, true);
_hl.cache = shl.cache; // hope this is right, shl is updated in _apply, that always creates an empty cache
hl.push(_hl);
var p = document.getElementById("persistents");
var p0 = document.createElement("div");
p0.id = "filter" + _hl.id;
p0.className = "persistent";
p0.style.backgroundColor = _hl.color;
p0.innerHTML = _hl.text;
p0.onclick = function() {selectfilter(_hl.id)};
_setfilterelementstate(p0, _hl);
p.appendChild(p0);
_restartshl();
selectfilter(_hl.id);
if (dorender)
render();
colors.push(colors.shift());
}
function _apply()
{
s = document.getElementById("search");
var t = _triminput(s.value);
if (!t)
{
shl = null;
}
else
{
t = _prepare(t)[0];
shl = newhl(t, shl);
}
} }
function highlight()
{
resetuistate();
_apply();
repaint();
selectfilter(0);
}
function filter()
{
resetuistate();
if ((!shl && !hl.length) || (current == shl))
{
_apply();
selectfilter(0);
}
if (current) {
current.filter = !current.filter;
_setfilterelementstate(_getfilterelement(current.id), current);
if (filterswitch)
render();
else
repaint();
}
}
function group()
{
resetuistate();
// the code it self happens in selectfilter() function
if (!shl)
{
_hint("press higlight or filter first");
return;
}
if (hl.length)
{
groupwith = true;
_hint("-&gt; now select a pinned filter to group the current highlight with");
}
else
{
_hint("you have to pin a filter with the 'pin' button first");
}
}
function _restartshl()
{
var s = document.getElementById("search");
s.value = "";
s.style.backgroundColor = "";
s.style.textDecoration = "";
current = shl = null;
}
function restart()
{
resetuistate();
var filtered = _gfilteron(); // was: = current && current.filter
if (current == shl)
{
_restartshl();
}
else
{
var p0 = _getfilterelement(current.id);
for (var h in hl)
if (hl[h].id == current.id) {
hl.splice(h, 1);
break;
}
if (current.text) {
shl = newhl(current.text, current);
var s = document.getElementById("search");
s.value = current.text;
_setfilterelementstate(s, shl);
}
selectfilter(0);
var p = document.getElementById("persistents");
p.removeChild(p0);
}
if (!shl) // means: filter could not be switched back to shl or directly shl was reset
render();
}
function selectfilter(filter)
{
var el0 = _getfilterelement(current ? current.id : 0);
var el1 = _getfilterelement(filter);
el0.style.border = "";
el0.style.margin = "";
el1.style.border = "solid 2px #3ad";
el1.style.margin = "0px";
function filterbyid(id)
{
for (var h in hl)
if (hl[h].id == id)
return hl[h];
}
if (groupwith && filter)
{
el1.innerHTML += "+" + shl.text;
var _hl = filterbyid(filter);
_hl.text = ""; // not backward compatible
_hl.text_r += "|" + shl.text_r;
_hl.cache = {};
resetuistate();
_restartshl();
render();
}
else
resetuistate();
current = (filter == 0) ? shl : filterbyid(filter);
// A bit hacky redraw of the search box color :)
if (filter === 0 && shl)
{
var s = document.getElementById("search");
s.style.backgroundColor = shl.color;
}
}
function flipfilter(event)
{
filterswitch = !filterswitch;
event.target.innerHTML = (filterswitch ? "filters on" : "filters off");
render();
event.stopPropagation();
}

@ -106,7 +106,7 @@
<div class="row"> <div class="row">
<div class="col"> <div class="col">
<h2>{{_('Administration')}}</h2> <h2>{{_('Administration')}}</h2>
<div class="btn btn-default"><a id="logfile" href="{{url_for('admin.view_logfile')}}">{{_('View Logfile')}}</a></div> <div class="btn btn-default"><a id="logfile" href="{{url_for('admin.view_logfile')}}">{{_('View Logfiles')}}</a></div>
<div class="btn btn-default" id="restart_database">{{_('Reconnect to Calibre DB')}}</div> <div class="btn btn-default" id="restart_database">{{_('Reconnect to Calibre DB')}}</div>
<div class="btn btn-default" id="admin_restart" data-toggle="modal" data-target="#RestartDialog">{{_('Restart Calibre-Web')}}</div> <div class="btn btn-default" id="admin_restart" data-toggle="modal" data-target="#RestartDialog">{{_('Restart Calibre-Web')}}</div>
<div class="btn btn-default" id="admin_stop" data-toggle="modal" data-target="#ShutdownDialog">{{_('Stop Calibre-Web')}}</div> <div class="btn btn-default" id="admin_stop" data-toggle="modal" data-target="#ShutdownDialog">{{_('Stop Calibre-Web')}}</div>

@ -1,141 +1,15 @@
{% extends "layout.html" %} {% extends "layout.html" %}
{% block body %} {% block body %}
{% block header %} <div id="log_group" class="inputs">
<style> <div><input type="radio" name="log_radio" id="log1" value="0" checked>
<label for="log0">{{_('Show Calibre-Web log')}}</label> {{logfiles[0]}}</div>
html, body { {% if accesslog_enable %}
margin: 0; <div><input type="radio" name="log_radio" id="log0" value="1">
height: 100%; <label for="log1">{{_('Show access log')}}</label> {{logfiles[1]}}</div>
overflow-y: hidden {% endif %}
} </div>
<div id="renderer" class="log"></div>
div.log {
font-family: Courier New;
font-size: 12px;
box-sizing: border-box;
height: 500px;
overflow-y: scroll;
border-style: solid;
}
div.log {
white-space: nowrap;
padding: 0.5em;
}
div.lhl {
background-color: #ffd;
}
div.thread {
background: #fdd;
}
div.persistent,
div.persistents,
div.hint {
display: inline;
cursor: pointer;
}
div.hint,
div.persistent {
font-family: Arial;
font-size: 12px;
padding: 2px;
margin: 1px;
}
div.hint {
color: gray;
font-style: italic;
}
div.button,
div.button2,
div.button_right {
display: inline;
font-weight: bold;
font-family: Arial;
font-size: 12px;
padding: 2px;
cursor: pointer;
color: #3ad;
_text-decoration: underline;
}
div.button2 {
border: solid 1px gray;
border-radius: 4px;
color: black;
text-decoration: none;
}
div.button_right {
margin-right: 1em;
float: right;
}
div.nounder {
text-decoration: none;
}
input.filebtn {
float: right;
position: relative;
}
</style>
{% endblock %}
<!--body onload="load()"-->
<div id="toobar" class="toobar">
<input class="search" id="search" type="text" onmousedown="selectfilter(0)"></input>
<div class="button2" onclick="highlight()" title="apply changes in the search box">apply</div>
<div class="button_right" onclick="flipfilter(event)" title="disable/enable selected filters">filters on</div>
<br/>
<div class="button" onclick="filter()" title="show only lines containing the phrase">filter</div>
<div class="hint" onclick="repaint()" id="hint"></div>
<div class="hint" onclick="repaint()" id="position"></div>
</div>
<div class="clear"></div>
<div class="logpaginator"><a href="{{'/logs/1'}}"><span class="glyphicon glyphicon-fast-backward"></span></a> <a href="{{ "/logs/"}}"><span class="glyphicon glyphicon-step-backward"></span></a> <a href="{{ '/logs/' }}"><span class="glyphicon glyphicon-step-forward"></span></a> <a href="{{'/logs/'}}"><span class="glyphicon glyphicon-fast-forward"></span></a></div>
<div class="logperpage">
<form id="logform1" action="" method="POST">
<label for="reversed">{{_('Reversed')}}:</label>
<input type="checkbox" name="reversed" id="reversed" onchange="this.form.submit();" {% if reversed %} checked="checked" {% endif %} />&nbsp;
<label for="perpage">{{_('Lines per page')}}:</label>
<select name="perpage" id ="perpage" onchange="this.form.submit();">
{% for key, value in perpage_p.items() %}
<option value="{{key}}"{% if value== perpage %} selected="selected" {% endif %}>{{value}}</option>
{% endfor %}
</select>
</form>
</div>
<div class="logwarn">{{warning}}</div>
<div class="clear"></div>
<div class="logdiv">
<table class="logtable" cellpadding="0" cellspacing="0">
<div id="renderer" class="log" onmouseup="mouseup(event)"><div>nothing loaded</div></div>
{% for line in log %}
<tr><td class="logline">{{line.line}}</td><td>{{line.date}}</td><td class="loglevel">{{line.level}}</td><td>{{line.message}}</td></tr>
{% endfor %}
</table>
</div>
<div class="logform">
<form id="logform2" action="" method="POST" style="width: auto">
<label for="from" style="display: inline-block; float: left; margin-right: 5px; height: 34px; line-height: 34px;">{{_('Jump to time:')}}</label>
<input style="display: inline-block; text-align: center; float: left; width: 155px;" class="form-control" type="text" name="from" id="from" size="15" value="{{from}}"/>
<input style="display: inline-block; float: left; margin-left: 5px;" class="btn btn-default" type="submit" value="{{_('Go')}}" />
</form>
</div>
{% endblock %} {% endblock %}
{% block js %} {% block js %}
<script src="{{ url_for('static', filename='js/logviewer.js') }}"></script> <script src="{{ url_for('static', filename='js/logviewer.js') }}"></script>
<script>
$(function(){
init('access.log');
});
</script>
{% endblock %} {% endblock %}

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

@ -40,18 +40,12 @@ from sqlalchemy.orm import relationship, sessionmaker
from sqlalchemy.ext.declarative import declarative_base from sqlalchemy.ext.declarative import declarative_base
from werkzeug.security import generate_password_hash from werkzeug.security import generate_password_hash
'''try:
import ldap
except ImportError:
pass'''
from . import constants, logger, cli from . import constants, logger, cli
session = None session = None
engine = create_engine(u'sqlite:///{0}'.format(cli.settingspath), echo=False)
engine = create_engine('sqlite:///{0}'.format(cli.settingspath), echo=False)
Base = declarative_base() Base = declarative_base()
@ -171,18 +165,12 @@ class UserBase:
def __repr__(self): def __repr__(self):
return '<User %r>' % self.nickname return '<User %r>' % self.nickname
# Login via LDAP method
'''@staticmethod
def try_login(username, password,config_dn, ldap_provider_url):
conn = get_ldap_connection(ldap_provider_url)
conn.simple_bind_s(
config_dn.replace("%s", username),
password)'''
# Baseclass for Users in Calibre-Web, settings which are depending on certain users are stored here. It is derived from # Baseclass for Users in Calibre-Web, settings which are depending on certain users are stored here. It is derived from
# User Base (all access methods are declared there) # User Base (all access methods are declared there)
class User(UserBase, Base): class User(UserBase, Base):
__tablename__ = 'user' __tablename__ = 'user'
__table_args__ = {'sqlite_autoincrement': True}
id = Column(Integer, primary_key=True) id = Column(Integer, primary_key=True)
nickname = Column(String(64), unique=True) nickname = Column(String(64), unique=True)
@ -476,35 +464,22 @@ class Config:
def get_config_certfile(self): def get_config_certfile(self):
if cli.certfilepath: if cli.certfilepath:
return cli.certfilepath return cli.certfilepath
else: if cli.certfilepath is "":
if cli.certfilepath is "": return None
return None return self.config_certfile
else:
return self.config_certfile
def get_config_keyfile(self): def get_config_keyfile(self):
if cli.keyfilepath: if cli.keyfilepath:
return cli.keyfilepath return cli.keyfilepath
else: if cli.certfilepath is "":
if cli.certfilepath is "": return None
return None return self.config_keyfile
else:
return self.config_keyfile def get_config_ipaddress(self):
return cli.ipadress or ""
def get_config_ipaddress(self, readable=False):
if not readable: def get_ipaddress_type(self):
if cli.ipadress: return cli.ipv6
return cli.ipadress
else:
return ""
else:
answer="0.0.0.0"
if cli.ipadress:
if cli.ipv6:
answer = "["+cli.ipadress+"]"
else:
answer = cli.ipadress
return answer
def _has_role(self, role_flag): def _has_role(self, role_flag):
return constants.has_flag(self.config_default_role, role_flag) return constants.has_flag(self.config_default_role, role_flag)
@ -782,6 +757,34 @@ def migrate_Database():
conn.execute("ALTER TABLE Settings ADD column `config_access_log` INTEGER DEFAULT 0") conn.execute("ALTER TABLE Settings ADD column `config_access_log` INTEGER DEFAULT 0")
conn.execute("ALTER TABLE Settings ADD column `config_access_logfile` String DEFAULT ''") conn.execute("ALTER TABLE Settings ADD column `config_access_logfile` String DEFAULT ''")
session.commit() session.commit()
try:
# check if one table with autoincrement is existing (should be user table)
conn = engine.connect()
conn.execute("SELECT COUNT(*) FROM sqlite_sequence WHERE name='user'")
except exc.OperationalError:
# Create new table user_id and copy contents of table user into it
conn = engine.connect()
conn.execute("CREATE TABLE user_id (id INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT,"
"nickname VARCHAR(64),"
"email VARCHAR(120),"
"role SMALLINT,"
"password VARCHAR,"
"kindle_mail VARCHAR(120),"
"locale VARCHAR(2),"
"sidebar_view INTEGER,"
"default_language VARCHAR(3),"
"mature_content BOOLEAN,"
"UNIQUE (nickname),"
"UNIQUE (email),"
"CHECK (mature_content IN (0, 1)))")
conn.execute("INSERT INTO user_id(id, nickname, email, role, password, kindle_mail,locale,"
"sidebar_view, default_language, mature_content) "
"SELECT id, nickname, email, role, password, kindle_mail, locale,"
"sidebar_view, default_language, mature_content FROM user")
# delete old user table and rename new user_id table to user:
conn.execute("DROP TABLE user")
conn.execute("ALTER TABLE user_id RENAME TO user")
session.commit()
# Remove login capability of user Guest # Remove login capability of user Guest
conn = engine.connect() conn = engine.connect()
@ -795,13 +798,6 @@ def clean_database():
session.query(RemoteAuthToken).filter(now > RemoteAuthToken.expiration).delete() session.query(RemoteAuthToken).filter(now > RemoteAuthToken.expiration).delete()
'''#get LDAP connection
def get_ldap_connection(ldap_provider_url):
conn = ldap.initialize('ldap://{}'.format(ldap_provider_url))
return conn'''
def create_default_config(): def create_default_config():
settings = Settings() settings = Settings()
settings.mail_server = "mail.example.com" settings.mail_server = "mail.example.com"

@ -33,7 +33,7 @@ from tempfile import gettempdir
from babel.dates import format_datetime from babel.dates import format_datetime
from flask_babel import gettext as _ from flask_babel import gettext as _
from . import constants, logger, config, get_locale, Server from . import constants, logger, config, get_locale, web_server
log = logger.create() log = logger.create()
@ -95,8 +95,7 @@ class Updater(threading.Thread):
self.status = 6 self.status = 6
log.debug(u'Preparing restart of server') log.debug(u'Preparing restart of server')
time.sleep(2) time.sleep(2)
Server.setRestartTyp(True) web_server.stop(True)
Server.stopServer()
self.status = 7 self.status = 7
time.sleep(2) time.sleep(2)
except requests.exceptions.HTTPError as ex: except requests.exceptions.HTTPError as ex:

@ -41,7 +41,7 @@ from werkzeug.exceptions import default_exceptions
from werkzeug.datastructures import Headers from werkzeug.datastructures import Headers
from werkzeug.security import generate_password_hash, check_password_hash from werkzeug.security import generate_password_hash, check_password_hash
from . import constants, logger, isoLanguages, ldap from . import constants, logger, isoLanguages, ldap1
from . import global_WorkerThread, searched_ids, lm, babel, db, ub, config, get_locale, app, language_table from . import global_WorkerThread, searched_ids, lm, babel, db, ub, config, get_locale, app, language_table
from .gdriveutils import getFileFromEbooksFolder, do_gdrive_download from .gdriveutils import getFileFromEbooksFolder, do_gdrive_download
from .helper import common_filters, get_search_results, fill_indexpage, speaking_language, check_valid_domain, \ from .helper import common_filters, get_search_results, fill_indexpage, speaking_language, check_valid_domain, \
@ -52,7 +52,7 @@ from .pagination import Pagination
from .redirect import redirect_back from .redirect import redirect_back
feature_support = dict() feature_support = dict()
feature_support['ldap'] = ldap.ldap_supported() feature_support['ldap'] = ldap1.ldap_supported()
try: 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
@ -1093,7 +1093,7 @@ def login():
.first() .first()
if config.config_login_type == 1 and user and feature_support['ldap']: if config.config_login_type == 1 and user and feature_support['ldap']:
try: try:
if ldap.ldap.bind_user(form['username'], form['password']) is not None: if ldap1.ldap.bind_user(form['username'], form['password']) is not None:
login_user(user, remember=True) login_user(user, remember=True)
flash(_(u"you are now logged in as: '%(nickname)s'", nickname=user.nickname), flash(_(u"you are now logged in as: '%(nickname)s'", nickname=user.nickname),
category="success") category="success")

@ -326,6 +326,8 @@ class WorkerThread(threading.Thread):
nextline = p.stdout.readline() nextline = p.stdout.readline()
if os.name == 'nt' and sys.version_info < (3, 0): if os.name == 'nt' and sys.version_info < (3, 0):
nextline = nextline.decode('windows-1252') nextline = nextline.decode('windows-1252')
elif os.name == 'posix' and sys.version_info < (3, 0):
nextline = nextline.decode('utf-8')
log.debug(nextline.strip('\r\n')) log.debug(nextline.strip('\r\n'))
# parse progress string from calibre-converter # parse progress string from calibre-converter
progress = re.search("(\d+)%\s.*", nextline) progress = re.search("(\d+)%\s.*", nextline)

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff
Loading…
Cancel
Save