2
0
mirror of https://github.com/janeczku/calibre-web synced 2024-11-02 09:41:02 +00:00

Merge remote-tracking branch 'pwr/config_sql' into Develop

This commit is contained in:
Ozzieisaacs 2019-07-16 21:15:06 +02:00
commit 929f32335f
17 changed files with 200 additions and 232 deletions

1
.gitignore vendored
View File

@ -14,6 +14,7 @@ build/
*.egg-info/ *.egg-info/
.installed.cfg .installed.cfg
*.egg *.egg
.pylint.d
# calibre-web # calibre-web
*.db *.db

View File

@ -34,8 +34,9 @@ from flask_login import LoginManager
from flask_babel import Babel from flask_babel import Babel
from flask_principal import Principal from flask_principal import Principal
from . import logger, cache_buster, cli, config_sql, ub from . import logger, cache_buster, cli, config_sql, ub, db, services
from .reverseproxy import ReverseProxied from .reverseproxy import ReverseProxied
from .server import WebServer
mimetypes.init() mimetypes.init()
@ -64,15 +65,10 @@ lm.anonymous_user = ub.Anonymous
ub.init_db(cli.settingspath) ub.init_db(cli.settingspath)
# pylint: disable=no-member
config = config_sql.load_configuration(ub.session) config = config_sql.load_configuration(ub.session)
from . import db, services
searched_ids = {} searched_ids = {}
from .worker import WorkerThread
global_WorkerThread = WorkerThread()
from .server import WebServer
web_server = WebServer() web_server = WebServer()
babel = Babel() babel = Babel()
@ -108,7 +104,6 @@ def create_app():
if services.goodreads: if services.goodreads:
services.goodreads.connect(config.config_goodreads_api_key, config.config_goodreads_api_secret, config.config_use_goodreads) services.goodreads.connect(config.config_goodreads_api_key, config.config_goodreads_api_secret, config.config_use_goodreads)
global_WorkerThread.start()
return app return app
@babel.localeselector @babel.localeselector

View File

@ -1,4 +1,3 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
# This file is part of the Calibre-Web (https://github.com/janeczku/calibre-web) # This file is part of the Calibre-Web (https://github.com/janeczku/calibre-web)
@ -23,56 +22,46 @@
from __future__ import division, print_function, unicode_literals from __future__ import division, print_function, unicode_literals
import sys import sys
import requests import sqlite3
from collections import OrderedDict
from flask import Blueprint import babel, pytz, requests, sqlalchemy
from flask import __version__ as flaskVersion import werkzeug, flask, flask_login, flask_principal, jinja2
from flask_babel import gettext as _ from flask_babel import gettext as _
from flask_principal import __version__ as flask_principalVersion
from flask_login import login_required
try:
from flask_login import __version__ as flask_loginVersion
except ImportError:
from flask_login.__about__ import __version__ as flask_loginVersion
from werkzeug import __version__ as werkzeugVersion
from babel import __version__ as babelVersion from . import db, converter, uploader, server, isoLanguages
from jinja2 import __version__ as jinja2Version
from pytz import __version__ as pytzVersion
from sqlalchemy import __version__ as sqlalchemyVersion
from . import db, converter, uploader
from .isoLanguages import __version__ as iso639Version
from .server import VERSION as serverVersion
from .web import render_title_template from .web import render_title_template
about = Blueprint('about', __name__) about = flask.Blueprint('about', __name__)
_VERSIONS = OrderedDict(
Python=sys.version,
WebServer=server.VERSION,
Flask=flask.__version__,
Flask_Login=flask_login.__version__,
Flask_Principal=flask_principal.__version__,
Werkzeug=werkzeug.__version__,
Babel=babel.__version__,
Jinja2=jinja2.__version__,
Requests=requests.__version__,
SqlAlchemy=sqlalchemy.__version__,
pySqlite=sqlite3.version,
SQLite=sqlite3.sqlite_version,
iso639=isoLanguages.__version__,
pytz=pytz.__version__,
)
_VERSIONS.update(uploader.get_versions())
@about.route("/stats") @about.route("/stats")
@login_required @flask_login.login_required
def stats(): def stats():
counter = db.session.query(db.Books).count() counter = db.session.query(db.Books).count()
authors = db.session.query(db.Authors).count() authors = db.session.query(db.Authors).count()
categorys = db.session.query(db.Tags).count() categorys = db.session.query(db.Tags).count()
series = db.session.query(db.Series).count() series = db.session.query(db.Series).count()
versions = uploader.get_versions() _VERSIONS['ebook converter'] = _(converter.get_version())
versions['Babel'] = 'v' + babelVersion return render_title_template('stats.html', bookcounter=counter, authorcounter=authors, versions=_VERSIONS,
versions['Sqlalchemy'] = 'v' + sqlalchemyVersion
versions['Werkzeug'] = 'v' + werkzeugVersion
versions['Jinja2'] = 'v' + jinja2Version
versions['Flask'] = 'v' + flaskVersion
versions['Flask Login'] = 'v' + flask_loginVersion
versions['Flask Principal'] = 'v' + flask_principalVersion
versions['Iso 639'] = 'v' + iso639Version
versions['pytz'] = 'v' + pytzVersion
versions['Requests'] = 'v' + requests.__version__
versions['pySqlite'] = 'v' + db.session.bind.dialect.dbapi.version
versions['Sqlite'] = 'v' + db.session.bind.dialect.dbapi.sqlite_version
versions.update(converter.versioncheck())
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") categorycounter=categorys, seriecounter=series, title=_(u"Statistics"), page="stat")

View File

@ -27,10 +27,6 @@ import base64
import json import json
import time import time
from datetime import datetime, timedelta from datetime import datetime, timedelta
# try:
# from imp import reload
# except ImportError:
# pass
from babel import Locale as LC from babel import Locale as LC
from babel.dates import format_datetime from babel.dates import format_datetime

View File

@ -1,7 +1,5 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
# This file is part of the Calibre-Web (https://github.com/janeczku/calibre-web) # This file is part of the Calibre-Web (https://github.com/janeczku/calibre-web)
# Copyright (C) 2018 OzzieIsaacs # Copyright (C) 2018 OzzieIsaacs
# #
@ -22,50 +20,17 @@ from __future__ import division, print_function, unicode_literals
import sys import sys
import os import os
import argparse import argparse
import socket
from .constants import CONFIG_DIR as _CONFIG_DIR from .constants import CONFIG_DIR as _CONFIG_DIR
from .constants import STABLE_VERSION as _STABLE_VERSION from .constants import STABLE_VERSION as _STABLE_VERSION
from .constants import NIGHTLY_VERSION as _NIGHTLY_VERSION from .constants import NIGHTLY_VERSION as _NIGHTLY_VERSION
VALID_CHARACTERS = 'ABCDEFabcdef:0123456789'
ipv6 = False
def version_info(): def version_info():
if _NIGHTLY_VERSION[1].startswith('$Format'): if _NIGHTLY_VERSION[1].startswith('$Format'):
return "Calibre-Web version: %s - unkown git-clone" % _STABLE_VERSION['version'] return "Calibre-Web version: %s - unkown git-clone" % _STABLE_VERSION['version']
else: return "Calibre-Web version: %s -%s" % (_STABLE_VERSION['version'], _NIGHTLY_VERSION[1])
return "Calibre-Web version: %s -%s" % (_STABLE_VERSION['version'],_NIGHTLY_VERSION[1])
def validate_ip4(address):
address_list = address.split('.')
if len(address_list) != 4:
return False
for val in address_list:
if not val.isdigit():
return False
i = int(val)
if i < 0 or i > 255:
return False
return True
def validate_ip6(address):
address_list = address.split(':')
return (
len(address_list) == 8
and all(len(current) <= 4 for current in address_list)
and all(current in VALID_CHARACTERS for current in address)
)
def validate_ip(address):
if validate_ip4(address) or ipv6:
return address
print("IP address is invalid. Exiting")
sys.exit(1)
parser = argparse.ArgumentParser(description='Calibre Web is a web app' parser = argparse.ArgumentParser(description='Calibre Web is a web app'
@ -95,8 +60,8 @@ if sys.version_info < (3, 0):
args.s = args.s.decode('utf-8') 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")
# handle and check parameter for ssl encryption # handle and check parameter for ssl encryption
certfilepath = None certfilepath = None
@ -108,7 +73,7 @@ if args.c:
print("Certfilepath is invalid. Exiting...") print("Certfilepath is invalid. Exiting...")
sys.exit(1) sys.exit(1)
if args.c is "": if args.c == "":
certfilepath = "" certfilepath = ""
if args.k: if args.k:
@ -122,15 +87,26 @@ if (args.k and not args.c) or (not args.k and args.c):
print("Certfile and Keyfile have to be used together. Exiting...") print("Certfile and Keyfile have to be used together. Exiting...")
sys.exit(1) sys.exit(1)
if args.k is "": if args.k == "":
keyfilepath = "" keyfilepath = ""
# handle and check ipadress argument # handle and check ipadress argument
if args.i: ipadress = args.i or None
ipv6 = validate_ip6(args.i) if ipadress:
ipadress = validate_ip(args.i) try:
else: # try to parse the given ip address with socket
ipadress = None if hasattr(socket, 'inet_pton'):
if ':' in ipadress:
socket.inet_pton(socket.AF_INET6, ipadress)
else:
socket.inet_pton(socket.AF_INET, ipadress)
else:
# on windows python < 3.4, inet_pton is not available
# inet_atom only handles IPv4 addresses
socket.inet_aton(ipadress)
except socket.error as err:
print(ipadress, ':', err)
sys.exit(1)
# handle and check user password argument # handle and check user password argument
user_password = args.s or None user_password = args.s or None

View File

@ -52,7 +52,7 @@ class _Settings(_Base):
config_random_books = Column(Integer, default=4) config_random_books = Column(Integer, default=4)
config_authors_max = Column(Integer, default=0) config_authors_max = Column(Integer, default=0)
config_read_column = Column(Integer, default=0) config_read_column = Column(Integer, default=0)
config_title_regex = Column(String, default=u'^(A|The|An|Der|Die|Das|Den|Ein|Eine|Einen|Dem|Des|Einem|Eines)\s+') config_title_regex = Column(String, default=r'^(A|The|An|Der|Die|Das|Den|Ein|Eine|Einen|Dem|Des|Einem|Eines)\s+')
config_log_level = Column(SmallInteger, default=logger.DEFAULT_LOG_LEVEL) config_log_level = Column(SmallInteger, default=logger.DEFAULT_LOG_LEVEL)
config_access_log = Column(SmallInteger, default=0) config_access_log = Column(SmallInteger, default=0)
config_uploading = Column(SmallInteger, default=0) config_uploading = Column(SmallInteger, default=0)
@ -106,7 +106,6 @@ class _Settings(_Base):
# Class holds all application specific settings in calibre-web # Class holds all application specific settings in calibre-web
class _ConfigSQL(object): class _ConfigSQL(object):
# pylint: disable=no-member
def __init__(self, session): def __init__(self, session):
self._session = session self._session = session
self._settings = None self._settings = None
@ -137,9 +136,6 @@ class _ConfigSQL(object):
def get_config_ipaddress(self): def get_config_ipaddress(self):
return cli.ipadress or "" return cli.ipadress or ""
def get_ipaddress_type(self):
return cli.ipv6
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)
@ -226,8 +222,14 @@ class _ConfigSQL(object):
if self.config_google_drive_watch_changes_response: if self.config_google_drive_watch_changes_response:
self.config_google_drive_watch_changes_response = json.loads(self.config_google_drive_watch_changes_response) self.config_google_drive_watch_changes_response = json.loads(self.config_google_drive_watch_changes_response)
self.db_configured = (self.config_calibre_dir and
(not self.config_use_google_drive or os.path.exists(self.config_calibre_dir + '/metadata.db'))) have_metadata_db = bool(self.config_calibre_dir)
if have_metadata_db:
if not self.config_use_google_drive:
db_file = os.path.join(self.config_calibre_dir, 'metadata.db')
have_metadata_db = os.path.isfile(db_file)
self.db_configured = have_metadata_db
logger.setup(self.config_logfile, self.config_log_level) logger.setup(self.config_logfile, self.config_log_level)
def save(self): def save(self):
@ -264,6 +266,7 @@ def _migrate_table(session, orm_class):
log.debug("%s: %s", column_name, err) log.debug("%s: %s", column_name, err)
column_default = "" if column.default is None else ("DEFAULT %r" % column.default.arg) column_default = "" if column.default is None else ("DEFAULT %r" % column.default.arg)
alter_table = "ALTER TABLE %s ADD COLUMN `%s` %s %s" % (orm_class.__tablename__, column_name, column.type, column_default) alter_table = "ALTER TABLE %s ADD COLUMN `%s` %s %s" % (orm_class.__tablename__, column_name, column.type, column_default)
log.debug(alter_table)
session.execute(alter_table) session.execute(alter_table)
changed = True changed = True

View File

@ -128,4 +128,3 @@ NIGHTLY_VERSION[1] = '$Format:%cI$'
# clean-up the module namespace # clean-up the module namespace
del sys, os, namedtuple del sys, os, namedtuple

View File

@ -1,4 +1,3 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
# This file is part of the Calibre-Web (https://github.com/janeczku/calibre-web) # This file is part of the Calibre-Web (https://github.com/janeczku/calibre-web)
@ -21,41 +20,36 @@ from __future__ import division, print_function, unicode_literals
import os import os
import re import re
from flask_babel import gettext as _ from . import config, logger
from . import config
from .subproc_wrapper import process_wait from .subproc_wrapper import process_wait
def versionKindle(): log = logger.create()
versions = _(u'not installed')
if os.path.exists(config.config_converterpath): _NOT_CONFIGURED = 'not configured'
_NOT_INSTALLED = 'not installed'
_EXECUTION_ERROR = 'Execution permissions missing'
def _get_command_version(path, pattern, argument=None):
if os.path.exists(path):
command = [path]
if argument:
command.append(argument)
try: try:
for lines in process_wait(config.config_converterpath): for line in process_wait(command):
if re.search('Amazon kindlegen\(', lines): if re.search(pattern, line):
versions = lines return line
except Exception: except Exception as ex:
versions = _(u'Excecution permissions missing') log.warning("%s: %s", path, ex)
return {'kindlegen' : versions} return _EXECUTION_ERROR
return _NOT_INSTALLED
def versionCalibre(): def get_version():
versions = _(u'not installed') version = None
if os.path.exists(config.config_converterpath):
try:
for lines in process_wait([config.config_converterpath, '--version']):
if re.search('ebook-convert.*\(calibre', lines):
versions = lines
except Exception:
versions = _(u'Excecution permissions missing')
return {'Calibre converter' : versions}
def versioncheck():
if config.config_ebookconverter == 1: if config.config_ebookconverter == 1:
return versionKindle() version = _get_command_version(config.config_converterpath, r'Amazon kindlegen\(')
elif config.config_ebookconverter == 2: elif config.config_ebookconverter == 2:
return versionCalibre() version = _get_command_version(config.config_converterpath, r'ebook-convert.*\(calibre', '--version')
else: return version or _NOT_CONFIGURED
return {'ebook_converter':_(u'not configured')}

View File

@ -407,16 +407,14 @@ def setup_db(config):
def dispose(): def dispose():
global session global session
engine = None old_session = session
if session: session = None
engine = session.bind if old_session:
try: session.close() try: old_session.close()
except: pass
session = None
if engine:
try: engine.dispose()
except: pass except: pass
if old_session.bind:
try: old_session.bind.dispose()
except: pass
for attr in list(Books.__dict__.keys()): for attr in list(Books.__dict__.keys()):
if attr.startswith("custom_column_"): if attr.startswith("custom_column_"):

View File

@ -30,12 +30,12 @@ from uuid import uuid4
from flask import Blueprint, request, flash, redirect, url_for, abort, Markup, Response from flask import Blueprint, request, flash, redirect, url_for, abort, Markup, Response
from flask_babel import gettext as _ from flask_babel import gettext as _
from flask_login import current_user from flask_login import current_user, login_required
from . import constants, logger, isoLanguages, gdriveutils, uploader, helper from . import constants, logger, isoLanguages, gdriveutils, uploader, helper
from . import config, get_locale, db, ub, global_WorkerThread from . import config, get_locale, db, ub, worker
from .helper import order_authors, common_filters from .helper import order_authors, common_filters
from .web import login_required_if_no_ano, render_title_template, edit_required, upload_required, login_required from .web import login_required_if_no_ano, render_title_template, edit_required, upload_required
editbook = Blueprint('editbook', __name__) editbook = Blueprint('editbook', __name__)
@ -358,7 +358,7 @@ def upload_single_file(request, book, book_id):
# Queue uploader info # Queue uploader info
uploadText=_(u"File format %(ext)s added to %(book)s", ext=file_ext.upper(), book=book.title) uploadText=_(u"File format %(ext)s added to %(book)s", ext=file_ext.upper(), book=book.title)
global_WorkerThread.add_upload(current_user.nickname, worker.add_upload(current_user.nickname,
"<a href=\"" + url_for('web.show_book', book_id=book.id) + "\">" + uploadText + "</a>") "<a href=\"" + url_for('web.show_book', book_id=book.id) + "\">" + uploadText + "</a>")
@ -667,7 +667,7 @@ def upload():
if error: if error:
flash(error, category="error") flash(error, category="error")
uploadText=_(u"File %(file)s uploaded", file=book.title) uploadText=_(u"File %(file)s uploaded", file=book.title)
global_WorkerThread.add_upload(current_user.nickname, worker.add_upload(current_user.nickname,
"<a href=\"" + url_for('web.show_book', book_id=book.id) + "\">" + uploadText + "</a>") "<a href=\"" + url_for('web.show_book', book_id=book.id) + "\">" + uploadText + "</a>")
# create data for displaying display Full language name instead of iso639.part3language # create data for displaying display Full language name instead of iso639.part3language

View File

@ -60,7 +60,7 @@ try:
except ImportError: except ImportError:
use_PIL = False use_PIL = False
from . import logger, config, global_WorkerThread, get_locale, db, ub, isoLanguages from . import logger, config, get_locale, db, ub, isoLanguages, worker
from . import gdriveutils as gd from . import gdriveutils as gd
from .constants import STATIC_DIR as _STATIC_DIR from .constants import STATIC_DIR as _STATIC_DIR
from .pagination import Pagination from .pagination import Pagination
@ -112,7 +112,7 @@ def convert_book_format(book_id, calibrepath, old_book_format, new_book_format,
text = (u"%s -> %s: %s" % (old_book_format, new_book_format, book.title)) text = (u"%s -> %s: %s" % (old_book_format, new_book_format, book.title))
settings['old_book_format'] = old_book_format settings['old_book_format'] = old_book_format
settings['new_book_format'] = new_book_format settings['new_book_format'] = new_book_format
global_WorkerThread.add_convert(file_path, book.id, user_id, text, settings, kindle_mail) worker.add_convert(file_path, book.id, user_id, text, settings, kindle_mail)
return None return None
else: else:
error_message = _(u"%(format)s not found: %(fn)s", error_message = _(u"%(format)s not found: %(fn)s",
@ -121,9 +121,9 @@ def convert_book_format(book_id, calibrepath, old_book_format, new_book_format,
def send_test_mail(kindle_mail, user_name): def send_test_mail(kindle_mail, user_name):
global_WorkerThread.add_email(_(u'Calibre-Web test e-mail'),None, None, config.get_mail_settings(), worker.add_email(_(u'Calibre-Web test e-mail'), None, None,
kindle_mail, user_name, _(u"Test e-mail"), config.get_mail_settings(), kindle_mail, user_name,
_(u'This e-mail has been sent via Calibre-Web.')) _(u"Test e-mail"), _(u'This e-mail has been sent via Calibre-Web.'))
return return
@ -138,8 +138,9 @@ def send_registration_mail(e_mail, user_name, default_password, resend=False):
text += "Don't forget to change your password after first login.\r\n" text += "Don't forget to change your password after first login.\r\n"
text += "Sincerely\r\n\r\n" text += "Sincerely\r\n\r\n"
text += "Your Calibre-Web team" text += "Your Calibre-Web team"
global_WorkerThread.add_email(_(u'Get Started with Calibre-Web'),None, None, config.get_mail_settings(), worker.add_email(_(u'Get Started with Calibre-Web'), None, None,
e_mail, None, _(u"Registration e-mail for user: %(name)s", name=user_name), text) config.get_mail_settings(), e_mail, None,
_(u"Registration e-mail for user: %(name)s", name=user_name), text)
return return
@ -207,15 +208,15 @@ def send_mail(book_id, book_format, convert, kindle_mail, calibrepath, user_id):
if convert: if convert:
# returns None if success, otherwise errormessage # returns None if success, otherwise errormessage
return convert_book_format(book_id, calibrepath, u'epub', book_format.lower(), user_id, kindle_mail) return convert_book_format(book_id, calibrepath, u'epub', book_format.lower(), user_id, kindle_mail)
else:
for entry in iter(book.data): for entry in iter(book.data):
if entry.format.upper() == book_format.upper(): if entry.format.upper() == book_format.upper():
result = entry.name + '.' + book_format.lower() converted_file_name = entry.name + '.' + book_format.lower()
global_WorkerThread.add_email(_(u"Send to Kindle"), book.path, result, config.get_mail_settings(), worker.add_email(_(u"Send to Kindle"), book.path, converted_file_name,
kindle_mail, user_id, _(u"E-mail: %(book)s", book=book.title), config.get_mail_settings(), kindle_mail, user_id,
_(u'This e-mail has been sent via Calibre-Web.')) _(u"E-mail: %(book)s", book=book.title), _(u'This e-mail has been sent via Calibre-Web.'))
return return
return _(u"The requested file could not be read. Maybe wrong permissions?") return _(u"The requested file could not be read. Maybe wrong permissions?")
def get_valid_filename(value, replace_whitespace=True): def get_valid_filename(value, replace_whitespace=True):
@ -232,7 +233,7 @@ def get_valid_filename(value, replace_whitespace=True):
value = value.replace(u'§', u'SS') value = value.replace(u'§', u'SS')
value = value.replace(u'ß', u'ss') value = value.replace(u'ß', u'ss')
value = unicodedata.normalize('NFKD', value) value = unicodedata.normalize('NFKD', value)
re_slugify = re.compile('[\W\s-]', re.UNICODE) re_slugify = re.compile(r'[\W\s-]', re.UNICODE)
if isinstance(value, str): # Python3 str, Python2 unicode if isinstance(value, str): # Python3 str, Python2 unicode
value = re_slugify.sub('', value).strip() value = re_slugify.sub('', value).strip()
else: else:
@ -254,7 +255,7 @@ def get_valid_filename(value, replace_whitespace=True):
def get_sorted_author(value): def get_sorted_author(value):
try: try:
if ',' not in value: if ',' not in value:
regexes = ["^(JR|SR)\.?$", "^I{1,3}\.?$", "^IV\.?$"] regexes = [r"^(JR|SR)\.?$", r"^I{1,3}\.?$", r"^IV\.?$"]
combined = "(" + ")|(".join(regexes) + ")" combined = "(" + ")|(".join(regexes) + ")"
value = value.split(" ") value = value.split(" ")
if re.match(combined, value[-1].upper()): if re.match(combined, value[-1].upper()):

View File

@ -97,15 +97,20 @@ def setup(log_file, log_level=None):
''' '''
log_file = _absolute_log_file(log_file, DEFAULT_LOG_FILE) log_file = _absolute_log_file(log_file, DEFAULT_LOG_FILE)
log_level = log_level or DEFAULT_LOG_LEVEL
logging.getLogger(__package__).setLevel(log_level)
r = logging.root r = logging.root
r.setLevel(log_level or DEFAULT_LOG_LEVEL) if log_level >= logging.INFO or os.environ.get('FLASK_DEBUG'):
# avoid spamming the log with debug messages from libraries
r.setLevel(log_level)
previous_handler = r.handlers[0] if r.handlers else None previous_handler = r.handlers[0] if r.handlers else None
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:
return return
r.debug("logging to %s level %s", log_file, r.level) logging.debug("logging to %s level %s", log_file, r.level)
if log_file == LOG_TO_STDERR: if log_file == LOG_TO_STDERR:
file_handler = StreamHandler() file_handler = StreamHandler()

View File

@ -1,4 +1,3 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
# This file is part of the Calibre-Web (https://github.com/janeczku/calibre-web) # This file is part of the Calibre-Web (https://github.com/janeczku/calibre-web)
@ -28,23 +27,30 @@ 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 _version from gevent import __version__ as _version
VERSION = {'Gevent': 'v' + _version} VERSION = 'Gevent ' + _version
_GEVENT = True _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 _version from tornado import version as _version
VERSION = {'Tornado': 'v' + _version} VERSION = 'Tornado ' + _version
_GEVENT = False _GEVENT = False
from . import logger, global_WorkerThread from . import logger
log = logger.create() log = logger.create()
class WebServer:
def _readable_listen_address(address, port):
if ':' in address:
address = "[" + address + "]"
return '%s:%s' % (address, port)
class WebServer(object):
def __init__(self): def __init__(self):
signal.signal(signal.SIGINT, self._killServer) signal.signal(signal.SIGINT, self._killServer)
@ -56,14 +62,12 @@ class WebServer:
self.app = None self.app = None
self.listen_address = None self.listen_address = None
self.listen_port = None self.listen_port = None
self.IPV6 = False
self.unix_socket_file = None self.unix_socket_file = None
self.ssl_args = None self.ssl_args = None
def init_app(self, application, config): def init_app(self, application, config):
self.app = application self.app = application
self.listen_address = config.get_config_ipaddress() self.listen_address = config.get_config_ipaddress()
self.IPV6 = config.get_ipaddress_type()
self.listen_port = config.config_port self.listen_port = config.config_port
if config.config_access_log: if config.config_access_log:
@ -78,8 +82,7 @@ class WebServer:
keyfile_path = config.get_config_keyfile() keyfile_path = config.get_config_keyfile()
if certfile_path and keyfile_path: if certfile_path and keyfile_path:
if os.path.isfile(certfile_path) and os.path.isfile(keyfile_path): if os.path.isfile(certfile_path) and os.path.isfile(keyfile_path):
self.ssl_args = {"certfile": certfile_path, self.ssl_args = dict(certfile=certfile_path, keyfile=keyfile_path)
"keyfile": keyfile_path}
else: else:
log.warning('The specified paths for the ssl certificate file and/or key file seem to be broken. Ignoring ssl.') log.warning('The specified paths for the ssl certificate file and/or key file seem to be broken. Ignoring ssl.')
log.warning('Cert path: %s', certfile_path) log.warning('Cert path: %s', certfile_path)
@ -107,32 +110,33 @@ class WebServer:
if os.name != 'nt': if os.name != 'nt':
unix_socket_file = os.environ.get("CALIBRE_UNIX_SOCKET") unix_socket_file = os.environ.get("CALIBRE_UNIX_SOCKET")
if unix_socket_file: if unix_socket_file:
output = "socket:" + unix_socket_file + ":" + str(self.listen_port) return self._make_gevent_unix_socket(unix_socket_file), "unix:" + unix_socket_file
return self._make_gevent_unix_socket(unix_socket_file), output
if self.listen_address: if self.listen_address:
return (self.listen_address, self.listen_port), self._get_readable_listen_address() return (self.listen_address, self.listen_port), None
if os.name == 'nt': if os.name == 'nt':
self.listen_address = '0.0.0.0' self.listen_address = '0.0.0.0'
return (self.listen_address, self.listen_port), self._get_readable_listen_address() return (self.listen_address, self.listen_port), None
address = ('', self.listen_port)
try: try:
address = ('::', self.listen_port)
sock = WSGIServer.get_listener(address, 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...')
output = self._get_readable_listen_address(False) address = ('', self.listen_port)
sock = WSGIServer.get_listener(address, family=socket.AF_INET) sock = WSGIServer.get_listener(address, family=socket.AF_INET)
return sock, output
return sock, _readable_listen_address(*address)
def _start_gevent(self): def _start_gevent(self):
ssl_args = self.ssl_args or {} ssl_args = self.ssl_args or {}
try: try:
sock, output = self._make_gevent_socket() sock, output = self._make_gevent_socket()
if output is None:
output = _readable_listen_address(self.listen_address, self.listen_port)
log.info('Starting Gevent server on %s', output) 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()
@ -142,30 +146,18 @@ class WebServer:
self.unix_socket_file = None self.unix_socket_file = None
def _start_tornado(self): def _start_tornado(self):
log.info('Starting Tornado server on %s', self._get_readable_listen_address()) log.info('Starting Tornado server on %s', _readable_listen_address(self.listen_address, self.listen_port))
# Max Buffersize set to 200MB ) # Max Buffersize set to 200MB )
http_server = HTTPServer(WSGIContainer(self.app), http_server = HTTPServer(WSGIContainer(self.app),
max_buffer_size = 209700000, max_buffer_size=209700000,
ssl_options=self.ssl_args) ssl_options=self.ssl_args)
http_server.listen(self.listen_port, self.listen_address) http_server.listen(self.listen_port, self.listen_address)
self.wsgiserver=IOLoop.instance() self.wsgiserver = IOLoop.instance()
self.wsgiserver.start() self.wsgiserver.start()
# wait for stop signal # wait for stop signal
self.wsgiserver.close(True) 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): def start(self):
try: try:
if _GEVENT: if _GEVENT:
@ -179,7 +171,6 @@ class WebServer:
return False return False
finally: finally:
self.wsgiserver = None self.wsgiserver = None
global_WorkerThread.stop()
if not self.restart: if not self.restart:
log.info("Performing shutdown of Calibre-Web") log.info("Performing shutdown of Calibre-Web")
@ -193,7 +184,7 @@ class WebServer:
os.execv(sys.executable, arguments) os.execv(sys.executable, arguments)
return True return True
def _killServer(self, signum, frame): def _killServer(self, ignored_signum, ignored_frame):
self.stop() self.stop()
def stop(self, restart=False): def stop(self, restart=False):

View File

@ -29,10 +29,7 @@ _ldap = LDAP()
def init_app(app, config): def init_app(app, config):
global _ldap
if config.config_login_type != constants.LOGIN_LDAP: if config.config_login_type != constants.LOGIN_LDAP:
_ldap = None
return return
app.config['LDAP_HOST'] = config.config_ldap_provider_url app.config['LDAP_HOST'] = config.config_ldap_provider_url

View File

@ -478,13 +478,11 @@ def init_db(app_db_path):
def dispose(): def dispose():
global session global session
engine = None old_session = session
if session: session = None
engine = session.bind if old_session:
try: session.close() try: old_session.close()
except: pass
session = None
if engine:
try: engine.dispose()
except: pass except: pass
if old_session.bind:
try: old_session.bind.dispose()
except: pass

View File

@ -41,8 +41,8 @@ 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, services from . import constants, logger, isoLanguages, services, worker
from . import global_WorkerThread, searched_ids, lm, babel, db, ub, config, negociate_locale, get_locale, app from . import searched_ids, lm, babel, db, ub, config, negociate_locale, get_locale, app
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, \
order_authors, get_typeahead, render_task_status, json_serial, get_cc_columns, \ order_authors, get_typeahead, render_task_status, json_serial, get_cc_columns, \
@ -245,7 +245,7 @@ def before_request():
@web.route("/ajax/emailstat") @web.route("/ajax/emailstat")
@login_required @login_required
def get_email_status_json(): def get_email_status_json():
tasks = global_WorkerThread.get_taskstatus() tasks = worker.get_taskstatus()
answer = render_task_status(tasks) answer = render_task_status(tasks)
js = json.dumps(answer, default=json_serial) js = json.dumps(answer, default=json_serial)
response = make_response(js) response = make_response(js)
@ -760,7 +760,7 @@ def category_list():
@login_required @login_required
def get_tasks_status(): def get_tasks_status():
# if current user admin, show all email, otherwise only own emails # if current user admin, show all email, otherwise only own emails
tasks = global_WorkerThread.get_taskstatus() tasks = worker.get_taskstatus()
answer = render_task_status(tasks) answer = render_task_status(tasks)
return render_title_template('tasks.html', entries=answer, title=_(u"Tasks"), page="tasks") return render_title_template('tasks.html', entries=answer, title=_(u"Tasks"), page="tasks")

View File

@ -25,7 +25,7 @@ import smtplib
import socket import socket
import time import time
import threading import threading
from datetime import datetime, timedelta from datetime import datetime
try: try:
from StringIO import StringIO from StringIO import StringIO
@ -66,6 +66,13 @@ RET_FAIL = 0
RET_SUCCESS = 1 RET_SUCCESS = 1
def _get_main_thread():
for t in threading.enumerate():
if t.__class__.__name__ == '_MainThread':
return t
raise Exception("main thread not found?!")
# For gdrive download book from gdrive to calibredir (temp dir for books), read contents in both cases and append # For gdrive download book from gdrive to calibredir (temp dir for books), read contents in both cases and append
# it in MIME Base64 encoded to # it in MIME Base64 encoded to
def get_attachment(bookpath, filename): def get_attachment(bookpath, filename):
@ -173,19 +180,19 @@ class email_SSL(emailbase, smtplib.SMTP_SSL):
class WorkerThread(threading.Thread): class WorkerThread(threading.Thread):
def __init__(self): def __init__(self):
self._stopevent = threading.Event()
threading.Thread.__init__(self) threading.Thread.__init__(self)
self.status = 0 self.status = 0
self.current = 0 self.current = 0
self.last = 0 self.last = 0
self.queue = list() self.queue = list()
self.UIqueue = list() self.UIqueue = list()
self.asyncSMTP=None self.asyncSMTP = None
self.id = 0 self.id = 0
# Main thread loop starting the different tasks # Main thread loop starting the different tasks
def run(self): def run(self):
while not self._stopevent.isSet(): main_thread = _get_main_thread()
while main_thread.is_alive():
doLock = threading.Lock() doLock = threading.Lock()
doLock.acquire() doLock.acquire()
if self.current != self.last: if self.current != self.last:
@ -200,10 +207,8 @@ class WorkerThread(threading.Thread):
self.current += 1 self.current += 1
else: else:
doLock.release() doLock.release()
time.sleep(1) if main_thread.is_alive():
time.sleep(1)
def stop(self):
self._stopevent.set()
def get_send_status(self): def get_send_status(self):
if self.asyncSMTP: if self.asyncSMTP:
@ -317,7 +322,7 @@ class WorkerThread(threading.Thread):
nextline = p.communicate()[0] nextline = p.communicate()[0]
# Format of error message (kindlegen translates its output texts): # Format of error message (kindlegen translates its output texts):
# Error(prcgen):E23006: Language not recognized in metadata.The dc:Language field is mandatory.Aborting. # Error(prcgen):E23006: Language not recognized in metadata.The dc:Language field is mandatory.Aborting.
conv_error = re.search(".*\(.*\):(E\d+):\s(.*)", nextline, re.MULTILINE) conv_error = re.search(r".*\(.*\):(E\d+):\s(.*)", nextline, re.MULTILINE)
# If error occoures, store error message for logfile # If error occoures, store error message for logfile
if conv_error: if conv_error:
error_message = _(u"Kindlegen failed with Error %(error)s. Message: %(message)s", error_message = _(u"Kindlegen failed with Error %(error)s. Message: %(message)s",
@ -332,7 +337,7 @@ class WorkerThread(threading.Thread):
nextline = nextline.decode('utf-8') 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(r"(\d+)%\s.*", nextline)
if progress: if progress:
self.UIqueue[self.current]['progress'] = progress.group(1) + ' %' self.UIqueue[self.current]['progress'] = progress.group(1) + ' %'
@ -511,3 +516,23 @@ class WorkerThread(threading.Thread):
self.UIqueue[self.current]['stat'] = STAT_FINISH_SUCCESS self.UIqueue[self.current]['stat'] = STAT_FINISH_SUCCESS
self.UIqueue[self.current]['progress'] = "100 %" self.UIqueue[self.current]['progress'] = "100 %"
self.UIqueue[self.current]['formRuntime'] = datetime.now() - self.queue[self.current]['starttime'] self.UIqueue[self.current]['formRuntime'] = datetime.now() - self.queue[self.current]['starttime']
_worker = WorkerThread()
_worker.start()
def get_taskstatus():
return _worker.get_taskstatus()
def add_email(subject, filepath, attachment, settings, recipient, user_name, taskMessage, text):
return _worker.add_email(subject, filepath, attachment, settings, recipient, user_name, taskMessage, text)
def add_upload(user_name, taskMessage):
return _worker.add_upload(user_name, taskMessage)
def add_convert(file_path, bookid, user_name, taskMessage, settings, kindle_mail=None):
return _worker.add_convert(file_path, bookid, user_name, taskMessage, settings, kindle_mail)