mirror of
https://github.com/janeczku/calibre-web
synced 2024-08-18 19:59:27 +00:00
Merge remote-tracking branch 'upstream/Develop' into Develop
Conflicts: cps/web.py
This commit is contained in:
commit
11c8b47c39
13
cps.py
13
cps.py
|
@ -23,13 +23,17 @@ import os
|
|||
|
||||
|
||||
# Insert local directories into path
|
||||
sys.path.append(os.path.dirname(os.path.abspath(__file__)))
|
||||
sys.path.append(os.path.join(os.path.dirname(os.path.abspath(__file__)), 'vendor'))
|
||||
if sys.version_info < (3, 0):
|
||||
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 web_server
|
||||
from cps.opds import opds
|
||||
from cps import Server
|
||||
from cps.web import web
|
||||
from cps.jinjia import jinjia
|
||||
from cps.about import about
|
||||
|
@ -56,7 +60,8 @@ def main():
|
|||
app.register_blueprint(editbook)
|
||||
if oauth_available:
|
||||
app.register_blueprint(oauth)
|
||||
Server.startServer()
|
||||
success = web_server.start()
|
||||
sys.exit(0 if success else 1)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
|
|
|
@ -84,11 +84,11 @@ searched_ids = {}
|
|||
from .worker import WorkerThread
|
||||
global_WorkerThread = WorkerThread()
|
||||
|
||||
from .server import server
|
||||
Server = server()
|
||||
from .server import WebServer
|
||||
web_server = WebServer()
|
||||
|
||||
from .ldap import Ldap
|
||||
ldap = Ldap()
|
||||
from .ldap_login import Ldap
|
||||
ldap1 = Ldap()
|
||||
|
||||
babel = Babel()
|
||||
|
||||
|
@ -97,16 +97,22 @@ log = logger.create()
|
|||
|
||||
def create_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)
|
||||
|
||||
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')
|
||||
Server.init_app(app)
|
||||
web_server.init_app(app, config)
|
||||
db.setup_db()
|
||||
babel.init_app(app)
|
||||
ldap.init_app(app)
|
||||
ldap1.init_app(app)
|
||||
global_WorkerThread.start()
|
||||
return app
|
||||
|
||||
|
|
|
@ -41,8 +41,9 @@ 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 . import db, converter, uploader
|
||||
from .isoLanguages import __version__ as iso639Version
|
||||
from .server import VERSION as serverVersion
|
||||
from .web import render_title_template
|
||||
|
||||
|
||||
|
@ -71,7 +72,7 @@ def stats():
|
|||
versions['pySqlite'] = 'v' + db.engine.dialect.dbapi.version
|
||||
versions['Sqlite'] = 'v' + db.engine.dialect.dbapi.sqlite_version
|
||||
versions.update(converter.versioncheck())
|
||||
versions.update(Server.getNameVersion())
|
||||
versions.update(serverVersion)
|
||||
versions['Python'] = sys.version
|
||||
return render_title_template('stats.html', bookcounter=counter, authorcounter=authors, versions=versions,
|
||||
categorycounter=categorys, seriecounter=series, title=_(u"Statistics"), page="stat")
|
||||
|
|
59
cps/admin.py
59
cps/admin.py
|
@ -41,15 +41,15 @@ from sqlalchemy import and_
|
|||
from sqlalchemy.exc import IntegrityError
|
||||
from werkzeug.security import generate_password_hash
|
||||
|
||||
from . import constants, logger, ldap
|
||||
from . import db, ub, Server, get_locale, config, updater_thread, babel, gdriveutils
|
||||
from . import constants, logger, ldap1
|
||||
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, \
|
||||
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()
|
||||
feature_support['ldap'] = ldap.ldap_supported()
|
||||
feature_support['ldap'] = ldap1.ldap_supported()
|
||||
|
||||
try:
|
||||
from goodreads.client import GoodreadsClient
|
||||
|
@ -63,12 +63,6 @@ except ImportError:
|
|||
# except ImportError:
|
||||
# feature_support['rar'] = False
|
||||
|
||||
'''try:
|
||||
import ldap
|
||||
feature_support['ldap'] = True
|
||||
except ImportError:
|
||||
feature_support['ldap'] = False'''
|
||||
|
||||
try:
|
||||
from oauth_bb import oauth_check
|
||||
feature_support['oauth'] = True
|
||||
|
@ -103,12 +97,10 @@ def shutdown():
|
|||
showtext = {}
|
||||
if task == 0:
|
||||
showtext['text'] = _(u'Server restarted, please reload page')
|
||||
Server.setRestartTyp(True)
|
||||
else:
|
||||
showtext['text'] = _(u'Performing shutdown of server, please close window')
|
||||
Server.setRestartTyp(False)
|
||||
# stop gevent/tornado server
|
||||
Server.stopServer()
|
||||
web_server.stop(task == 0)
|
||||
return json.dumps(showtext)
|
||||
else:
|
||||
if task == 2:
|
||||
|
@ -221,8 +213,7 @@ def view_configuration():
|
|||
# ub.session.close()
|
||||
# ub.engine.dispose()
|
||||
# stop Server
|
||||
Server.setRestartTyp(True)
|
||||
Server.stopServer()
|
||||
web_server.stop(True)
|
||||
log.info('Reboot required, restarting')
|
||||
readColumn = db.session.query(db.Custom_Columns)\
|
||||
.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")
|
||||
return render_title_template("config_edit.html", content=config, origin=origin,
|
||||
gdrive=gdriveutils.gdrive_support, gdriveError=gdriveError,
|
||||
goodreads=goodreads_support, title=_(u"Basic Configuration"),
|
||||
feature_support=feature_support, title=_(u"Basic Configuration"),
|
||||
page="config")
|
||||
elif not to_save["config_ldap_serv_username"] or not to_save["config_ldap_serv_password"]:
|
||||
ub.session.commit()
|
||||
flash(_(u'Please enter a LDAP service account and password'), category="error")
|
||||
return render_title_template("config_edit.html", content=config, origin=origin,
|
||||
gdrive=gdriveutils.gdrive_support, gdriveError=gdriveError,
|
||||
goodreads=goodreads_support, title=_(u"Basic Configuration"),
|
||||
feature_support=feature_support, title=_(u"Basic Configuration"),
|
||||
page="config")
|
||||
else:
|
||||
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")
|
||||
return render_title_template("config_edit.html", content=config, origin=origin,
|
||||
gdrive=gdriveutils.gdrive_support, gdriveError=gdriveError,
|
||||
goodreads=goodreads_support, title=_(u"Basic Configuration"),
|
||||
feature_support=feature_support, title=_(u"Basic Configuration"),
|
||||
page="config")
|
||||
|
||||
# Remote login configuration
|
||||
|
@ -556,12 +547,11 @@ def configuration_helper(origin):
|
|||
title=_(u"Basic Configuration"), page="config")
|
||||
if reboot_required:
|
||||
# stop Server
|
||||
Server.setRestartTyp(True)
|
||||
Server.stopServer()
|
||||
web_server.stop(True)
|
||||
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:
|
||||
if is_gdrive_ready() and feature_support['gdrive'] is True and config.config_use_google_drive == True:
|
||||
gdrivefolders = listRootFolders()
|
||||
else:
|
||||
gdrivefolders = list()
|
||||
|
@ -582,9 +572,6 @@ def new_user():
|
|||
to_save = request.form.to_dict()
|
||||
content.default_language = to_save["default_language"]
|
||||
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:
|
||||
content.locale = to_save["locale"]
|
||||
|
||||
|
@ -806,19 +793,27 @@ def reset_password(user_id):
|
|||
@login_required
|
||||
@admin_required
|
||||
def view_logfile():
|
||||
perpage_p = {0:"30",1:"40",2:"100"}
|
||||
for key, value in perpage_p.items():
|
||||
print(key)
|
||||
print(value)
|
||||
return render_title_template("logviewer.html",title=_(u"Logfile viewer"), perpage_p=perpage_p, perpage = 30,
|
||||
page="logfile")
|
||||
logfiles = {}
|
||||
logfiles[0] = logger.get_logfile(config.config_logfile)
|
||||
logfiles[1] = logger.get_accesslogfile(config.config_access_logfile)
|
||||
return render_title_template("logviewer.html",title=_(u"Logfile viewer"), accesslog_enable=config.config_access_log,
|
||||
logfiles=logfiles, page="logfile")
|
||||
|
||||
|
||||
@admi.route("/ajax/accesslog")
|
||||
@admi.route("/ajax/log/<int:logtype>")
|
||||
@login_required
|
||||
@admin_required
|
||||
def send_logfile():
|
||||
return send_from_directory(constants.BASE_DIR,"access.log")
|
||||
def send_logfile(logtype):
|
||||
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'])
|
||||
|
|
12
cps/cli.py
12
cps/cli.py
|
@ -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')
|
||||
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")
|
||||
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 = 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')
|
||||
TEMPLATES_DIR = os.path.join(BASE_DIR, 'cps', 'templates')
|
||||
TRANSLATIONS_DIR = os.path.join(BASE_DIR, 'cps', 'translations')
|
||||
|
|
|
@ -342,7 +342,7 @@ def setup_db():
|
|||
try:
|
||||
if not os.path.exists(dbpath):
|
||||
raise
|
||||
engine = create_engine('sqlite:///' + dbpath,
|
||||
engine = create_engine('sqlite:///{0}'.format(dbpath),
|
||||
echo=False,
|
||||
isolation_level="SERIALIZABLE",
|
||||
connect_args={'check_same_thread': False})
|
||||
|
|
|
@ -16,10 +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/>.
|
||||
|
||||
from __future__ import division, print_function, unicode_literals
|
||||
import base64
|
||||
|
||||
try:
|
||||
from flask_simpleldap import LDAP, LDAPException
|
||||
from flask_simpleldap import LDAP # , LDAPException
|
||||
ldap_support = True
|
||||
except ImportError:
|
||||
ldap_support = False
|
|
@ -25,6 +25,7 @@ from logging.handlers import RotatingFileHandler
|
|||
|
||||
from .constants import BASE_DIR as _BASE_DIR
|
||||
|
||||
|
||||
ACCESS_FORMATTER_GEVENT = Formatter("%(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_ACCESS_LOG = os.path.join(_BASE_DIR, "access.log")
|
||||
LOG_TO_STDERR = '/dev/stderr'
|
||||
DEFAULT_ACCESS_LEVEL= logging.INFO
|
||||
|
||||
logging.addLevelName(logging.WARNING, "WARN")
|
||||
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)
|
||||
|
||||
|
||||
def setup(log_file, log_level=None, logger=None):
|
||||
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
|
||||
def _absolute_log_file(log_file, default_log_file):
|
||||
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_file
|
||||
return os.path.abspath(log_file)
|
||||
|
||||
return default_log_file
|
||||
|
||||
|
||||
def get_logfile(log_file):
|
||||
return _absolute_log_file(log_file, DEFAULT_LOG_FILE)
|
||||
|
||||
|
||||
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)
|
||||
|
||||
# print ('%r -- %r' % (log_level, log_file))
|
||||
if logger != "access" and logger != "tornado.access":
|
||||
r = logging.root
|
||||
else:
|
||||
r = logging.getLogger(logger)
|
||||
r.propagate = False
|
||||
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:
|
||||
|
@ -115,16 +114,32 @@ def setup(log_file, log_level=None, logger=None):
|
|||
try:
|
||||
file_handler = RotatingFileHandler(log_file, maxBytes=50000, backupCount=2)
|
||||
except IOError:
|
||||
if log_file == default_file:
|
||||
if log_file == DEFAULT_LOG_FILE:
|
||||
raise
|
||||
file_handler = RotatingFileHandler(default_file, maxBytes=50000, backupCount=2)
|
||||
file_handler.setFormatter(formatter)
|
||||
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)
|
||||
|
||||
|
||||
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
|
||||
|
|
|
@ -88,7 +88,7 @@ def register_user_with_oauth(user=None):
|
|||
if len(all_oauth.keys()) == 0:
|
||||
return
|
||||
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:
|
||||
for oauth in all_oauth.keys():
|
||||
# Find this OAuth token in the database, or create it
|
||||
|
|
15
cps/opds.py
15
cps/opds.py
|
@ -31,7 +31,7 @@ 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, ldap
|
||||
from . import logger, config, db, ub, ldap1
|
||||
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
|
||||
|
@ -40,14 +40,14 @@ from .web import common_filters, get_search_results, render_read_books, download
|
|||
opds = Blueprint('opds', __name__)
|
||||
|
||||
log = logger.create()
|
||||
ldap_support = ldap.ldap_supported()
|
||||
ldap_support = ldap1.ldap_supported()
|
||||
|
||||
|
||||
def requires_basic_auth_if_no_ano(f):
|
||||
@wraps(f)
|
||||
def decorated(*args, **kwargs):
|
||||
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
|
||||
if config.config_anonbrowse != 1:
|
||||
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
|
||||
|
||||
|
||||
'''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/")
|
||||
@requires_basic_auth_if_no_ano
|
||||
def feed_index():
|
||||
|
|
212
cps/server.py
212
cps/server.py
|
@ -20,54 +20,60 @@
|
|||
from __future__ import division, print_function, unicode_literals
|
||||
import sys
|
||||
import os
|
||||
import errno
|
||||
import signal
|
||||
import socket
|
||||
import logging
|
||||
|
||||
try:
|
||||
from gevent.pywsgi import WSGIServer
|
||||
from gevent.pool import Pool
|
||||
from gevent import __version__ as geventVersion
|
||||
gevent_present = True
|
||||
from gevent import __version__ as _version
|
||||
VERSION = {'Gevent': 'v' + _version}
|
||||
_GEVENT = True
|
||||
except ImportError:
|
||||
from tornado.wsgi import WSGIContainer
|
||||
from tornado.httpserver import HTTPServer
|
||||
from tornado.ioloop import IOLoop
|
||||
from tornado import version as tornadoVersion
|
||||
from tornado import log as tornadoLog
|
||||
from tornado import options as tornadoOptions
|
||||
gevent_present = False
|
||||
from tornado import version as _version
|
||||
VERSION = {'Tornado': 'v' + _version}
|
||||
_GEVENT = False
|
||||
|
||||
from . import logger, config, global_WorkerThread
|
||||
from . import logger, global_WorkerThread
|
||||
|
||||
|
||||
log = logger.create()
|
||||
|
||||
|
||||
class server:
|
||||
|
||||
wsgiserver = None
|
||||
restart = False
|
||||
app = None
|
||||
access_logger = None
|
||||
class WebServer:
|
||||
|
||||
def __init__(self):
|
||||
signal.signal(signal.SIGINT, self.killServer)
|
||||
signal.signal(signal.SIGTERM, self.killServer)
|
||||
signal.signal(signal.SIGINT, self._killServer)
|
||||
signal.signal(signal.SIGTERM, self._killServer)
|
||||
|
||||
def init_app(self, application):
|
||||
self.app = application
|
||||
self.port = config.config_port
|
||||
self.listening = config.get_config_ipaddress(readable=True) + ":" + str(self.port)
|
||||
self.wsgiserver = None
|
||||
self.access_logger = None
|
||||
if config.config_access_log:
|
||||
if gevent_present:
|
||||
logger.setup(config.config_access_logfile, logger.DEFAULT_ACCESS_LEVEL, "access")
|
||||
self.access_logger = logging.getLogger("access")
|
||||
else:
|
||||
logger.setup(config.config_access_logfile, logger.DEFAULT_ACCESS_LEVEL, "tornado.access")
|
||||
|
||||
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:
|
||||
log_name = "gevent.access" if _GEVENT else "tornado.access"
|
||||
formatter = logger.ACCESS_FORMATTER_GEVENT if _GEVENT else logger.ACCESS_FORMATTER_TORNADO
|
||||
self.access_logger = logger.create_access_log(config.config_access_logfile, log_name, formatter)
|
||||
else:
|
||||
if not _GEVENT:
|
||||
logger.get('tornado.access').disabled = True
|
||||
|
||||
certfile_path = config.get_config_certfile()
|
||||
keyfile_path = config.get_config_keyfile()
|
||||
if certfile_path and keyfile_path:
|
||||
|
@ -79,101 +85,121 @@ class server:
|
|||
log.warning('Cert path: %s', certfile_path)
|
||||
log.warning('Key path: %s', keyfile_path)
|
||||
|
||||
def _make_gevent_socket(self):
|
||||
if config.get_config_ipaddress():
|
||||
return (config.get_config_ipaddress(), self.port)
|
||||
if os.name == 'nt':
|
||||
return ('0.0.0.0', self.port)
|
||||
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):
|
||||
if os.name != 'nt':
|
||||
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':
|
||||
self.listen_address = '0.0.0.0'
|
||||
return (self.listen_address, self.listen_port), self._get_readable_listen_address()
|
||||
|
||||
address = ('', self.listen_port)
|
||||
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:
|
||||
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
|
||||
log.warning('Unable to listen on "", trying on IPv4 only...')
|
||||
output = self._get_readable_listen_address(False)
|
||||
sock = WSGIServer.get_listener(address, family=socket.AF_INET)
|
||||
return sock, output
|
||||
|
||||
def start_gevent(self):
|
||||
def _start_gevent(self):
|
||||
ssl_args = self.ssl_args or {}
|
||||
log.info('Starting Gevent server')
|
||||
|
||||
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.serve_forever()
|
||||
except socket.error:
|
||||
try:
|
||||
log.info('Unable to listen on "", trying on "0.0.0.0" only...')
|
||||
self.wsgiserver = WSGIServer(('0.0.0.0', config.config_port), 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:
|
||||
log.exception("Unknown error while starting gevent")
|
||||
sys.exit(0)
|
||||
finally:
|
||||
if self.unix_socket_file:
|
||||
os.remove(self.unix_socket_file)
|
||||
self.unix_socket_file = None
|
||||
|
||||
def start_tornado(self):
|
||||
log.info('Starting Tornado server on %s', self.listening)
|
||||
def _start_tornado(self):
|
||||
log.info('Starting Tornado server on %s', self._get_readable_listen_address())
|
||||
|
||||
try:
|
||||
# Max Buffersize set to 200MB )
|
||||
http_server = HTTPServer(WSGIContainer(self.app),
|
||||
max_buffer_size = 209700000,
|
||||
ssl_options=self.ssl_args)
|
||||
address = config.get_config_ipaddress()
|
||||
http_server.listen(self.port, address)
|
||||
http_server.listen(self.listen_port, self.listen_address)
|
||||
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:
|
||||
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:
|
||||
if _GEVENT:
|
||||
# leave subprocess out to allow forking for fetchers and processors
|
||||
self.start_gevent()
|
||||
self._start_gevent()
|
||||
else:
|
||||
self.start_tornado()
|
||||
|
||||
if self.restart is True:
|
||||
log.info("Performing restart of Calibre-Web")
|
||||
self._start_tornado()
|
||||
except Exception as ex:
|
||||
log.error("Error starting server: %s", ex)
|
||||
print("Error starting server: %s" % ex)
|
||||
return False
|
||||
finally:
|
||||
self.wsgiserver = None
|
||||
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:
|
||||
|
||||
if not self.restart:
|
||||
log.info("Performing shutdown of Calibre-Web")
|
||||
global_WorkerThread.stop()
|
||||
sys.exit(0)
|
||||
return True
|
||||
|
||||
def setRestartTyp(self,starttyp):
|
||||
self.restart = starttyp
|
||||
log.info("Performing restart of Calibre-Web")
|
||||
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):
|
||||
self.stopServer()
|
||||
def _killServer(self, signum, frame):
|
||||
self.stop()
|
||||
|
||||
def stopServer(self):
|
||||
def stop(self, restart=False):
|
||||
self.restart = restart
|
||||
if self.wsgiserver:
|
||||
if gevent_present:
|
||||
if _GEVENT:
|
||||
self.wsgiserver.close()
|
||||
else:
|
||||
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;
|
||||
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)
|
||||
*/
|
||||
/* global _, i18nMsg, tinymce */
|
||||
var dbResults = [];
|
||||
// var dbResults = [];
|
||||
var ggResults = [];
|
||||
|
||||
$(function () {
|
||||
var msg = i18nMsg;
|
||||
/*var douban = "https://api.douban.com";
|
||||
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 ggDone = false;
|
||||
|
||||
|
@ -56,12 +56,10 @@ $(function () {
|
|||
if (showFlag === 1) {
|
||||
$("#meta-info").html("<ul id=\"book-list\" class=\"media-list\"></ul>");
|
||||
}
|
||||
if (ggDone && dbDone) {
|
||||
if (!ggResults && !dbResults) {
|
||||
if (!ggDone) {
|
||||
$("#meta-info").html("<p class=\"text-danger\">" + msg.no_result + "</p>");
|
||||
return;
|
||||
}
|
||||
}
|
||||
if (ggDone && ggResults.length > 0) {
|
||||
ggResults.forEach(function(result) {
|
||||
var book = {
|
||||
|
@ -137,7 +135,10 @@ $(function () {
|
|||
dataType: "jsonp",
|
||||
jsonp: "callback",
|
||||
success: function success(data) {
|
||||
if ("items" in data) {
|
||||
ggResults = data.items;
|
||||
ggDone = true;
|
||||
}
|
||||
},
|
||||
complete: function complete() {
|
||||
ggDone = true;
|
||||
|
|
|
@ -1,114 +1,68 @@
|
|||
var threadregexp = /(?:^| - )(\[[^\]]*\]):/;
|
||||
/* This file is part of the Calibre-Web (https://github.com/janeczku/calibre-web)
|
||||
* Copyright (C) 2018 OzzieIsaacs
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
var colors = ["#ffa", "#aaf", "#afa", "#aff", "#faf", "#aaa", "#fd8", "#f80", "#4df", "#4fc", "#76973c", "#7e56d8", "#99593d", "#37778a", "#4068fc"];
|
||||
var screenlines = 1;
|
||||
// Upon loading load the logfile for the first option (event log)
|
||||
$(function() {
|
||||
init(0);
|
||||
});
|
||||
|
||||
var file = null;
|
||||
var text;
|
||||
|
||||
var current, nextFilterId = 1;
|
||||
var shl = null;
|
||||
var hl = [];
|
||||
var groupwith = false;
|
||||
var filterswitch = true;
|
||||
|
||||
var selectedlineid = -1;
|
||||
var selectedthread = null;
|
||||
var reachedbottom = false;
|
||||
var reachedtop = false;
|
||||
// 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();
|
||||
init(element);
|
||||
});
|
||||
|
||||
|
||||
function wheelscroll(event)
|
||||
{
|
||||
renderincremental(event.deltaY);
|
||||
}
|
||||
|
||||
function keypress(event)
|
||||
{
|
||||
if (event.key == "PageDown") {
|
||||
_render(screenlines - 1);
|
||||
event.preventDefault();
|
||||
}
|
||||
if (event.key == "PageUp") {
|
||||
_render(-(screenlines - 1));
|
||||
event.preventDefault();
|
||||
}
|
||||
if (event.key == "Home" && event.ctrlKey) {
|
||||
selectedlineid = 0;
|
||||
render();
|
||||
event.preventDefault();
|
||||
}
|
||||
if (event.key == "End" && event.ctrlKey) {
|
||||
selectedlineid = text.length - 1;
|
||||
render();
|
||||
event.preventDefault();
|
||||
}
|
||||
if (event.key == "ArrowUp") {
|
||||
renderincremental(-1);
|
||||
event.preventDefault();
|
||||
}
|
||||
if (event.key == "ArrowDown") {
|
||||
renderincremental(1);
|
||||
event.preventDefault();
|
||||
}
|
||||
}
|
||||
|
||||
function init(filename) {
|
||||
document.addEventListener("wheel", wheelscroll, false);
|
||||
document.addEventListener("keypress", keypress, false);
|
||||
window.addEventListener("resize", resize, false);
|
||||
|
||||
_resize();
|
||||
|
||||
var s = document.getElementById("search");
|
||||
s.value = "";
|
||||
selectfilter(0);
|
||||
reload(filename);
|
||||
}
|
||||
|
||||
function _resize()
|
||||
{
|
||||
// Handle reloading of the log file and display the content
|
||||
function init(logType) {
|
||||
var d = document.getElementById("renderer");
|
||||
var t = document.getElementById("toobar");
|
||||
screenlines = Math.floor((window.innerHeight - t.offsetHeight) / d.firstChild.offsetHeight) - 1;
|
||||
}
|
||||
d.innerHTML = "loading ...";
|
||||
|
||||
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';
|
||||
/*var r = new XMLHttpRequest();
|
||||
r.open("GET", "/ajax/log/" + logType, true);
|
||||
r.responseType = "text";
|
||||
r.onload = function() {
|
||||
console.log("prepare");
|
||||
prepare(r.responseText);
|
||||
if (selectedlineid > text.length) {
|
||||
selectedlineid = -1;
|
||||
var text;
|
||||
text = (r.responseText).split("\n");
|
||||
$("#renderer").text("");
|
||||
console.log(text.length);
|
||||
for (var i = 0; i < text.length; i++) {
|
||||
$("#renderer").append( "<div>" + _sanitize(text[i]) + "</div>" );
|
||||
}
|
||||
console.log("render");
|
||||
render();
|
||||
};
|
||||
r.send();
|
||||
r.send();*/
|
||||
$.ajax({
|
||||
url: "/ajax/log/" + logType,
|
||||
datatype: 'text',
|
||||
cache: false
|
||||
})
|
||||
.done( function(data) {
|
||||
var text;
|
||||
$("#renderer").text("");
|
||||
text = (data).split("\n");
|
||||
console.log(text.length);
|
||||
for (var i = 0; i < text.length; i++) {
|
||||
$("#renderer").append( "<div>" + _sanitize(text[i]) + "</div>" );
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function _sanitize(t)
|
||||
{
|
||||
|
||||
function _sanitize(t) {
|
||||
t = t
|
||||
.replace(/&/g, "&")
|
||||
.replace(/ /g, " ")
|
||||
|
@ -118,492 +72,3 @@ function _sanitize(t)
|
|||
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("-> 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="col">
|
||||
<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="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>
|
||||
|
|
|
@ -1,141 +1,15 @@
|
|||
{% extends "layout.html" %}
|
||||
{% block body %}
|
||||
{% block header %}
|
||||
<style>
|
||||
|
||||
html, body {
|
||||
margin: 0;
|
||||
height: 100%;
|
||||
overflow-y: hidden
|
||||
}
|
||||
|
||||
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 %} />
|
||||
<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>
|
||||
<div id="log_group" class="inputs">
|
||||
<div><input type="radio" name="log_radio" id="log1" value="0" checked>
|
||||
<label for="log0">{{_('Show Calibre-Web log')}}</label> {{logfiles[0]}}</div>
|
||||
{% if accesslog_enable %}
|
||||
<div><input type="radio" name="log_radio" id="log0" value="1">
|
||||
<label for="log1">{{_('Show access log')}}</label> {{logfiles[1]}}</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
<div id="renderer" class="log"></div>
|
||||
{% endblock %}
|
||||
{% block js %}
|
||||
<script src="{{ url_for('static', filename='js/logviewer.js') }}"></script>
|
||||
<script>
|
||||
$(function(){
|
||||
init('access.log');
|
||||
});
|
||||
|
||||
</script>
|
||||
|
||||
{% endblock %}
|
||||
|
|
Binary file not shown.
File diff suppressed because it is too large
Load Diff
Binary file not shown.
File diff suppressed because it is too large
Load Diff
Binary file not shown.
File diff suppressed because it is too large
Load Diff
Binary file not shown.
File diff suppressed because it is too large
Load Diff
Binary file not shown.
File diff suppressed because it is too large
Load Diff
Binary file not shown.
File diff suppressed because it is too large
Load Diff
Binary file not shown.
File diff suppressed because it is too large
Load Diff
Binary file not shown.
File diff suppressed because it is too large
Load Diff
Binary file not shown.
File diff suppressed because it is too large
Load Diff
Binary file not shown.
File diff suppressed because it is too large
Load Diff
Binary file not shown.
File diff suppressed because it is too large
Load Diff
Binary file not shown.
File diff suppressed because it is too large
Load Diff
Binary file not shown.
File diff suppressed because it is too large
Load Diff
74
cps/ub.py
74
cps/ub.py
|
@ -40,18 +40,12 @@ 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'''
|
||||
|
||||
from . import constants, logger, cli
|
||||
|
||||
|
||||
session = None
|
||||
|
||||
|
||||
engine = create_engine('sqlite:///{0}'.format(cli.settingspath), echo=False)
|
||||
engine = create_engine(u'sqlite:///{0}'.format(cli.settingspath), echo=False)
|
||||
Base = declarative_base()
|
||||
|
||||
|
||||
|
@ -171,18 +165,12 @@ class UserBase:
|
|||
def __repr__(self):
|
||||
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
|
||||
# User Base (all access methods are declared there)
|
||||
class User(UserBase, Base):
|
||||
__tablename__ = 'user'
|
||||
__table_args__ = {'sqlite_autoincrement': True}
|
||||
|
||||
id = Column(Integer, primary_key=True)
|
||||
nickname = Column(String(64), unique=True)
|
||||
|
@ -476,35 +464,22 @@ class Config:
|
|||
def get_config_certfile(self):
|
||||
if cli.certfilepath:
|
||||
return cli.certfilepath
|
||||
else:
|
||||
if cli.certfilepath is "":
|
||||
return None
|
||||
else:
|
||||
return self.config_certfile
|
||||
|
||||
def get_config_keyfile(self):
|
||||
if cli.keyfilepath:
|
||||
return cli.keyfilepath
|
||||
else:
|
||||
if cli.certfilepath is "":
|
||||
return None
|
||||
else:
|
||||
return self.config_keyfile
|
||||
|
||||
def get_config_ipaddress(self, readable=False):
|
||||
if not readable:
|
||||
if cli.ipadress:
|
||||
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 get_config_ipaddress(self):
|
||||
return cli.ipadress or ""
|
||||
|
||||
def get_ipaddress_type(self):
|
||||
return cli.ipv6
|
||||
|
||||
def _has_role(self, 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_logfile` String DEFAULT ''")
|
||||
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
|
||||
conn = engine.connect()
|
||||
|
@ -795,13 +798,6 @@ def clean_database():
|
|||
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():
|
||||
settings = Settings()
|
||||
settings.mail_server = "mail.example.com"
|
||||
|
|
|
@ -33,7 +33,7 @@ from tempfile import gettempdir
|
|||
from babel.dates import format_datetime
|
||||
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()
|
||||
|
@ -95,8 +95,7 @@ class Updater(threading.Thread):
|
|||
self.status = 6
|
||||
log.debug(u'Preparing restart of server')
|
||||
time.sleep(2)
|
||||
Server.setRestartTyp(True)
|
||||
Server.stopServer()
|
||||
web_server.stop(True)
|
||||
self.status = 7
|
||||
time.sleep(2)
|
||||
except requests.exceptions.HTTPError as ex:
|
||||
|
|
|
@ -41,7 +41,7 @@ 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, ldap
|
||||
from . import constants, logger, isoLanguages, ldap1
|
||||
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, \
|
||||
|
@ -52,7 +52,7 @@ from .pagination import Pagination
|
|||
from .redirect import redirect_back
|
||||
|
||||
feature_support = dict()
|
||||
feature_support['ldap'] = ldap.ldap_supported()
|
||||
feature_support['ldap'] = ldap1.ldap_supported()
|
||||
|
||||
try:
|
||||
from .oauth_bb import oauth_check, register_user_with_oauth, logout_oauth_user, get_oauth_status
|
||||
|
@ -1093,7 +1093,7 @@ def login():
|
|||
.first()
|
||||
if config.config_login_type == 1 and user and feature_support['ldap']:
|
||||
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)
|
||||
flash(_(u"you are now logged in as: '%(nickname)s'", nickname=user.nickname),
|
||||
category="success")
|
||||
|
|
|
@ -326,6 +326,8 @@ class WorkerThread(threading.Thread):
|
|||
nextline = p.stdout.readline()
|
||||
if os.name == 'nt' and sys.version_info < (3, 0):
|
||||
nextline = nextline.decode('windows-1252')
|
||||
elif os.name == 'posix' and sys.version_info < (3, 0):
|
||||
nextline = nextline.decode('utf-8')
|
||||
log.debug(nextline.strip('\r\n'))
|
||||
# parse progress string from calibre-converter
|
||||
progress = re.search("(\d+)%\s.*", nextline)
|
||||
|
|
712
messages.pot
712
messages.pot
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
Loading…
Reference in New Issue
Block a user