Commit 50973ffb authored by Ozzieisaacs's avatar Ozzieisaacs

Merge remote-tracking branch 'constants/Develop-logging-cleanup' into Develop

parents a02f949d b89ab9ff
...@@ -17,14 +17,14 @@ ...@@ -17,14 +17,14 @@
# You should have received a copy of the GNU General Public License # You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>. # along with this program. If not, see <http://www.gnu.org/licenses/>.
import os from __future__ import absolute_import, division, print_function, unicode_literals
import sys import sys
import os
base_path = os.path.dirname(os.path.abspath(__file__))
# Insert local directories into path # Insert local directories into path
sys.path.append(base_path) sys.path.append(os.path.join(sys.path[0], 'vendor'))
sys.path.append(os.path.join(base_path, 'cps'))
sys.path.append(os.path.join(base_path, 'vendor'))
from cps import create_app from cps import create_app
from cps.opds import opds from cps.opds import opds
......
...@@ -20,29 +20,28 @@ ...@@ -20,29 +20,28 @@
# #
# You should have received a copy of the GNU General Public License # You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>. # along with this program. If not, see <http://www.gnu.org/licenses/>.
__all__ =['app']
import mimetypes from __future__ import division, print_function, unicode_literals
from flask import Flask, request, g
from flask_login import LoginManager
from flask_babel import Babel
import cache_buster
from reverseproxy import ReverseProxied
import logging
from logging.handlers import RotatingFileHandler
from flask_principal import Principal
from babel.core import UnknownLocaleError
from babel import Locale as LC
from babel import negotiate_locale
import os
import ub
import sys import sys
from ub import Config, Settings import os
import mimetypes
try: try:
import cPickle import cPickle
except ImportError: except ImportError:
import pickle as cPickle import pickle as cPickle
from babel import Locale as LC
from babel import negotiate_locale
from babel.core import UnknownLocaleError
from flask import Flask, request, g
from flask_login import LoginManager
from flask_babel import Babel
from flask_principal import Principal
from . import logger, cache_buster, ub
from .constants import TRANSLATIONS_DIR as _TRANSLATIONS_DIR
from .reverseproxy import ReverseProxied
mimetypes.init() mimetypes.init()
mimetypes.add_type('application/xhtml+xml', '.xhtml') mimetypes.add_type('application/xhtml+xml', '.xhtml')
...@@ -70,12 +69,11 @@ lm.anonymous_user = ub.Anonymous ...@@ -70,12 +69,11 @@ lm.anonymous_user = ub.Anonymous
ub.init_db() ub.init_db()
config = Config() config = ub.Config()
from . import db from . import db
try: try:
with open(os.path.join(config.get_main_dir, 'cps/translations/iso639.pickle'), 'rb') as f: with open(os.path.join(_TRANSLATIONS_DIR, 'iso639.pickle'), 'rb') as f:
language_table = cPickle.load(f) language_table = cPickle.load(f)
except cPickle.UnpicklingError as error: except cPickle.UnpicklingError as error:
# app.logger.error("Can't read file cps/translations/iso639.pickle: %s", error) # app.logger.error("Can't read file cps/translations/iso639.pickle: %s", error)
...@@ -91,24 +89,14 @@ from .server import server ...@@ -91,24 +89,14 @@ from .server import server
Server = server() Server = server()
babel = Babel() babel = Babel()
log = logger.create()
def create_app(): def create_app():
app.wsgi_app = ReverseProxied(app.wsgi_app) app.wsgi_app = ReverseProxied(app.wsgi_app)
cache_buster.init_cache_busting(app) cache_buster.init_cache_busting(app)
formatter = logging.Formatter( log.info('Starting Calibre Web...')
"[%(asctime)s] {%(pathname)s:%(lineno)d} %(levelname)s - %(message)s")
try:
file_handler = RotatingFileHandler(config.get_config_logfile(), maxBytes=50000, backupCount=2)
except IOError:
file_handler = RotatingFileHandler(os.path.join(config.get_main_dir, "calibre-web.log"),
maxBytes=50000, backupCount=2)
# ToDo: reset logfile value in config class
file_handler.setFormatter(formatter)
app.logger.addHandler(file_handler)
app.logger.setLevel(config.config_log_level)
app.logger.info('Starting Calibre Web...')
Principal(app) Principal(app)
lm.init_app(app) lm.init_app(app)
app.secret_key = os.getenv('SECRET_KEY', 'A0Zr98j/3yX R~XHH!jmN]LWX/,?RT') app.secret_key = os.getenv('SECRET_KEY', 'A0Zr98j/3yX R~XHH!jmN]LWX/,?RT')
...@@ -132,7 +120,7 @@ def get_locale(): ...@@ -132,7 +120,7 @@ def get_locale():
try: try:
preferred.append(str(LC.parse(x.replace('-', '_')))) preferred.append(str(LC.parse(x.replace('-', '_'))))
except (UnknownLocaleError, ValueError) as e: except (UnknownLocaleError, ValueError) as e:
app.logger.debug("Could not parse locale: %s", e) log.warning('Could not parse locale "%s": %s', x, e)
preferred.append('en') preferred.append('en')
return negotiate_locale(preferred, translations) return negotiate_locale(preferred, translations)
...@@ -145,3 +133,6 @@ def get_timezone(): ...@@ -145,3 +133,6 @@ def get_timezone():
from .updater import Updater from .updater import Updater
updater_thread = Updater() updater_thread = Updater()
__all__ = ['app']
...@@ -21,29 +21,30 @@ ...@@ -21,29 +21,30 @@
# You should have received a copy of the GNU General Public License # You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>. # along with this program. If not, see <http://www.gnu.org/licenses/>.
from flask import Blueprint from __future__ import division, print_function, unicode_literals
from flask_login import login_required
from . import db
import sys import sys
from .uploader import get_versions
from babel import __version__ as babelVersion
from sqlalchemy import __version__ as sqlalchemyVersion
from flask_principal import __version__ as flask_principalVersion
from iso639 import __version__ as iso639Version
from pytz import __version__ as pytzVersion
from flask import __version__ as flaskVersion
from werkzeug import __version__ as werkzeugVersion
from jinja2 import __version__ as jinja2Version
from .converter import versioncheck
from flask_babel import gettext as _
from cps import Server
import requests import requests
from .web import render_title_template
from flask import Blueprint
from flask import __version__ as flaskVersion
from flask_babel import gettext as _
from flask_principal import __version__ as flask_principalVersion
from flask_login import login_required
try: try:
from flask_login import __version__ as flask_loginVersion from flask_login import __version__ as flask_loginVersion
except ImportError: except ImportError:
from flask_login.__about__ import __version__ as flask_loginVersion from flask_login.__about__ import __version__ as flask_loginVersion
from werkzeug import __version__ as werkzeugVersion
from babel import __version__ as babelVersion
from jinja2 import __version__ as jinja2Version
from pytz import __version__ as pytzVersion
from sqlalchemy import __version__ as sqlalchemyVersion
from . import db, converter, Server, uploader
from .isoLanguages import __version__ as iso639Version
from .web import render_title_template
about = Blueprint('about', __name__) about = Blueprint('about', __name__)
...@@ -55,7 +56,7 @@ def stats(): ...@@ -55,7 +56,7 @@ def stats():
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 = get_versions() versions = uploader.get_versions()
versions['Babel'] = 'v' + babelVersion versions['Babel'] = 'v' + babelVersion
versions['Sqlalchemy'] = 'v' + sqlalchemyVersion versions['Sqlalchemy'] = 'v' + sqlalchemyVersion
versions['Werkzeug'] = 'v' + werkzeugVersion versions['Werkzeug'] = 'v' + werkzeugVersion
...@@ -69,7 +70,7 @@ def stats(): ...@@ -69,7 +70,7 @@ def stats():
versions['Requests'] = 'v' + requests.__version__ versions['Requests'] = 'v' + requests.__version__
versions['pySqlite'] = 'v' + db.engine.dialect.dbapi.version versions['pySqlite'] = 'v' + db.engine.dialect.dbapi.version
versions['Sqlite'] = 'v' + db.engine.dialect.dbapi.sqlite_version versions['Sqlite'] = 'v' + db.engine.dialect.dbapi.sqlite_version
versions.update(versioncheck()) versions.update(converter.versioncheck())
versions.update(Server.getNameVersion()) versions.update(Server.getNameVersion())
versions['Python'] = sys.version versions['Python'] = sys.version
return render_title_template('stats.html', bookcounter=counter, authorcounter=authors, versions=versions, return render_title_template('stats.html', bookcounter=counter, authorcounter=authors, versions=versions,
......
This diff is collapsed.
...@@ -17,8 +17,14 @@ ...@@ -17,8 +17,14 @@
# Inspired by https://github.com/ChrisTM/Flask-CacheBust # Inspired by https://github.com/ChrisTM/Flask-CacheBust
# Uses query strings so CSS font files are found without having to resort to absolute URLs # Uses query strings so CSS font files are found without having to resort to absolute URLs
import hashlib from __future__ import division, print_function, unicode_literals
import os import os
import hashlib
from . import logger
log = logger.create()
def init_cache_busting(app): def init_cache_busting(app):
...@@ -34,7 +40,7 @@ def init_cache_busting(app): ...@@ -34,7 +40,7 @@ def init_cache_busting(app):
hash_table = {} # map of file hashes hash_table = {} # map of file hashes
app.logger.debug('Computing cache-busting values...') log.debug('Computing cache-busting values...')
# compute file hashes # compute file hashes
for dirpath, __, filenames in os.walk(static_folder): for dirpath, __, filenames in os.walk(static_folder):
for filename in filenames: for filename in filenames:
...@@ -47,7 +53,7 @@ def init_cache_busting(app): ...@@ -47,7 +53,7 @@ def init_cache_busting(app):
file_path = rooted_filename.replace(static_folder, "") file_path = rooted_filename.replace(static_folder, "")
file_path = file_path.replace("\\", "/") # Convert Windows path to web path file_path = file_path.replace("\\", "/") # Convert Windows path to web path
hash_table[file_path] = file_hash hash_table[file_path] = file_hash
app.logger.debug('Finished computing cache-busting values') log.debug('Finished computing cache-busting values')
def bust_filename(filename): def bust_filename(filename):
return hash_table.get(filename, "") return hash_table.get(filename, "")
......
...@@ -18,9 +18,13 @@ ...@@ -18,9 +18,13 @@
# You should have received a copy of the GNU General Public License # You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>. # along with this program. If not, see <http://www.gnu.org/licenses/>.
import argparse from __future__ import division, print_function, unicode_literals
import os
import sys import sys
import os
import argparse
from .constants import CONFIG_DIR as _CONFIG_DIR
parser = argparse.ArgumentParser(description='Calibre Web is a web app' parser = argparse.ArgumentParser(description='Calibre Web is a web app'
' providing a interface for browsing, reading and downloading eBooks\n', prog='cps.py') ' providing a interface for browsing, reading and downloading eBooks\n', prog='cps.py')
...@@ -33,17 +37,8 @@ parser.add_argument('-k', metavar='path', ...@@ -33,17 +37,8 @@ parser.add_argument('-k', metavar='path',
parser.add_argument('-v', action='store_true', help='shows version number and exits Calibre-web') parser.add_argument('-v', action='store_true', help='shows version number and exits Calibre-web')
args = parser.parse_args() args = parser.parse_args()
generalPath = os.path.normpath(os.getenv("CALIBRE_DBPATH", settingspath = args.p or os.path.join(_CONFIG_DIR, "app.db")
os.path.dirname(os.path.realpath(__file__)) + os.sep + ".." + os.sep)) gdpath = args.g or os.path.join(_CONFIG_DIR, "gdrive.db")
if args.p:
settingspath = args.p
else:
settingspath = os.path.join(generalPath, "app.db")
if args.g:
gdpath = args.g
else:
gdpath = os.path.join(generalPath, "gdrive.db")
certfilepath = None certfilepath = None
keyfilepath = None keyfilepath = None
......
...@@ -17,17 +17,21 @@ ...@@ -17,17 +17,21 @@
# You should have received a copy of the GNU General Public License # You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>. # along with this program. If not, see <http://www.gnu.org/licenses/>.
from __future__ import division, print_function, unicode_literals
import os import os
from constants import BookMeta
from cps import app from . import logger, isoLanguages
from iso639 import languages as isoLanguages from .constants import BookMeta
log = logger.create()
try: try:
from comicapi.comicarchive import ComicArchive, MetaDataStyle from comicapi.comicarchive import ComicArchive, MetaDataStyle
use_comic_meta = True use_comic_meta = True
except ImportError as e: except ImportError as e:
app.logger.warning('cannot import comicapi, extracting comic metadata will not work: %s', e) log.warning('cannot import comicapi, extracting comic metadata will not work: %s', e)
import zipfile import zipfile
import tarfile import tarfile
use_comic_meta = False use_comic_meta = False
......
...@@ -2,7 +2,7 @@ ...@@ -2,7 +2,7 @@
# -*- 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) 2019 OzzieIsaacs # Copyright (C) 2019 OzzieIsaacs, pwr
# #
# This program is free software: you can redistribute it and/or modify # 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 # it under the terms of the GNU General Public License as published by
...@@ -17,10 +17,101 @@ ...@@ -17,10 +17,101 @@
# You should have received a copy of the GNU General Public License # You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>. # along with this program. If not, see <http://www.gnu.org/licenses/>.
from __future__ import division, print_function, unicode_literals
import sys
import os
from collections import namedtuple from collections import namedtuple
BASE_DIR = sys.path[0]
STATIC_DIR = os.path.join(BASE_DIR, 'cps', 'static')
TEMPLATES_DIR = os.path.join(BASE_DIR, 'cps', 'templates')
TRANSLATIONS_DIR = os.path.join(BASE_DIR, 'cps', 'translations')
CONFIG_DIR = os.environ.get('CALIBRE_DBPATH', BASE_DIR)
ROLE_USER = 0 << 0
ROLE_ADMIN = 1 << 0
ROLE_DOWNLOAD = 1 << 1
ROLE_UPLOAD = 1 << 2
ROLE_EDIT = 1 << 3
ROLE_PASSWD = 1 << 4
ROLE_ANONYMOUS = 1 << 5
ROLE_EDIT_SHELFS = 1 << 6
ROLE_DELETE_BOOKS = 1 << 7
ROLE_VIEWER = 1 << 8
ALL_ROLES = {
"admin_role": ROLE_ADMIN,
"download_role": ROLE_DOWNLOAD,
"upload_role": ROLE_UPLOAD,
"edit_role": ROLE_EDIT,
"passwd_role": ROLE_PASSWD,
"edit_shelf_role": ROLE_EDIT_SHELFS,
"delete_role": ROLE_DELETE_BOOKS,
"viewer_role": ROLE_VIEWER,
}
DETAIL_RANDOM = 1 << 0
SIDEBAR_LANGUAGE = 1 << 1
SIDEBAR_SERIES = 1 << 2
SIDEBAR_CATEGORY = 1 << 3
SIDEBAR_HOT = 1 << 4
SIDEBAR_RANDOM = 1 << 5
SIDEBAR_AUTHOR = 1 << 6
SIDEBAR_BEST_RATED = 1 << 7
SIDEBAR_READ_AND_UNREAD = 1 << 8
SIDEBAR_RECENT = 1 << 9
SIDEBAR_SORTED = 1 << 10
MATURE_CONTENT = 1 << 11
SIDEBAR_PUBLISHER = 1 << 12
SIDEBAR_RATING = 1 << 13
SIDEBAR_FORMAT = 1 << 14
ADMIN_USER_ROLES = (ROLE_VIEWER << 1) - 1 - (ROLE_ANONYMOUS | ROLE_EDIT_SHELFS)
ADMIN_USER_SIDEBAR = (SIDEBAR_FORMAT << 1) - 1
UPDATE_STABLE = 0 << 0
AUTO_UPDATE_STABLE = 1 << 0
UPDATE_NIGHTLY = 1 << 1
AUTO_UPDATE_NIGHTLY = 1 << 2
LOGIN_STANDARD = 0
LOGIN_LDAP = 1
LOGIN_OAUTH_GITHUB = 2
LOGIN_OAUTH_GOOGLE = 3
DEFAULT_PASSWORD = "admin123"
DEFAULT_PORT = 8083
try:
env_CALIBRE_PORT = os.environ.get("CALIBRE_PORT", DEFAULT_PORT)
DEFAULT_PORT = int(env_CALIBRE_PORT)
except ValueError:
print('Environment variable CALIBRE_PORT has invalid value (%s), faling back to default (8083)' % env_CALIBRE_PORT)
del env_CALIBRE_PORT
EXTENSIONS_AUDIO = {'mp3', 'm4a', 'm4b'}
EXTENSIONS_CONVERT = {'pdf', 'epub', 'mobi', 'azw3', 'docx', 'rtf', 'fb2', 'lit', 'lrf', 'txt', 'htmlz', 'rtf', 'odt'}
EXTENSIONS_UPLOAD = {'txt', 'pdf', 'epub', 'mobi', 'azw', 'azw3', 'cbr', 'cbz', 'cbt', 'djvu', 'prc', 'doc', 'docx',
'fb2', 'html', 'rtf', 'odt', 'mp3', 'm4a', 'm4b'}
# EXTENSIONS_READER = set(['txt', 'pdf', 'epub', 'zip', 'cbz', 'tar', 'cbt'] +
# (['rar','cbr'] if feature_support['rar'] else []))
def has_flag(value, bit_flag):
return bit_flag == (bit_flag & (value or 0))
""" """
:rtype: BookMeta :rtype: BookMeta
""" """
BookMeta = namedtuple('BookMeta', 'file_path, extension, title, author, cover, description, tags, series, ' BookMeta = namedtuple('BookMeta', 'file_path, extension, title, author, cover, description, tags, series, '
'series_id, languages') 'series_id, languages')
STABLE_VERSION = {'version': '0.6.4 Beta'}
# clean-up the module namespace
del sys, os, namedtuple
...@@ -17,14 +17,14 @@ ...@@ -17,14 +17,14 @@
# You should have received a copy of the GNU General Public License # You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>. # along with this program. If not, see <http://www.gnu.org/licenses/>.
from __future__ import division, print_function, unicode_literals
import os import os
# import subprocess
import ub
import re import re
from flask_babel import gettext as _ from flask_babel import gettext as _
from subproc_wrapper import process_open
from . import config from . import config
from .subproc_wrapper import process_open
def versionKindle(): def versionKindle():
......
...@@ -18,16 +18,20 @@ ...@@ -18,16 +18,20 @@
# You should have received a copy of the GNU General Public License # You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>. # along with this program. If not, see <http://www.gnu.org/licenses/>.
from sqlalchemy import * from __future__ import division, print_function, unicode_literals
from sqlalchemy.ext.declarative import declarative_base import sys
from sqlalchemy.orm import *
import os import os
import re import re
import ast import ast
from . import config
import ub from sqlalchemy import create_engine
import sys from sqlalchemy import Table, Column, ForeignKey
import unidecode from sqlalchemy import String, Integer, Boolean
from sqlalchemy.orm import relationship, sessionmaker, scoped_session
from sqlalchemy.ext.declarative import declarative_base
from . import config, ub
session = None session = None
cc_exceptions = ['datetime', 'comments', 'float', 'composite', 'series'] cc_exceptions = ['datetime', 'comments', 'float', 'composite', 'series']
...@@ -385,7 +389,7 @@ def setup_db(): ...@@ -385,7 +389,7 @@ def setup_db():
ccdict = {'__tablename__': 'custom_column_' + str(row.id), ccdict = {'__tablename__': 'custom_column_' + str(row.id),
'id': Column(Integer, primary_key=True), 'id': Column(Integer, primary_key=True),
'value': Column(String)} 'value': Column(String)}
cc_classes[row.id] = type('Custom_Column_' + str(row.id), (Base,), ccdict) cc_classes[row.id] = type(str('Custom_Column_' + str(row.id)), (Base,), ccdict)
for cc_id in cc_ids: for cc_id in cc_ids:
if (cc_id[1] == 'bool') or (cc_id[1] == 'int'): if (cc_id[1] == 'bool') or (cc_id[1] == 'int'):
......
...@@ -21,28 +21,25 @@ ...@@ -21,28 +21,25 @@
# You should have received a copy of the GNU General Public License # You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>. # along with this program. If not, see <http://www.gnu.org/licenses/>.
# opds routing functions from __future__ import division, print_function, unicode_literals
from . import config, language_table, get_locale, app, ub, global_WorkerThread, db
from flask import request, flash, redirect, url_for, abort, Markup, Response
from flask import Blueprint
import datetime
import os import os
import datetime
import json import json
from flask_babel import gettext as _ from shutil import move, copyfile
from uuid import uuid4 from uuid import uuid4
from . import helper
from .helper import order_authors, common_filters from flask import Blueprint, request, flash, redirect, url_for, abort, Markup, Response
from flask_babel import gettext as _
from flask_login import current_user from flask_login import current_user
from .web import login_required_if_no_ano, render_title_template, edit_required, \
upload_required, login_required, EXTENSIONS_UPLOAD
from . import gdriveutils
from shutil import move, copyfile
from . import uploader
from iso639 import languages as isoLanguages
editbook = Blueprint('editbook', __name__) from . import constants, logger, isoLanguages, gdriveutils, uploader, helper
from . import config, get_locale, db, ub, global_WorkerThread, language_table
from .helper import order_authors, common_filters
from .web import login_required_if_no_ano, render_title_template, edit_required, upload_required, login_required
EXTENSIONS_CONVERT = {'pdf', 'epub', 'mobi', 'azw3', 'docx', 'rtf', 'fb2', 'lit', 'lrf', 'txt', 'htmlz', 'rtf', 'odt'} editbook = Blueprint('editbook', __name__)
log = logger.create()
# Modifies different Database objects, first check if elements have to be added to database, than check # Modifies different Database objects, first check if elements have to be added to database, than check
...@@ -201,7 +198,7 @@ def delete_book(book_id, book_format): ...@@ -201,7 +198,7 @@ def delete_book(book_id, book_format):
db.session.commit() db.session.commit()
else: else:
# book not found # book not found
app.logger.info('Book with id "'+str(book_id)+'" could not be deleted') log.error('Book with id "%s" could not be deleted: not found', book_id)
if book_format: if book_format:
return redirect(url_for('editbook.edit_book', book_id=book_id)) return redirect(url_for('editbook.edit_book', book_id=book_id))
else: else:
...@@ -231,16 +228,16 @@ def render_edit_book(book_id): ...@@ -231,16 +228,16 @@ def render_edit_book(book_id):
valid_source_formats=list() valid_source_formats=list()
if config.config_ebookconverter == 2: if config.config_ebookconverter == 2:
for file in book.data: for file in book.data:
if file.format.lower() in EXTENSIONS_CONVERT: if file.format.lower() in constants.EXTENSIONS_CONVERT:
valid_source_formats.append(file.format.lower()) valid_source_formats.append(file.format.lower())
# Determine what formats don't already exist # Determine what formats don't already exist
allowed_conversion_formats = EXTENSIONS_CONVERT.copy() allowed_conversion_formats = constants.EXTENSIONS_CONVERT.copy()
for file in book.data: for file in book.data:
try: try:
allowed_conversion_formats.remove(file.format.lower()) allowed_conversion_formats.remove(file.format.lower())
except Exception: except Exception:
app.logger.warning(file.format.lower() + ' already removed from list.') log.warning('%s already removed from list.', file.format.lower())
return render_title_template('book_edit.html', book=book, authors=author_names, cc=cc, return render_title_template('book_edit.html', book=book, authors=author_names, cc=cc,
title=_(u"edit metadata"), page="editbook", title=_(u"edit metadata"), page="editbook",
...@@ -321,7 +318,7 @@ def upload_single_file(request, book, book_id): ...@@ -321,7 +318,7 @@ def upload_single_file(request, book, book_id):
if requested_file.filename != '': if requested_file.filename != '':
if '.' in requested_file.filename: if '.' in requested_file.filename:
file_ext = requested_file.filename.rsplit('.', 1)[-1].lower() file_ext = requested_file.filename.rsplit('.', 1)[-1].lower()
if file_ext not in EXTENSIONS_UPLOAD: if file_ext not in constants.EXTENSIONS_UPLOAD:
flash(_("File extension '%(ext)s' is not allowed to be uploaded to this server", ext=file_ext), flash(_("File extension '%(ext)s' is not allowed to be uploaded to this server", ext=file_ext),
category="error") category="error")
return redirect(url_for('web.show_book', book_id=book.id)) return redirect(url_for('web.show_book', book_id=book.id))
...@@ -352,7 +349,7 @@ def upload_single_file(request, book, book_id): ...@@ -352,7 +349,7 @@ def upload_single_file(request, book, book_id):
# Format entry already exists, no need to update the database # Format entry already exists, no need to update the database
if is_format: if is_format:
app.logger.info('Book format already existing') log.warning('Book format %s already existing', file_ext.upper())
else: else:
db_format = db.Data(book_id, file_ext.upper(), file_size, file_name) db_format = db.Data(book_id, file_ext.upper(), file_size, file_name)
db.session.add(db_format) db.session.add(db_format)
...@@ -530,7 +527,7 @@ def edit_book(book_id): ...@@ -530,7 +527,7 @@ def edit_book(book_id):
res = list(language_table[get_locale()].keys())[invers_lang_table.index(lang)] res = list(language_table[get_locale()].keys())[invers_lang_table.index(lang)]
input_l.append(res) input_l.append(res)
except ValueError: except ValueError:
app.logger.error('%s is not a valid language' % lang) log.error('%s is not a valid language', lang)
flash(_(u"%(langname)s is not a valid language", langname=lang), category="error") flash(_(u"%(langname)s is not a valid language", langname=lang), category="error")
modify_database_object(input_l, book.languages, db.Languages, db.session, 'languages') modify_database_object(input_l, book.languages, db.Languages, db.session, 'languages')
...@@ -569,7 +566,7 @@ def edit_book(book_id): ...@@ -569,7 +566,7 @@ def edit_book(book_id):
flash(error, category="error") flash(error, category="error")
return render_edit_book(book_id) return render_edit_book(book_id)
except Exception as e: except Exception as e:
app.logger.exception(e) log.exception(e)
db.session.rollback() db.session.rollback()
flash(_("Error editing book, please check logfile for details"), category="error") flash(_("Error editing book, please check logfile for details"), category="error")
return redirect(url_for('web.show_book', book_id=book.id)) return redirect(url_for('web.show_book', book_id=book.id))
...@@ -590,7 +587,7 @@ def upload(): ...@@ -590,7 +587,7 @@ def upload():
# check if file extension is correct # check if file extension is correct
if '.' in requested_file.filename: if '.' in requested_file.filename:
file_ext = requested_file.filename.rsplit('.', 1)[-1].lower() file_ext = requested_file.filename.rsplit('.', 1)[-1].lower()
if file_ext not in EXTENSIONS_UPLOAD: if file_ext not in constants.EXTENSIONS_UPLOAD:
flash( flash(
_("File extension '%(ext)s' is not allowed to be uploaded to this server", _("File extension '%(ext)s' is not allowed to be uploaded to this server",
ext=file_ext), category="error") ext=file_ext), category="error")
...@@ -631,7 +628,7 @@ def upload(): ...@@ -631,7 +628,7 @@ def upload():
if meta.cover is None: if meta.cover is None:
has_cover = 0 has_cover = 0
copyfile(os.path.join(config.get_main_dir, "cps/static/generic_cover.jpg"), copyfile(os.path.join(constants.STATIC_DIR, 'generic_cover.jpg'),
os.path.join(filepath, "cover.jpg")) os.path.join(filepath, "cover.jpg"))
else: else:
has_cover = 1 has_cover = 1
...@@ -741,9 +738,7 @@ def convert_bookformat(book_id): ...@@ -741,9 +738,7 @@ def convert_bookformat(book_id):
flash(_(u"Source or destination format for conversion missing"), category="error") flash(_(u"Source or destination format for conversion missing"), category="error")
return redirect(request.environ["HTTP_REFERER"]) return redirect(request.environ["HTTP_REFERER"])
app.logger.debug('converting: book id: ' + str(book_id) + log.info('converting: book id: %s from: %s to: %s', book_id, book_format_from, book_format_to)
' from: ' + request.form['book_format_from'] +
' to: ' + request.form['book_format_to'])
rtn = helper.convert_book_format(book_id, config.config_calibre_dir, book_format_from.upper(), rtn = helper.convert_book_format(book_id, config.config_calibre_dir, book_format_from.upper(),
book_format_to.upper(), current_user.nickname) book_format_to.upper(), current_user.nickname)
......
...@@ -17,11 +17,13 @@ ...@@ -17,11 +17,13 @@
# You should have received a copy of the GNU General Public License # You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>. # along with this program. If not, see <http://www.gnu.org/licenses/>.
from __future__ import division, print_function, unicode_literals
import os
import zipfile import zipfile
from lxml import etree from lxml import etree
import os
from . import isoLanguages
from .constants import BookMeta from .constants import BookMeta
import isoLanguages
def extractCover(zipFile, coverFile, coverpath, tmp_file_name): def extractCover(zipFile, coverFile, coverpath, tmp_file_name):
...@@ -125,7 +127,7 @@ def get_epub_info(tmp_file_path, original_file_name, original_file_extension): ...@@ -125,7 +127,7 @@ def get_epub_info(tmp_file_path, original_file_name, original_file_extension):
else: else:
title = epub_metadata['title'] title = epub_metadata['title']
return uploader.BookMeta( return BookMeta(
file_path=tmp_file_path, file_path=tmp_file_path,
extension=original_file_extension, extension=original_file_extension,
title=title.encode('utf-8').decode('utf-8'), title=title.encode('utf-8').decode('utf-8'),
......
...@@ -17,7 +17,9 @@ ...@@ -17,7 +17,9 @@
# You should have received a copy of the GNU General Public License # You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>. # along with this program. If not, see <http://www.gnu.org/licenses/>.
from __future__ import division, print_function, unicode_literals
from lxml import etree from lxml import etree
from .constants import BookMeta from .constants import BookMeta
......
...@@ -20,26 +20,31 @@ ...@@ -20,26 +20,31 @@
# #
# You should have received a copy of the GNU General Public License # You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>. # along with this program. If not, see <http://www.gnu.org/licenses/>.
from __future__ import division, print_function, unicode_literals
import os import os
from flask import Blueprint import hashlib
from . import gdriveutils
from flask import flash, request, redirect, url_for, abort
from flask_babel import gettext as _
from . import app, config, ub, db
from flask_login import login_required
import json import json
import tempfile
from uuid import uuid4 from uuid import uuid4
from time import time from time import time
import tempfile
from shutil import move, copyfile from shutil import move, copyfile
from .web import admin_required
from flask import Blueprint, flash, request, redirect, url_for, abort
from flask_babel import gettext as _
from flask_login import login_required
try: try:
from googleapiclient.errors import HttpError from googleapiclient.errors import HttpError
except ImportError: except ImportError:
pass pass
from . import logger, gdriveutils, config, ub, db
from .web import admin_required
gdrive = Blueprint('gdrive', __name__) gdrive = Blueprint('gdrive', __name__)
log = logger.create()
current_milli_time = lambda: int(round(time() * 1000)) current_milli_time = lambda: int(round(time() * 1000))
...@@ -66,10 +71,10 @@ def google_drive_callback(): ...@@ -66,10 +71,10 @@ def google_drive_callback():
abort(403) abort(403)
try: try:
credentials = gdriveutils.Gauth.Instance().auth.flow.step2_exchange(auth_code) credentials = gdriveutils.Gauth.Instance().auth.flow.step2_exchange(auth_code)
with open(os.path.join(config.get_main_dir,'gdrive_credentials'), 'w') as f: with open(gdriveutils.CREDENTIALS, 'w') as f:
f.write(credentials.to_json()) f.write(credentials.to_json())
except ValueError as error: except ValueError as error:
app.logger.error(error) log.error(error)
return redirect(url_for('admin.configuration')) return redirect(url_for('admin.configuration'))
...@@ -78,7 +83,7 @@ def google_drive_callback(): ...@@ -78,7 +83,7 @@ def google_drive_callback():
@admin_required @admin_required
def watch_gdrive(): def watch_gdrive():
if not config.config_google_drive_watch_changes_response: if not config.config_google_drive_watch_changes_response:
with open(os.path.join(config.get_main_dir,'client_secrets.json'), 'r') as settings: with open(gdriveutils.CLIENT_SECRETS, 'r') as settings:
filedata = json.load(settings) filedata = json.load(settings)
if filedata['web']['redirect_uris'][0].endswith('/'): if filedata['web']['redirect_uris'][0].endswith('/'):
filedata['web']['redirect_uris'][0] = filedata['web']['redirect_uris'][0][:-((len('/gdrive/callback')+1))] filedata['web']['redirect_uris'][0] = filedata['web']['redirect_uris'][0][:-((len('/gdrive/callback')+1))]
...@@ -126,7 +131,7 @@ def revoke_watch_gdrive(): ...@@ -126,7 +131,7 @@ def revoke_watch_gdrive():
@gdrive.route("/gdrive/watch/callback", methods=['GET', 'POST']) @gdrive.route("/gdrive/watch/callback", methods=['GET', 'POST'])
def on_received_watch_confirmation(): def on_received_watch_confirmation():
app.logger.debug(request.headers) log.debug('%r', request.headers)
if request.headers.get('X-Goog-Channel-Token') == gdrive_watch_callback_token \ if request.headers.get('X-Goog-Channel-Token') == gdrive_watch_callback_token \
and request.headers.get('X-Goog-Resource-State') == 'change' \ and request.headers.get('X-Goog-Resource-State') == 'change' \
and request.data: and request.data:
...@@ -134,27 +139,26 @@ def on_received_watch_confirmation(): ...@@ -134,27 +139,26 @@ def on_received_watch_confirmation():
data = request.data data = request.data
def updateMetaData(): def updateMetaData():
app.logger.info('Change received from gdrive') log.info('Change received from gdrive')
app.logger.debug(data) log.debug('%r', data)
try: try:
j = json.loads(data) j = json.loads(data)
app.logger.info('Getting change details') log.info('Getting change details')
response = gdriveutils.getChangeById(gdriveutils.Gdrive.Instance().drive, j['id']) response = gdriveutils.getChangeById(gdriveutils.Gdrive.Instance().drive, j['id'])
app.logger.debug(response) log.debug('%r', response)
if response: if response:
dbpath = os.path.join(config.config_calibre_dir, "metadata.db") dbpath = os.path.join(config.config_calibre_dir, "metadata.db")
if not response['deleted'] and response['file']['title'] == 'metadata.db' and response['file']['md5Checksum'] != hashlib.md5(dbpath): if not response['deleted'] and response['file']['title'] == 'metadata.db' and response['file']['md5Checksum'] != hashlib.md5(dbpath):
tmpDir = tempfile.gettempdir() tmpDir = tempfile.gettempdir()
app.logger.info('Database file updated') log.info('Database file updated')
copyfile(dbpath, os.path.join(tmpDir, "metadata.db_" + str(current_milli_time()))) copyfile(dbpath, os.path.join(tmpDir, "metadata.db_" + str(current_milli_time())))
app.logger.info('Backing up existing and downloading updated metadata.db') log.info('Backing up existing and downloading updated metadata.db')
gdriveutils.downloadFile(None, "metadata.db", os.path.join(tmpDir, "tmp_metadata.db")) gdriveutils.downloadFile(None, "metadata.db", os.path.join(tmpDir, "tmp_metadata.db"))
app.logger.info('Setting up new DB') log.info('Setting up new DB')
# prevent error on windows, as os.rename does on exisiting files # prevent error on windows, as os.rename does on exisiting files
move(os.path.join(tmpDir, "tmp_metadata.db"), dbpath) move(os.path.join(tmpDir, "tmp_metadata.db"), dbpath)
db.setup_db() db.setup_db()
except Exception as e: except Exception as e:
app.logger.info(e.message) log.exception(e)
app.logger.exception(e)
updateMetaData() updateMetaData()
return '' return ''
...@@ -17,23 +17,35 @@ ...@@ -17,23 +17,35 @@
# You should have received a copy of the GNU General Public License # You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>. # along with this program. If not, see <http://www.gnu.org/licenses/>.
from __future__ import division, print_function, unicode_literals
import os
import shutil
from flask import Response, stream_with_context
from sqlalchemy import create_engine
from sqlalchemy import Column, UniqueConstraint
from sqlalchemy import String, Integer
from sqlalchemy.orm import sessionmaker, scoped_session
from sqlalchemy.ext.declarative import declarative_base
try: try:
from pydrive.auth import GoogleAuth from pydrive.auth import GoogleAuth
from pydrive.drive import GoogleDrive from pydrive.drive import GoogleDrive
from pydrive.auth import RefreshError, InvalidConfigError from pydrive.auth import RefreshError
from apiclient import errors from apiclient import errors
gdrive_support = True gdrive_support = True
except ImportError: except ImportError:
gdrive_support = False gdrive_support = False
import os from . import logger, cli, config
from . import config, app from .constants import BASE_DIR as _BASE_DIR
import cli
import shutil
from flask import Response, stream_with_context SETTINGS_YAML = os.path.join(_BASE_DIR, 'settings.yaml')
from sqlalchemy import * CREDENTIALS = os.path.join(_BASE_DIR, 'gdrive_credentials')
from sqlalchemy.ext.declarative import declarative_base CLIENT_SECRETS = os.path.join(_BASE_DIR, 'client_secrets.json')
from sqlalchemy.orm import *
log = logger.create()
class Singleton: class Singleton:
...@@ -78,7 +90,7 @@ class Singleton: ...@@ -78,7 +90,7 @@ class Singleton:
@Singleton @Singleton
class Gauth: class Gauth:
def __init__(self): def __init__(self):
self.auth = GoogleAuth(settings_file=os.path.join(config.get_main_dir,'settings.yaml')) self.auth = GoogleAuth(settings_file=SETTINGS_YAML)
@Singleton @Singleton
...@@ -87,8 +99,7 @@ class Gdrive: ...@@ -87,8 +99,7 @@ class Gdrive:
self.drive = getDrive(gauth=Gauth.Instance().auth) self.drive = getDrive(gauth=Gauth.Instance().auth)
def is_gdrive_ready(): def is_gdrive_ready():
return os.path.exists(os.path.join(config.get_main_dir, 'settings.yaml')) and \ return os.path.exists(SETTINGS_YAML) and os.path.exists(CREDENTIALS)
os.path.exists(os.path.join(config.get_main_dir, 'gdrive_credentials'))
engine = create_engine('sqlite:///{0}'.format(cli.gdpath), echo=False) engine = create_engine('sqlite:///{0}'.format(cli.gdpath), echo=False)
...@@ -150,17 +161,17 @@ migrate() ...@@ -150,17 +161,17 @@ migrate()
def getDrive(drive=None, gauth=None): def getDrive(drive=None, gauth=None):
if not drive: if not drive:
if not gauth: if not gauth:
gauth = GoogleAuth(settings_file=os.path.join(config.get_main_dir,'settings.yaml')) gauth = GoogleAuth(settings_file=SETTINGS_YAML)
# Try to load saved client credentials # Try to load saved client credentials
gauth.LoadCredentialsFile(os.path.join(config.get_main_dir,'gdrive_credentials')) gauth.LoadCredentialsFile(CREDENTIALS)
if gauth.access_token_expired: if gauth.access_token_expired:
# Refresh them if expired # Refresh them if expired
try: try:
gauth.Refresh() gauth.Refresh()
except RefreshError as e: except RefreshError as e:
app.logger.error("Google Drive error: " + e.message) log.error("Google Drive error: %s", e)
except Exception as e: except Exception as e:
app.logger.exception(e) log.exception(e)
else: else:
# Initialize the saved creds # Initialize the saved creds
gauth.Authorize() gauth.Authorize()
...@@ -170,7 +181,7 @@ def getDrive(drive=None, gauth=None): ...@@ -170,7 +181,7 @@ def getDrive(drive=None, gauth=None):
try: try:
drive.auth.Refresh() drive.auth.Refresh()
except RefreshError as e: except RefreshError as e:
app.logger.error("Google Drive error: " + e.message) log.error("Google Drive error: %s", e)
return drive return drive
def listRootFolders(): def listRootFolders():
...@@ -207,7 +218,7 @@ def getEbooksFolderId(drive=None): ...@@ -207,7 +218,7 @@ def getEbooksFolderId(drive=None):
try: try:
gDriveId.gdrive_id = getEbooksFolder(drive)['id'] gDriveId.gdrive_id = getEbooksFolder(drive)['id']
except Exception: except Exception:
app.logger.error('Error gDrive, root ID not found') log.error('Error gDrive, root ID not found')
gDriveId.path = '/' gDriveId.path = '/'
session.merge(gDriveId) session.merge(gDriveId)
session.commit() session.commit()
...@@ -447,10 +458,10 @@ def getChangeById (drive, change_id): ...@@ -447,10 +458,10 @@ def getChangeById (drive, change_id):
change = drive.auth.service.changes().get(changeId=change_id).execute() change = drive.auth.service.changes().get(changeId=change_id).execute()
return change return change
except (errors.HttpError) as error: except (errors.HttpError) as error:
app.logger.info(error.message) log.error(error)
return None return None
except Exception as e: except Exception as e:
app.logger.info(e) log.error(e)
return None return None
...@@ -520,6 +531,6 @@ def do_gdrive_download(df, headers): ...@@ -520,6 +531,6 @@ def do_gdrive_download(df, headers):
if resp.status == 206: if resp.status == 206:
yield content yield content
else: else:
app.logger.info('An error occurred: %s' % resp) log.warning('An error occurred: %s', resp)
return return
return Response(stream_with_context(stream()), headers=headers) return Response(stream_with_context(stream()), headers=headers)
This diff is collapsed.
...@@ -17,6 +17,9 @@ ...@@ -17,6 +17,9 @@
# You should have received a copy of the GNU General Public License # You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>. # along with this program. If not, see <http://www.gnu.org/licenses/>.
from __future__ import division, print_function, unicode_literals
try: try:
from iso639 import languages, __version__ from iso639 import languages, __version__
get = languages.get get = languages.get
......
...@@ -23,15 +23,21 @@ ...@@ -23,15 +23,21 @@
# custom jinja filters # custom jinja filters
from flask import Blueprint, request, url_for from __future__ import division, print_function, unicode_literals
import datetime import datetime
import mimetypes
import re import re
from . import mimetypes, app
from babel.dates import format_date from babel.dates import format_date
from flask import Blueprint, request, url_for
from flask_babel import get_locale from flask_babel import get_locale
from flask_login import current_user from flask_login import current_user
from . import logger
jinjia = Blueprint('jinjia', __name__) jinjia = Blueprint('jinjia', __name__)
log = logger.create()
# pagination links in jinja # pagination links in jinja
...@@ -79,8 +85,7 @@ def formatdate_filter(val): ...@@ -79,8 +85,7 @@ def formatdate_filter(val):
formatdate = datetime.datetime.strptime(conformed_timestamp[:15], "%Y%m%d %H%M%S") formatdate = datetime.datetime.strptime(conformed_timestamp[:15], "%Y%m%d %H%M%S")
return format_date(formatdate, format='medium', locale=get_locale()) return format_date(formatdate, format='medium', locale=get_locale())
except AttributeError as e: except AttributeError as e:
app.logger.error('Babel error: %s, Current user locale: %s, Current User: %s' % log.error('Babel error: %s, Current user locale: %s, Current User: %s', e, current_user.locale, current_user.nickname)
(e, current_user.locale, current_user.nickname))
return formatdate return formatdate
@jinjia.app_template_filter('formatdateinput') @jinjia.app_template_filter('formatdateinput')
......
# -*- coding: utf-8 -*-
# This file is part of the Calibre-Web (https://github.com/janeczku/calibre-web)
# Copyright (C) 2019 pwr
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
from __future__ import division, print_function, unicode_literals
import os
import inspect
import logging
from logging import Formatter, StreamHandler
from logging.handlers import RotatingFileHandler
from .constants import BASE_DIR as _BASE_DIR
FORMATTER = Formatter("[%(asctime)s] %(levelname)5s {%(name)s:%(lineno)d} %(message)s")
DEFAULT_LOG_LEVEL = logging.INFO
DEFAULT_LOG_FILE = os.path.join(_BASE_DIR, "calibre-web.log")
LOG_TO_STDERR = '/dev/stderr'
logging.addLevelName(logging.WARNING, "WARN")
logging.addLevelName(logging.CRITICAL, "CRIT")
def get(name=None):
return logging.getLogger(name)
def create():
parent_frame = inspect.stack(0)[1]
if hasattr(parent_frame, 'frame'):
parent_frame = parent_frame.frame
else:
parent_frame = parent_frame[0]
parent_module = inspect.getmodule(parent_frame)
return get(parent_module.__name__)
def is_debug_enabled():
return logging.root.level <= logging.DEBUG
def get_level_name(level):
return logging.getLevelName(level)
def is_valid_logfile(file_path):
if not file_path:
return True
if os.path.isdir(file_path):
return False
log_dir = os.path.dirname(file_path)
return (not log_dir) or os.path.isdir(log_dir)
def setup(log_file, log_level=None):
if log_file:
if not os.path.dirname(log_file):
log_file = os.path.join(_BASE_DIR, log_file)
log_file = os.path.abspath(log_file)
else:
# log_file = LOG_TO_STDERR
log_file = DEFAULT_LOG_FILE
# print ('%r -- %r' % (log_level, log_file))
r = logging.root
r.setLevel(log_level or DEFAULT_LOG_LEVEL)
previous_handler = r.handlers[0] if r.handlers else None
# print ('previous %r' % previous_handler)
if previous_handler:
# if the log_file has not changed, don't create a new handler
if getattr(previous_handler, 'baseFilename', None) == log_file:
return
r.debug("logging to %s level %s", log_file, r.level)
if log_file == LOG_TO_STDERR:
file_handler = StreamHandler()
file_handler.baseFilename = LOG_TO_STDERR
else:
try:
file_handler = RotatingFileHandler(log_file, maxBytes=50000, backupCount=2)
except IOError:
if log_file == DEFAULT_LOG_FILE:
raise
file_handler = RotatingFileHandler(DEFAULT_LOG_FILE, maxBytes=50000, backupCount=2)
file_handler.setFormatter(FORMATTER)
for h in r.handlers:
r.removeHandler(h)
h.close()
r.addHandler(file_handler)
# print ('new handler %r' % file_handler)
# Enable logging of smtp lib debug output
class StderrLogger(object):
def __init__(self, name=None):
self.log = get(name or self.__class__.__name__)
self.buffer = ''
def write(self, message):
try:
if message == '\n':
self.log.debug(self.buffer.replace('\n', '\\n'))
self.buffer = ''
else:
self.buffer += message
except:
self.logger.debug("Logging Error")
#!/usr/bin/env python #!/usr/bin/env python
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
from __future__ import division, print_function, unicode_literals
from flask import session from flask import session
try: try:
from flask_dance.consumer.backend.sqla import SQLAlchemyBackend, first, _get_real_user from flask_dance.consumer.backend.sqla import SQLAlchemyBackend, first, _get_real_user
from sqlalchemy.orm.exc import NoResultFound from sqlalchemy.orm.exc import NoResultFound
......
...@@ -21,30 +21,34 @@ ...@@ -21,30 +21,34 @@
# You should have received a copy of the GNU General Public License # You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/> # along with this program. If not, see <http://www.gnu.org/licenses/>
from flask_dance.contrib.github import make_github_blueprint, github from __future__ import division, print_function, unicode_literals
from flask_dance.contrib.google import make_google_blueprint, google import json
from flask_dance.consumer import oauth_authorized, oauth_error from functools import wraps
from oauth import OAuthBackend from oauth import OAuthBackend
from sqlalchemy.orm.exc import NoResultFound
from flask import session, request, make_response, abort from flask import session, request, make_response, abort
import json
from cps import config, app
import ub
from flask_login import login_user, current_user
from flask import Blueprint, flash, redirect, url_for from flask import Blueprint, flash, redirect, url_for
from flask_babel import gettext as _ from flask_babel import gettext as _
# from web import github_oauth_required from flask_dance.consumer import oauth_authorized, oauth_error
from functools import wraps from flask_dance.contrib.github import make_github_blueprint, github
from web import login_required from flask_dance.contrib.google import make_google_blueprint, google
from flask_login import login_user, current_user
from sqlalchemy.orm.exc import NoResultFound
from . import constants, logger, config, app, ub
from .web import login_required
# from .web import github_oauth_required
oauth_check = {} oauth_check = {}
oauth = Blueprint('oauth', __name__) oauth = Blueprint('oauth', __name__)
log = logger.create()
def github_oauth_required(f): def github_oauth_required(f):
@wraps(f) @wraps(f)
def inner(*args, **kwargs): def inner(*args, **kwargs):
if config.config_login_type == ub.LOGIN_OAUTH_GITHUB: if config.config_login_type == constants.LOGIN_OAUTH_GITHUB:
return f(*args, **kwargs) return f(*args, **kwargs)
if request.is_xhr: if request.is_xhr:
data = {'status': 'error', 'message': 'Not Found'} data = {'status': 'error', 'message': 'Not Found'}
...@@ -59,7 +63,7 @@ def github_oauth_required(f): ...@@ -59,7 +63,7 @@ def github_oauth_required(f):
def google_oauth_required(f): def google_oauth_required(f):
@wraps(f) @wraps(f)
def inner(*args, **kwargs): def inner(*args, **kwargs):
if config.config_use_google_oauth == ub.LOGIN_OAUTH_GOOGLE: if config.config_use_google_oauth == constants.LOGIN_OAUTH_GOOGLE:
return f(*args, **kwargs) return f(*args, **kwargs)
if request.is_xhr: if request.is_xhr:
data = {'status': 'error', 'message': 'Not Found'} data = {'status': 'error', 'message': 'Not Found'}
...@@ -101,7 +105,7 @@ def register_user_with_oauth(user=None): ...@@ -101,7 +105,7 @@ def register_user_with_oauth(user=None):
try: try:
ub.session.commit() ub.session.commit()
except Exception as e: except Exception as e:
app.logger.exception(e) log.exception(e)
ub.session.rollback() ub.session.rollback()
...@@ -133,9 +137,9 @@ if ub.oauth_support: ...@@ -133,9 +137,9 @@ if ub.oauth_support:
google_blueprint.backend = OAuthBackend(ub.OAuth, ub.session, user=current_user, user_required=True) google_blueprint.backend = OAuthBackend(ub.OAuth, ub.session, user=current_user, user_required=True)
if config.config_login_type == ub.LOGIN_OAUTH_GITHUB: if config.config_login_type == constants.LOGIN_OAUTH_GITHUB:
register_oauth_blueprint(github_blueprint, 'GitHub') register_oauth_blueprint(github_blueprint, 'GitHub')
if config.config_login_type == ub.LOGIN_OAUTH_GOOGLE: if config.config_login_type == constants.LOGIN_OAUTH_GOOGLE:
register_oauth_blueprint(google_blueprint, 'Google') register_oauth_blueprint(google_blueprint, 'Google')
...@@ -195,7 +199,7 @@ if ub.oauth_support: ...@@ -195,7 +199,7 @@ if ub.oauth_support:
ub.session.add(oauth) ub.session.add(oauth)
ub.session.commit() ub.session.commit()
except Exception as e: except Exception as e:
app.logger.exception(e) log.exception(e)
ub.session.rollback() ub.session.rollback()
# Disable Flask-Dance's default behavior for saving the OAuth token # Disable Flask-Dance's default behavior for saving the OAuth token
...@@ -221,7 +225,7 @@ if ub.oauth_support: ...@@ -221,7 +225,7 @@ if ub.oauth_support:
ub.session.add(oauth) ub.session.add(oauth)
ub.session.commit() ub.session.commit()
except Exception as e: except Exception as e:
app.logger.exception(e) log.exception(e)
ub.session.rollback() ub.session.rollback()
return redirect(url_for('web.login')) return redirect(url_for('web.login'))
#if config.config_public_reg: #if config.config_public_reg:
...@@ -264,11 +268,11 @@ if ub.oauth_support: ...@@ -264,11 +268,11 @@ if ub.oauth_support:
logout_oauth_user() logout_oauth_user()
flash(_(u"Unlink to %(oauth)s success.", oauth=oauth_check[provider]), category="success") flash(_(u"Unlink to %(oauth)s success.", oauth=oauth_check[provider]), category="success")
except Exception as e: except Exception as e:
app.logger.exception(e) log.exception(e)
ub.session.rollback() ub.session.rollback()
flash(_(u"Unlink to %(oauth)s failed.", oauth=oauth_check[provider]), category="error") flash(_(u"Unlink to %(oauth)s failed.", oauth=oauth_check[provider]), category="error")
except NoResultFound: except NoResultFound:
app.logger.warning("oauth %s for user %d not fount" % (provider, current_user.id)) log.warning("oauth %s for user %d not fount", provider, current_user.id)
flash(_(u"Not linked to %(oauth)s.", oauth=oauth_check[provider]), category="error") flash(_(u"Not linked to %(oauth)s.", oauth=oauth_check[provider]), category="error")
return redirect(url_for('web.profile')) return redirect(url_for('web.profile'))
......
...@@ -21,22 +21,24 @@ ...@@ -21,22 +21,24 @@
# You should have received a copy of the GNU General Public License # You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>. # along with this program. If not, see <http://www.gnu.org/licenses/>.
# opds routing functions from __future__ import division, print_function, unicode_literals
from . import config, db import sys
from flask import request, render_template, Response, g, make_response
from pagination import Pagination
from flask import Blueprint
import datetime import datetime
import ub
from flask_login import current_user
from functools import wraps from functools import wraps
from .web import login_required_if_no_ano, common_filters, get_search_results, render_read_books, download_required
from sqlalchemy.sql.expression import func, text from flask import Blueprint, request, render_template, Response, g, make_response
from flask_login import current_user
from sqlalchemy.sql.expression import func, text, or_, and_
from werkzeug.security import check_password_hash from werkzeug.security import check_password_hash
from . import logger, config, db, ub
from .helper import fill_indexpage, get_download_link, get_book_cover from .helper import fill_indexpage, get_download_link, get_book_cover
import sys from .pagination import Pagination
from .web import common_filters, get_search_results, render_read_books, download_required
opds = Blueprint('opds', __name__) opds = Blueprint('opds', __name__)
log = logger.create()
def requires_basic_auth_if_no_ano(f): def requires_basic_auth_if_no_ano(f):
...@@ -231,10 +233,10 @@ def feed_shelf(book_id): ...@@ -231,10 +233,10 @@ def feed_shelf(book_id):
if current_user.is_anonymous: if current_user.is_anonymous:
shelf = ub.session.query(ub.Shelf).filter(ub.Shelf.is_public == 1, ub.Shelf.id == book_id).first() shelf = ub.session.query(ub.Shelf).filter(ub.Shelf.is_public == 1, ub.Shelf.id == book_id).first()
else: else:
shelf = ub.session.query(ub.Shelf).filter(ub.or_(ub.and_(ub.Shelf.user_id == int(current_user.id), shelf = ub.session.query(ub.Shelf).filter(or_(and_(ub.Shelf.user_id == int(current_user.id),
ub.Shelf.id == book_id), ub.Shelf.id == book_id),
ub.and_(ub.Shelf.is_public == 1, and_(ub.Shelf.is_public == 1,
ub.Shelf.id == book_id))).first() ub.Shelf.id == book_id))).first()
result = list() result = list()
# user is allowed to access shelf # user is allowed to access shelf
if shelf: if shelf:
......
...@@ -21,6 +21,7 @@ ...@@ -21,6 +21,7 @@
# You should have received a copy of the GNU General Public License # You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>. # along with this program. If not, see <http://www.gnu.org/licenses/>.
from __future__ import division, print_function, unicode_literals
from math import ceil from math import ceil
......
...@@ -28,10 +28,12 @@ ...@@ -28,10 +28,12 @@
# http://flask.pocoo.org/snippets/62/ # http://flask.pocoo.org/snippets/62/
from __future__ import division, print_function, unicode_literals
try: try:
from urllib.parse import urlparse, urljoin from urllib.parse import urlparse, urljoin
except ImportError: except ImportError:
from urlparse import urlparse, urljoin from urlparse import urlparse, urljoin
from flask import request, url_for, redirect from flask import request, url_for, redirect
......
...@@ -37,6 +37,8 @@ ...@@ -37,6 +37,8 @@
# #
# Inspired by http://flask.pocoo.org/snippets/35/ # Inspired by http://flask.pocoo.org/snippets/35/
from __future__ import division, print_function, unicode_literals
class ReverseProxied(object): class ReverseProxied(object):
"""Wrap the application in this middleware and configure the """Wrap the application in this middleware and configure the
......
...@@ -17,12 +17,11 @@ ...@@ -17,12 +17,11 @@
# You should have received a copy of the GNU General Public License # You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>. # along with this program. If not, see <http://www.gnu.org/licenses/>.
from __future__ import division, print_function, unicode_literals
from socket import error as SocketError
import sys import sys
import os import os
import signal import signal
from . import config, global_WorkerThread import socket
try: try:
from gevent.pywsgi import WSGIServer from gevent.pywsgi import WSGIServer
...@@ -36,6 +35,11 @@ except ImportError: ...@@ -36,6 +35,11 @@ except ImportError:
from tornado import version as tornadoVersion from tornado import version as tornadoVersion
gevent_present = False gevent_present = False
from . import logger, config, global_WorkerThread
log = logger.create()
class server: class server:
...@@ -49,76 +53,77 @@ class server: ...@@ -49,76 +53,77 @@ class server:
def init_app(self, application): def init_app(self, application):
self.app = application self.app = application
self.port = config.config_port
self.ssl_args = None
certfile_path = config.get_config_certfile()
keyfile_path = config.get_config_keyfile()
if certfile_path and keyfile_path:
if os.path.isfile(certfile_path) and os.path.isfile(keyfile_path):
self.ssl_args = {"certfile": certfile_path,
"keyfile": keyfile_path}
else:
log.warning('The specified paths for the ssl certificate file and/or key file seem to be broken. Ignoring ssl.')
log.warning('Cert path: %s', certfile_path)
log.warning('Key path: %s', keyfile_path)
def _make_gevent_socket(self):
if os.name == 'nt':
return ('0.0.0.0', self.port)
try:
s = WSGIServer.get_listener(('', self.port), family=socket.AF_INET6)
except socket.error as ex:
log.error('%s', ex)
log.warning('Unable to listen on \'\', trying on IPv4 only...')
s = WSGIServer.get_listener(('', self.port), family=socket.AF_INET)
log.debug("%r %r", s._sock, s._sock.getsockname())
return s
def start_gevent(self): def start_gevent(self):
ssl_args = dict() ssl_args = self.ssl_args or {}
log.info('Starting Gevent server')
try: try:
certfile_path = config.get_config_certfile() sock = self._make_gevent_socket()
keyfile_path = config.get_config_keyfile() self.wsgiserver = WSGIServer(sock, self.app, spawn=Pool(), **ssl_args)
if certfile_path and keyfile_path:
if os.path.isfile(certfile_path) and os.path.isfile(keyfile_path):
ssl_args = {"certfile": certfile_path,
"keyfile": keyfile_path}
else:
self.app.logger.info('The specified paths for the ssl certificate file and/or key file seem '
'to be broken. Ignoring ssl. Cert path: %s | Key path: '
'%s' % (certfile_path, keyfile_path))
if os.name == 'nt':
self.wsgiserver = WSGIServer(('0.0.0.0', config.config_port), self.app, spawn=Pool(), **ssl_args)
else:
self.wsgiserver = WSGIServer(('', config.config_port), self.app, spawn=Pool(), **ssl_args)
self.wsgiserver.serve_forever() self.wsgiserver.serve_forever()
except (OSError, socket.error) as e:
except SocketError: log.info("Error starting server: %s", e.strerror)
try: print("Error starting server: %s" % e.strerror)
self.app.logger.info('Unable to listen on \'\', trying on IPv4 only...') global_WorkerThread.stop()
self.wsgiserver = WSGIServer(('0.0.0.0', config.config_port), self.app, spawn=Pool(), **ssl_args) sys.exit(1)
self.wsgiserver.serve_forever()
except (OSError, SocketError) as e:
self.app.logger.info("Error starting server: %s" % e.strerror)
print("Error starting server: %s" % e.strerror)
global_WorkerThread.stop()
sys.exit(1)
except Exception: except Exception:
self.app.logger.info("Unknown error while starting gevent") log.exception("Unknown error while starting gevent")
def start_tornado(self):
log.info('Starting Tornado server')
try:
# Max Buffersize set to 200MB
http_server = HTTPServer(WSGIContainer(self.app),
max_buffer_size = 209700000,
ssl_options=self.ssl_args)
http_server.listen(self.port)
self.wsgiserver=IOLoop.instance()
self.wsgiserver.start()
# wait for stop signal
self.wsgiserver.close(True)
except socket.error as err:
log.exception("Error starting tornado server")
print("Error starting server: %s" % err.strerror)
global_WorkerThread.stop()
sys.exit(1)
def startServer(self): def startServer(self):
if gevent_present: if gevent_present:
self.app.logger.info('Starting Gevent server')
# leave subprocess out to allow forking for fetchers and processors # leave subprocess out to allow forking for fetchers and processors
self.start_gevent() self.start_gevent()
else: else:
try: self.start_tornado()
ssl = None
self.app.logger.info('Starting Tornado server')
certfile_path = config.get_config_certfile()
keyfile_path = config.get_config_keyfile()
if certfile_path and keyfile_path:
if os.path.isfile(certfile_path) and os.path.isfile(keyfile_path):
ssl = {"certfile": certfile_path,
"keyfile": keyfile_path}
else:
self.app.logger.info('The specified paths for the ssl certificate file and/or key file '
'seem to be broken. Ignoring ssl. Cert path: %s | Key '
'path: %s' % (certfile_path, keyfile_path))
# Max Buffersize set to 200MB
http_server = HTTPServer(WSGIContainer(self.app),
max_buffer_size = 209700000,
ssl_options=ssl)
http_server.listen(config.config_port)
self.wsgiserver=IOLoop.instance()
self.wsgiserver.start()
# wait for stop signal
self.wsgiserver.close(True)
except SocketError as e:
self.app.logger.info("Error starting server: %s" % e.strerror)
print("Error starting server: %s" % e.strerror)
global_WorkerThread.stop()
sys.exit(1)
if self.restart is True: if self.restart is True:
self.app.logger.info("Performing restart of Calibre-Web") log.info("Performing restart of Calibre-Web")
global_WorkerThread.stop() global_WorkerThread.stop()
if os.name == 'nt': if os.name == 'nt':
arguments = ["\"" + sys.executable + "\""] arguments = ["\"" + sys.executable + "\""]
...@@ -128,7 +133,7 @@ class server: ...@@ -128,7 +133,7 @@ class server:
else: else:
os.execl(sys.executable, sys.executable, *sys.argv) os.execl(sys.executable, sys.executable, *sys.argv)
else: else:
self.app.logger.info("Performing shutdown of Calibre-Web") log.info("Performing shutdown of Calibre-Web")
global_WorkerThread.stop() global_WorkerThread.stop()
sys.exit(0) sys.exit(0)
......
...@@ -21,28 +21,34 @@ ...@@ -21,28 +21,34 @@
# You should have received a copy of the GNU General Public License # You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>. # along with this program. If not, see <http://www.gnu.org/licenses/>.
from __future__ import division, print_function, unicode_literals
from flask import Blueprint, request, flash, redirect, url_for from flask import Blueprint, request, flash, redirect, url_for
from . import ub, searched_ids, app, db
from flask_babel import gettext as _ from flask_babel import gettext as _
from sqlalchemy.sql.expression import func, or_
from flask_login import login_required, current_user from flask_login import login_required, current_user
from sqlalchemy.sql.expression import func, or_, and_
from . import logger, ub, searched_ids, db
from .web import render_title_template from .web import render_title_template
shelf = Blueprint('shelf', __name__) shelf = Blueprint('shelf', __name__)
log = logger.create()
@shelf.route("/shelf/add/<int:shelf_id>/<int:book_id>") @shelf.route("/shelf/add/<int:shelf_id>/<int:book_id>")
@login_required @login_required
def add_to_shelf(shelf_id, book_id): def add_to_shelf(shelf_id, book_id):
shelf = ub.session.query(ub.Shelf).filter(ub.Shelf.id == shelf_id).first() shelf = ub.session.query(ub.Shelf).filter(ub.Shelf.id == shelf_id).first()
if shelf is None: if shelf is None:
app.logger.info("Invalid shelf specified") log.error("Invalid shelf specified: %s", shelf_id)
if not request.is_xhr: if not request.is_xhr:
flash(_(u"Invalid shelf specified"), category="error") flash(_(u"Invalid shelf specified"), category="error")
return redirect(url_for('web.index')) return redirect(url_for('web.index'))
return "Invalid shelf specified", 400 return "Invalid shelf specified", 400
if not shelf.is_public and not shelf.user_id == int(current_user.id): if not shelf.is_public and not shelf.user_id == int(current_user.id):
app.logger.info("Sorry you are not allowed to add a book to the the shelf: %s" % shelf.name) log.error("User %s not allowed to add a book to %s", current_user, shelf)
if not request.is_xhr: if not request.is_xhr:
flash(_(u"Sorry you are not allowed to add a book to the the shelf: %(shelfname)s", shelfname=shelf.name), flash(_(u"Sorry you are not allowed to add a book to the the shelf: %(shelfname)s", shelfname=shelf.name),
category="error") category="error")
...@@ -50,7 +56,7 @@ def add_to_shelf(shelf_id, book_id): ...@@ -50,7 +56,7 @@ def add_to_shelf(shelf_id, book_id):
return "Sorry you are not allowed to add a book to the the shelf: %s" % shelf.name, 403 return "Sorry you are not allowed to add a book to the the shelf: %s" % shelf.name, 403
if shelf.is_public and not current_user.role_edit_shelfs(): if shelf.is_public and not current_user.role_edit_shelfs():
app.logger.info("User is not allowed to edit public shelves") log.info("User %s not allowed to edit public shelves", current_user)
if not request.is_xhr: if not request.is_xhr:
flash(_(u"You are not allowed to edit public shelves"), category="error") flash(_(u"You are not allowed to edit public shelves"), category="error")
return redirect(url_for('web.index')) return redirect(url_for('web.index'))
...@@ -59,7 +65,7 @@ def add_to_shelf(shelf_id, book_id): ...@@ -59,7 +65,7 @@ def add_to_shelf(shelf_id, book_id):
book_in_shelf = ub.session.query(ub.BookShelf).filter(ub.BookShelf.shelf == shelf_id, book_in_shelf = ub.session.query(ub.BookShelf).filter(ub.BookShelf.shelf == shelf_id,
ub.BookShelf.book_id == book_id).first() ub.BookShelf.book_id == book_id).first()
if book_in_shelf: if book_in_shelf:
app.logger.info("Book is already part of the shelf: %s" % shelf.name) log.error("Book %s is already part of %s", book_id, shelf)
if not request.is_xhr: if not request.is_xhr:
flash(_(u"Book is already part of the shelf: %(shelfname)s", shelfname=shelf.name), category="error") flash(_(u"Book is already part of the shelf: %(shelfname)s", shelfname=shelf.name), category="error")
return redirect(url_for('web.index')) return redirect(url_for('web.index'))
...@@ -88,17 +94,17 @@ def add_to_shelf(shelf_id, book_id): ...@@ -88,17 +94,17 @@ def add_to_shelf(shelf_id, book_id):
def search_to_shelf(shelf_id): def search_to_shelf(shelf_id):
shelf = ub.session.query(ub.Shelf).filter(ub.Shelf.id == shelf_id).first() shelf = ub.session.query(ub.Shelf).filter(ub.Shelf.id == shelf_id).first()
if shelf is None: if shelf is None:
app.logger.info("Invalid shelf specified") log.error("Invalid shelf specified: %s", shelf_id)
flash(_(u"Invalid shelf specified"), category="error") flash(_(u"Invalid shelf specified"), category="error")
return redirect(url_for('web.index')) return redirect(url_for('web.index'))
if not shelf.is_public and not shelf.user_id == int(current_user.id): if not shelf.is_public and not shelf.user_id == int(current_user.id):
app.logger.info("You are not allowed to add a book to the the shelf: %s" % shelf.name) log.error("User %s not allowed to add a book to %s", current_user, shelf)
flash(_(u"You are not allowed to add a book to the the shelf: %(name)s", name=shelf.name), category="error") flash(_(u"You are not allowed to add a book to the the shelf: %(name)s", name=shelf.name), category="error")
return redirect(url_for('web.index')) return redirect(url_for('web.index'))
if shelf.is_public and not current_user.role_edit_shelfs(): if shelf.is_public and not current_user.role_edit_shelfs():
app.logger.info("User is not allowed to edit public shelves") log.error("User %s not allowed to edit public shelves", current_user)
flash(_(u"User is not allowed to edit public shelves"), category="error") flash(_(u"User is not allowed to edit public shelves"), category="error")
return redirect(url_for('web.index')) return redirect(url_for('web.index'))
...@@ -116,7 +122,7 @@ def search_to_shelf(shelf_id): ...@@ -116,7 +122,7 @@ def search_to_shelf(shelf_id):
books_for_shelf = searched_ids[current_user.id] books_for_shelf = searched_ids[current_user.id]
if not books_for_shelf: if not books_for_shelf:
app.logger.info("Books are already part of the shelf: %s" % shelf.name) log.error("Books are already part of %s", shelf)
flash(_(u"Books are already part of the shelf: %(name)s", name=shelf.name), category="error") flash(_(u"Books are already part of the shelf: %(name)s", name=shelf.name), category="error")
return redirect(url_for('web.index')) return redirect(url_for('web.index'))
...@@ -142,7 +148,7 @@ def search_to_shelf(shelf_id): ...@@ -142,7 +148,7 @@ def search_to_shelf(shelf_id):
def remove_from_shelf(shelf_id, book_id): def remove_from_shelf(shelf_id, book_id):
shelf = ub.session.query(ub.Shelf).filter(ub.Shelf.id == shelf_id).first() shelf = ub.session.query(ub.Shelf).filter(ub.Shelf.id == shelf_id).first()
if shelf is None: if shelf is None:
app.logger.info("Invalid shelf specified") log.error("Invalid shelf specified: %s", shelf_id)
if not request.is_xhr: if not request.is_xhr:
return redirect(url_for('web.index')) return redirect(url_for('web.index'))
return "Invalid shelf specified", 400 return "Invalid shelf specified", 400
...@@ -161,7 +167,7 @@ def remove_from_shelf(shelf_id, book_id): ...@@ -161,7 +167,7 @@ def remove_from_shelf(shelf_id, book_id):
ub.BookShelf.book_id == book_id).first() ub.BookShelf.book_id == book_id).first()
if book_shelf is None: if book_shelf is None:
app.logger.info("Book already removed from shelf") log.error("Book %s already removed from %s", book_id, shelf)
if not request.is_xhr: if not request.is_xhr:
return redirect(url_for('web.index')) return redirect(url_for('web.index'))
return "Book already removed from shelf", 410 return "Book already removed from shelf", 410
...@@ -174,7 +180,7 @@ def remove_from_shelf(shelf_id, book_id): ...@@ -174,7 +180,7 @@ def remove_from_shelf(shelf_id, book_id):
return redirect(request.environ["HTTP_REFERER"]) return redirect(request.environ["HTTP_REFERER"])
return "", 204 return "", 204
else: else:
app.logger.info("Sorry you are not allowed to remove a book from this shelf: %s" % shelf.name) log.error("User %s not allowed to remove a book from %s", current_user, shelf)
if not request.is_xhr: if not request.is_xhr:
flash(_(u"Sorry you are not allowed to remove a book from this shelf: %(sname)s", sname=shelf.name), flash(_(u"Sorry you are not allowed to remove a book from this shelf: %(sname)s", sname=shelf.name),
category="error") category="error")
...@@ -248,15 +254,15 @@ def delete_shelf(shelf_id): ...@@ -248,15 +254,15 @@ def delete_shelf(shelf_id):
else: else:
if (not cur_shelf.is_public and cur_shelf.user_id == int(current_user.id)) \ if (not cur_shelf.is_public and cur_shelf.user_id == int(current_user.id)) \
or (cur_shelf.is_public and current_user.role_edit_shelfs()): or (cur_shelf.is_public and current_user.role_edit_shelfs()):
deleted = ub.session.query(ub.Shelf).filter(ub.or_(ub.and_(ub.Shelf.user_id == int(current_user.id), deleted = ub.session.query(ub.Shelf).filter(or_(and_(ub.Shelf.user_id == int(current_user.id),
ub.Shelf.id == shelf_id), ub.Shelf.id == shelf_id),
ub.and_(ub.Shelf.is_public == 1, and_(ub.Shelf.is_public == 1,
ub.Shelf.id == shelf_id))).delete() ub.Shelf.id == shelf_id))).delete()
if deleted: if deleted:
ub.session.query(ub.BookShelf).filter(ub.BookShelf.shelf == shelf_id).delete() ub.session.query(ub.BookShelf).filter(ub.BookShelf.shelf == shelf_id).delete()
ub.session.commit() ub.session.commit()
app.logger.info(_(u"successfully deleted shelf %(name)s", name=cur_shelf.name, category="success")) log.info("successfully deleted %s", cur_shelf)
return redirect(url_for('web.index')) return redirect(url_for('web.index'))
# @shelf.route("/shelfdown/<int:shelf_id>") # @shelf.route("/shelfdown/<int:shelf_id>")
...@@ -267,10 +273,10 @@ def show_shelf(type, shelf_id): ...@@ -267,10 +273,10 @@ def show_shelf(type, shelf_id):
if current_user.is_anonymous: if current_user.is_anonymous:
shelf = ub.session.query(ub.Shelf).filter(ub.Shelf.is_public == 1, ub.Shelf.id == shelf_id).first() shelf = ub.session.query(ub.Shelf).filter(ub.Shelf.is_public == 1, ub.Shelf.id == shelf_id).first()
else: else:
shelf = ub.session.query(ub.Shelf).filter(ub.or_(ub.and_(ub.Shelf.user_id == int(current_user.id), shelf = ub.session.query(ub.Shelf).filter(or_(and_(ub.Shelf.user_id == int(current_user.id),
ub.Shelf.id == shelf_id), ub.Shelf.id == shelf_id),
ub.and_(ub.Shelf.is_public == 1, and_(ub.Shelf.is_public == 1,
ub.Shelf.id == shelf_id))).first() ub.Shelf.id == shelf_id))).first()
result = list() result = list()
# user is allowed to access shelf # user is allowed to access shelf
if shelf: if shelf:
...@@ -283,7 +289,7 @@ def show_shelf(type, shelf_id): ...@@ -283,7 +289,7 @@ def show_shelf(type, shelf_id):
if cur_book: if cur_book:
result.append(cur_book) result.append(cur_book)
else: else:
app.logger.info('Not existing book %s in shelf %s deleted' % (book.book_id, shelf.id)) log.info('Not existing book %s in %s deleted', book.book_id, shelf)
ub.session.query(ub.BookShelf).filter(ub.BookShelf.book_id == book.book_id).delete() ub.session.query(ub.BookShelf).filter(ub.BookShelf.book_id == book.book_id).delete()
ub.session.commit() ub.session.commit()
return render_title_template(page, entries=result, title=_(u"Shelf: '%(name)s'", name=shelf.name), return render_title_template(page, entries=result, title=_(u"Shelf: '%(name)s'", name=shelf.name),
...@@ -309,10 +315,10 @@ def order_shelf(shelf_id): ...@@ -309,10 +315,10 @@ def order_shelf(shelf_id):
if current_user.is_anonymous: if current_user.is_anonymous:
shelf = ub.session.query(ub.Shelf).filter(ub.Shelf.is_public == 1, ub.Shelf.id == shelf_id).first() shelf = ub.session.query(ub.Shelf).filter(ub.Shelf.is_public == 1, ub.Shelf.id == shelf_id).first()
else: else:
shelf = ub.session.query(ub.Shelf).filter(ub.or_(ub.and_(ub.Shelf.user_id == int(current_user.id), shelf = ub.session.query(ub.Shelf).filter(or_(and_(ub.Shelf.user_id == int(current_user.id),
ub.Shelf.id == shelf_id), ub.Shelf.id == shelf_id),
ub.and_(ub.Shelf.is_public == 1, and_(ub.Shelf.is_public == 1,
ub.Shelf.id == shelf_id))).first() ub.Shelf.id == shelf_id))).first()
result = list() result = list()
if shelf: if shelf:
books_in_shelf2 = ub.session.query(ub.BookShelf).filter(ub.BookShelf.shelf == shelf_id) \ books_in_shelf2 = ub.session.query(ub.BookShelf).filter(ub.BookShelf.shelf == shelf_id) \
......
.tooltip.bottom .tooltip-inner{font-size:13px;font-family:Open Sans Semibold,Helvetica Neue,Helvetica,Arial,sans-serif;-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale;padding:3px 10px;border-radius:4px;background-color:#fff;-webkit-box-shadow:0 4px 10px 0 rgba(0,0,0,.35);box-shadow:0 4px 10px 0 rgba(0,0,0,.35);opacity:1;white-space:nowrap;margin-top:-16px!important;line-height:1.71428571;color:#ddd}
@font-face { @font-face {
font-family: 'Grand Hotel'; font-family: 'Grand Hotel';
font-style: normal; font-style: normal;
......
...@@ -16,9 +16,11 @@ ...@@ -16,9 +16,11 @@
# #
# You should have received a copy of the GNU General Public License # You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>. # along with this program. If not, see <http://www.gnu.org/licenses/>.
import subprocess
import os from __future__ import division, print_function, unicode_literals
import sys import sys
import os
import subprocess
def process_open(command, quotes=(), env=None, sout=subprocess.PIPE): def process_open(command, quotes=(), env=None, sout=subprocess.PIPE):
......
...@@ -54,16 +54,16 @@ ...@@ -54,16 +54,16 @@
<div class="discover load-more"> <div class="discover load-more">
<h2 class="{{title}}">{{_(title)}}</h2> <h2 class="{{title}}">{{_(title)}}</h2>
<div class="filterheader hidden-xs hidden-sm"> <div class="filterheader hidden-xs hidden-sm">
<a id="new" class="btn btn-primary" href="{{url_for('web.books_list', data=page, sort='new')}}"><span class="glyphicon glyphicon-sort-by-order"></span></a> <a data-toggle="tooltip" id="new" class="btn btn-primary" href="{{url_for('web.books_list', data=page, sort='new')}}"><span class="glyphicon glyphicon-sort-by-attributes"></span></a>
<a id="old" class="btn btn-primary" href="{{url_for('web.books_list', data=page, sort='old')}}"><span class="glyphicon glyphicon-sort-by-order-alt"></span></a> <a id="old" class="btn btn-primary" href="{{url_for('web.books_list', data=page, sort='old')}}"><span class="glyphicon glyphicon-sort-by-attributes-alt"></span></a>
<a id="asc" class="btn btn-primary" href="{{url_for('web.books_list', data=page, sort='abc')}}"><span class="glyphicon glyphicon-font"></span><span class="glyphicon glyphicon-sort-by-alphabet"></span></a> <a id="asc" class="btn btn-primary" href="{{url_for('web.books_list', data=page, sort='abc')}}"><span class="glyphicon glyphicon-font"></span><span class="glyphicon glyphicon-sort-by-alphabet"></span></a>
<a id="desc" class="btn btn-primary" href="{{url_for('web.books_list', data=page, sort='zyx')}}"><span class="glyphicon glyphicon-font"></span><span class="glyphicon glyphicon-sort-by-alphabet-alt"></span></a> <a id="desc" class="btn btn-primary" href="{{url_for('web.books_list', data=page, sort='zyx')}}"><span class="glyphicon glyphicon-font"></span><span class="glyphicon glyphicon-sort-by-alphabet-alt"></span></a>
<a id="pub_new" class="btn btn-primary" href="{{url_for('web.books_list', data=page, sort='pubnew')}}"><span class="glyphicon glyphicon-calendar"></span><span class="glyphicon glyphicon-sort-by-order"></span></a> <a id="pub_new" class="btn btn-primary" href="{{url_for('web.books_list', data=page, sort='pubnew')}}"><span class="glyphicon glyphicon-calendar"></span><span class="glyphicon glyphicon-sort-by-order"></span></a>
<a id="pub_old" class="btn btn-primary" href="{{url_for('web.books_list', data=page, sort='pubold')}}"><span class="glyphicon glyphicon-calendar"></span><span class="glyphicon glyphicon-sort-by-order-alt"></span></a> <a id="pub_old" class="btn btn-primary" href="{{url_for('web.books_list', data=page, sort='pubold')}}"><span class="glyphicon glyphicon-calendar"></span><span class="glyphicon glyphicon-sort-by-order-alt"></span></a>
</div> <div class="btn-group character">
<div class="btn-group character"> <a id="no_shelf" class="btn btn-primary" href="{{url_for('web.books_list', data=page, sort='pubold')}}"><span class="glyphicon glyphicon-list"></span> <b>{{_('Group by series')}}</b></a>
<a id="no_shelf" class="btn btn-primary" href="{{url_for('web.books_list', data=page, sort='pubold')}}"><span class="glyphicon glyphicon-list"></span><b>{{_('Group by series')}}</b></a>
</div> </div>
</div>
<div class="row"> <div class="row">
{% if entries[0] %} {% if entries[0] %}
......
This diff is collapsed.
...@@ -17,22 +17,28 @@ ...@@ -17,22 +17,28 @@
# You should have received a copy of the GNU General Public License # You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>. # along with this program. If not, see <http://www.gnu.org/licenses/>.
from __future__ import division, print_function, unicode_literals
from . import config, get_locale, Server, app import sys
import threading import os
import zipfile import datetime
import json
import requests import requests
import shutil
import threading
import time import time
import zipfile
from io import BytesIO from io import BytesIO
import os
import sys
import shutil
from ub import UPDATE_STABLE
from tempfile import gettempdir from tempfile import gettempdir
import datetime
import json
from flask_babel import gettext as _
from babel.dates import format_datetime from babel.dates import format_datetime
from flask_babel import gettext as _
from . import constants, logger, config, get_locale, Server
log = logger.create()
_REPOSITORY_API_URL = 'https://api.github.com/repos/janeczku/calibre-web'
def is_sha1(sha1): def is_sha1(sha1):
...@@ -53,13 +59,13 @@ class Updater(threading.Thread): ...@@ -53,13 +59,13 @@ class Updater(threading.Thread):
self.updateIndex = None self.updateIndex = None
def get_current_version_info(self): def get_current_version_info(self):
if config.get_update_channel == UPDATE_STABLE: if config.get_update_channel == constants.UPDATE_STABLE:
return self._stable_version_info() return self._stable_version_info()
else: else:
return self._nightly_version_info() return self._nightly_version_info()
def get_available_updates(self, request_method): def get_available_updates(self, request_method):
if config.get_update_channel == UPDATE_STABLE: if config.get_update_channel == constants.UPDATE_STABLE:
return self._stable_available_updates(request_method) return self._stable_available_updates(request_method)
else: else:
return self._nightly_available_updates(request_method) return self._nightly_available_updates(request_method)
...@@ -67,45 +73,45 @@ class Updater(threading.Thread): ...@@ -67,45 +73,45 @@ class Updater(threading.Thread):
def run(self): def run(self):
try: try:
self.status = 1 self.status = 1
app.logger.debug(u'Download update file') log.debug(u'Download update file')
headers = {'Accept': 'application/vnd.github.v3+json'} headers = {'Accept': 'application/vnd.github.v3+json'}
r = requests.get(self._get_request_path(), stream=True, headers=headers) r = requests.get(self._get_request_path(), stream=True, headers=headers)
r.raise_for_status() r.raise_for_status()
self.status = 2 self.status = 2
app.logger.debug(u'Opening zipfile') log.debug(u'Opening zipfile')
z = zipfile.ZipFile(BytesIO(r.content)) z = zipfile.ZipFile(BytesIO(r.content))
self.status = 3 self.status = 3
app.logger.debug(u'Extracting zipfile') log.debug(u'Extracting zipfile')
tmp_dir = gettempdir() tmp_dir = gettempdir()
z.extractall(tmp_dir) z.extractall(tmp_dir)
foldername = os.path.join(tmp_dir, z.namelist()[0])[:-1] foldername = os.path.join(tmp_dir, z.namelist()[0])[:-1]
if not os.path.isdir(foldername): if not os.path.isdir(foldername):
self.status = 11 self.status = 11
app.logger.info(u'Extracted contents of zipfile not found in temp folder') log.info(u'Extracted contents of zipfile not found in temp folder')
return return
self.status = 4 self.status = 4
app.logger.debug(u'Replacing files') log.debug(u'Replacing files')
self.update_source(foldername, config.get_main_dir) self.update_source(foldername, constants.BASE_DIR)
self.status = 6 self.status = 6
app.logger.debug(u'Preparing restart of server') log.debug(u'Preparing restart of server')
time.sleep(2) time.sleep(2)
Server.setRestartTyp(True) Server.setRestartTyp(True)
Server.stopServer() Server.stopServer()
self.status = 7 self.status = 7
time.sleep(2) time.sleep(2)
except requests.exceptions.HTTPError as ex: except requests.exceptions.HTTPError as ex:
app.logger.info( u'HTTP Error' + ' ' + str(ex)) log.info(u'HTTP Error %s', ex)
self.status = 8 self.status = 8
except requests.exceptions.ConnectionError: except requests.exceptions.ConnectionError:
app.logger.info(u'Connection error') log.info(u'Connection error')
self.status = 9 self.status = 9
except requests.exceptions.Timeout: except requests.exceptions.Timeout:
app.logger.info(u'Timeout while establishing connection') log.info(u'Timeout while establishing connection')
self.status = 10 self.status = 10
except requests.exceptions.RequestException: except requests.exceptions.RequestException:
self.status = 11 self.status = 11
app.logger.info(u'General error') log.info(u'General error')
def get_update_status(self): def get_update_status(self):
return self.status return self.status
...@@ -153,14 +159,14 @@ class Updater(threading.Thread): ...@@ -153,14 +159,14 @@ class Updater(threading.Thread):
if sys.platform == "win32" or sys.platform == "darwin": if sys.platform == "win32" or sys.platform == "darwin":
change_permissions = False change_permissions = False
else: else:
app.logger.debug('Update on OS-System : ' + sys.platform) log.debug('Update on OS-System : %s', sys.platform)
new_permissions = os.stat(root_dst_dir) new_permissions = os.stat(root_dst_dir)
# print new_permissions # print new_permissions
for src_dir, __, files in os.walk(root_src_dir): for src_dir, __, files in os.walk(root_src_dir):
dst_dir = src_dir.replace(root_src_dir, root_dst_dir, 1) dst_dir = src_dir.replace(root_src_dir, root_dst_dir, 1)
if not os.path.exists(dst_dir): if not os.path.exists(dst_dir):
os.makedirs(dst_dir) os.makedirs(dst_dir)
app.logger.debug('Create-Dir: '+dst_dir) log.debug('Create-Dir: %s', dst_dir)
if change_permissions: if change_permissions:
# print('Permissions: User '+str(new_permissions.st_uid)+' Group '+str(new_permissions.st_uid)) # print('Permissions: User '+str(new_permissions.st_uid)+' Group '+str(new_permissions.st_uid))
os.chown(dst_dir, new_permissions.st_uid, new_permissions.st_gid) os.chown(dst_dir, new_permissions.st_uid, new_permissions.st_gid)
...@@ -170,22 +176,22 @@ class Updater(threading.Thread): ...@@ -170,22 +176,22 @@ class Updater(threading.Thread):
if os.path.exists(dst_file): if os.path.exists(dst_file):
if change_permissions: if change_permissions:
permission = os.stat(dst_file) permission = os.stat(dst_file)
app.logger.debug('Remove file before copy: '+dst_file) log.debug('Remove file before copy: %s', dst_file)
os.remove(dst_file) os.remove(dst_file)
else: else:
if change_permissions: if change_permissions:
permission = new_permissions permission = new_permissions
shutil.move(src_file, dst_dir) shutil.move(src_file, dst_dir)
app.logger.debug('Move File '+src_file+' to '+dst_dir) log.debug('Move File %s to %s', src_file, dst_dir)
if change_permissions: if change_permissions:
try: try:
os.chown(dst_file, permission.st_uid, permission.st_gid) os.chown(dst_file, permission.st_uid, permission.st_gid)
except (Exception) as e: except (Exception) as e:
# ex = sys.exc_info() # ex = sys.exc_info()
old_permissions = os.stat(dst_file) old_permissions = os.stat(dst_file)
app.logger.debug('Fail change permissions of ' + str(dst_file) + '. Before: ' log.debug('Fail change permissions of %s. Before: %s:%s After %s:%s error: %s',
+ str(old_permissions.st_uid) + ':' + str(old_permissions.st_gid) + ' After: ' dst_file, old_permissions.st_uid, old_permissions.st_gid,
+ str(permission.st_uid) + ':' + str(permission.st_gid) + ' error: '+str(e)) permission.st_uid, permission.st_gid, e)
return return
def update_source(self, source, destination): def update_source(self, source, destination):
...@@ -219,15 +225,15 @@ class Updater(threading.Thread): ...@@ -219,15 +225,15 @@ class Updater(threading.Thread):
for item in remove_items: for item in remove_items:
item_path = os.path.join(destination, item[1:]) item_path = os.path.join(destination, item[1:])
if os.path.isdir(item_path): if os.path.isdir(item_path):
app.logger.debug("Delete dir " + item_path) log.debug("Delete dir %s", item_path)
shutil.rmtree(item_path, ignore_errors=True) shutil.rmtree(item_path, ignore_errors=True)
else: else:
try: try:
app.logger.debug("Delete file " + item_path) log.debug("Delete file %s", item_path)
# log_from_thread("Delete file " + item_path) # log_from_thread("Delete file " + item_path)
os.remove(item_path) os.remove(item_path)
except Exception: except Exception:
app.logger.debug("Could not remove:" + item_path) log.debug("Could not remove: %s", item_path)
shutil.rmtree(source, ignore_errors=True) shutil.rmtree(source, ignore_errors=True)
@classmethod @classmethod
...@@ -243,12 +249,12 @@ class Updater(threading.Thread): ...@@ -243,12 +249,12 @@ class Updater(threading.Thread):
@classmethod @classmethod
def _stable_version_info(self): def _stable_version_info(self):
return {'version': '0.6.4 Beta'} # Current version return constants.STABLE_VERSION # Current version
def _nightly_available_updates(self, request_method): def _nightly_available_updates(self, request_method):
tz = datetime.timedelta(seconds=time.timezone if (time.localtime().tm_isdst == 0) else time.altzone) tz = datetime.timedelta(seconds=time.timezone if (time.localtime().tm_isdst == 0) else time.altzone)
if request_method == "GET": if request_method == "GET":
repository_url = 'https://api.github.com/repos/janeczku/calibre-web' repository_url = _REPOSITORY_API_URL
status, commit = self._load_remote_data(repository_url +'/git/refs/heads/master') status, commit = self._load_remote_data(repository_url +'/git/refs/heads/master')
parents = [] parents = []
if status['message'] != '': if status['message'] != '':
...@@ -348,7 +354,7 @@ class Updater(threading.Thread): ...@@ -348,7 +354,7 @@ class Updater(threading.Thread):
if request_method == "GET": if request_method == "GET":
parents = [] parents = []
# repository_url = 'https://api.github.com/repos/flatpak/flatpak/releases' # test URL # repository_url = 'https://api.github.com/repos/flatpak/flatpak/releases' # test URL
repository_url = 'https://api.github.com/repos/janeczku/calibre-web/releases?per_page=100' repository_url = _REPOSITORY_API_URL + '/releases?per_page=100'
status, commit = self._load_remote_data(repository_url) status, commit = self._load_remote_data(repository_url)
if status['message'] != '': if status['message'] != '':
return json.dumps(status) return json.dumps(status)
...@@ -434,10 +440,10 @@ class Updater(threading.Thread): ...@@ -434,10 +440,10 @@ class Updater(threading.Thread):
return json.dumps(status) return json.dumps(status)
def _get_request_path(self): def _get_request_path(self):
if config.get_update_channel == UPDATE_STABLE: if config.get_update_channel == constants.UPDATE_STABLE:
return self.updateFile return self.updateFile
else: else:
return 'https://api.github.com/repos/janeczku/calibre-web/zipball/master' return _REPOSITORY_API_URL + '/zipball/master'
def _load_remote_data(self, repository_url): def _load_remote_data(self, repository_url):
status = { status = {
......
...@@ -17,13 +17,19 @@ ...@@ -17,13 +17,19 @@
# You should have received a copy of the GNU General Public License # You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>. # along with this program. If not, see <http://www.gnu.org/licenses/>.
from __future__ import division, print_function, unicode_literals
from tempfile import gettempdir
import hashlib
import os import os
import hashlib
from tempfile import gettempdir
from flask_babel import gettext as _ from flask_babel import gettext as _
import comic
from . import app from . import logger, comic
from .constants import BookMeta
log = logger.create()
try: try:
from lxml.etree import LXML_VERSION as lxmlversion from lxml.etree import LXML_VERSION as lxmlversion
...@@ -36,7 +42,7 @@ try: ...@@ -36,7 +42,7 @@ try:
from wand.exceptions import PolicyError from wand.exceptions import PolicyError
use_generic_pdf_cover = False use_generic_pdf_cover = False
except (ImportError, RuntimeError) as e: except (ImportError, RuntimeError) as e:
app.logger.warning('cannot import Image, generating pdf covers for pdf uploads will not work: %s', e) log.warning('cannot import Image, generating pdf covers for pdf uploads will not work: %s', e)
use_generic_pdf_cover = True use_generic_pdf_cover = True
try: try:
...@@ -44,29 +50,29 @@ try: ...@@ -44,29 +50,29 @@ try:
from PyPDF2 import __version__ as PyPdfVersion from PyPDF2 import __version__ as PyPdfVersion
use_pdf_meta = True use_pdf_meta = True
except ImportError as e: except ImportError as e:
app.logger.warning('cannot import PyPDF2, extracting pdf metadata will not work: %s', e) log.warning('cannot import PyPDF2, extracting pdf metadata will not work: %s', e)
use_pdf_meta = False use_pdf_meta = False
try: try:
import epub from . import epub
use_epub_meta = True use_epub_meta = True
except ImportError as e: except ImportError as e:
app.logger.warning('cannot import epub, extracting epub metadata will not work: %s', e) log.warning('cannot import epub, extracting epub metadata will not work: %s', e)
use_epub_meta = False use_epub_meta = False
try: try:
import fb2 from . import fb2
use_fb2_meta = True use_fb2_meta = True
except ImportError as e: except ImportError as e:
app.logger.warning('cannot import fb2, extracting fb2 metadata will not work: %s', e) log.warning('cannot import fb2, extracting fb2 metadata will not work: %s', e)
use_fb2_meta = False use_fb2_meta = False
try: try:
from PIL import Image from PIL import Image
from PIL import __version__ as PILversion from PIL import __version__ as PILversion
use_PIL = True use_PIL = True
except ImportError: except ImportError as e:
app.logger.warning('cannot import Pillow, using png and webp images as cover will not work: %s', e) log.warning('cannot import Pillow, using png and webp images as cover will not work: %s', e)
use_generic_pdf_cover = True use_generic_pdf_cover = True
use_PIL = False use_PIL = False
...@@ -88,7 +94,7 @@ def process(tmp_file_path, original_file_name, original_file_extension): ...@@ -88,7 +94,7 @@ def process(tmp_file_path, original_file_name, original_file_extension):
meta = comic.get_comic_info(tmp_file_path, original_file_name, original_file_extension) meta = comic.get_comic_info(tmp_file_path, original_file_name, original_file_extension)
except Exception as ex: except Exception as ex:
app.logger.warning('cannot parse metadata, using default: %s', ex) log.warning('cannot parse metadata, using default: %s', ex)
if meta and meta.title.strip() and meta.author.strip(): if meta and meta.title.strip() and meta.author.strip():
return meta return meta
...@@ -192,10 +198,10 @@ def pdf_preview(tmp_file_path, tmp_dir): ...@@ -192,10 +198,10 @@ def pdf_preview(tmp_file_path, tmp_dir):
img.save(filename=os.path.join(tmp_dir, cover_file_name)) img.save(filename=os.path.join(tmp_dir, cover_file_name))
return cover_file_name return cover_file_name
except PolicyError as ex: except PolicyError as ex:
app.logger.warning('Pdf extraction forbidden by Imagemagick policy: %s', ex) log.warning('Pdf extraction forbidden by Imagemagick policy: %s', ex)
return None return None
except Exception as ex: except Exception as ex:
app.logger.warning('Cannot extract cover image, using default: %s', ex) log.warning('Cannot extract cover image, using default: %s', ex)
return None return None
......
This diff is collapsed.
...@@ -17,21 +17,15 @@ ...@@ -17,21 +17,15 @@
# You should have received a copy of the GNU General Public License # You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>. # along with this program. If not, see <http://www.gnu.org/licenses/>.
from __future__ import print_function from __future__ import division, print_function, unicode_literals
import smtplib
import threading
from datetime import datetime
import logging
import time
import socket
import sys import sys
import os import os
from email.generator import Generator
from . import config, db, app
from flask_babel import gettext as _
import re import re
from .gdriveutils import getFileFromEbooksFolder, updateGdriveCalibreFromLocal import smtplib
from .subproc_wrapper import process_open import socket
import time
import threading
from datetime import datetime
try: try:
from StringIO import StringIO from StringIO import StringIO
...@@ -47,6 +41,14 @@ except ImportError: ...@@ -47,6 +41,14 @@ except ImportError:
from email import encoders from email import encoders
from email.utils import formatdate from email.utils import formatdate
from email.utils import make_msgid from email.utils import make_msgid
from email.generator import Generator
from flask_babel import gettext as _
from . import logger, config, db, gdriveutils
from .subproc_wrapper import process_open
log = logger.create()
chunksize = 8192 chunksize = 8192
# task 'status' consts # task 'status' consts
...@@ -70,7 +72,7 @@ def get_attachment(bookpath, filename): ...@@ -70,7 +72,7 @@ def get_attachment(bookpath, filename):
"""Get file as MIMEBase message""" """Get file as MIMEBase message"""
calibrepath = config.config_calibre_dir calibrepath = config.config_calibre_dir
if config.config_use_google_drive: if config.config_use_google_drive:
df = getFileFromEbooksFolder(bookpath, filename) df = gdriveutils.getFileFromEbooksFolder(bookpath, filename)
if df: if df:
datafile = os.path.join(calibrepath, bookpath, filename) datafile = os.path.join(calibrepath, bookpath, filename)
if not os.path.exists(os.path.join(calibrepath, bookpath)): if not os.path.exists(os.path.join(calibrepath, bookpath)):
...@@ -88,8 +90,8 @@ def get_attachment(bookpath, filename): ...@@ -88,8 +90,8 @@ def get_attachment(bookpath, filename):
data = file_.read() data = file_.read()
file_.close() file_.close()
except IOError as e: except IOError as e:
app.logger.exception(e) # traceback.print_exc() log.exception(e) # traceback.print_exc()
app.logger.error(u'The requested file could not be read. Maybe wrong permissions?') log.error(u'The requested file could not be read. Maybe wrong permissions?')
return None return None
attachment = MIMEBase('application', 'octet-stream') attachment = MIMEBase('application', 'octet-stream')
...@@ -114,7 +116,7 @@ class emailbase(): ...@@ -114,7 +116,7 @@ class emailbase():
def send(self, strg): def send(self, strg):
"""Send `strg' to the server.""" """Send `strg' to the server."""
app.logger.debug('send:' + repr(strg[:300])) log.debug('send: %r', strg[:300])
if hasattr(self, 'sock') and self.sock: if hasattr(self, 'sock') and self.sock:
try: try:
if self.transferSize: if self.transferSize:
...@@ -139,7 +141,7 @@ class emailbase(): ...@@ -139,7 +141,7 @@ class emailbase():
raise smtplib.SMTPServerDisconnected('please run connect() first') raise smtplib.SMTPServerDisconnected('please run connect() first')
def _print_debug(self, *args): def _print_debug(self, *args):
app.logger.debug(args) log.debug(args)
def getTransferStatus(self): def getTransferStatus(self):
if self.transferSize: if self.transferSize:
...@@ -236,7 +238,7 @@ class WorkerThread(threading.Thread): ...@@ -236,7 +238,7 @@ class WorkerThread(threading.Thread):
filename = self._convert_ebook_format() filename = self._convert_ebook_format()
if filename: if filename:
if config.config_use_google_drive: if config.config_use_google_drive:
updateGdriveCalibreFromLocal() gdriveutils.updateGdriveCalibreFromLocal()
if curr_task == TASK_CONVERT: if curr_task == TASK_CONVERT:
self.add_email(self.queue[self.current]['settings']['subject'], self.queue[self.current]['path'], self.add_email(self.queue[self.current]['settings']['subject'], self.queue[self.current]['path'],
filename, self.queue[self.current]['settings'], self.queue[self.current]['kindle'], filename, self.queue[self.current]['settings'], self.queue[self.current]['kindle'],
...@@ -254,14 +256,14 @@ class WorkerThread(threading.Thread): ...@@ -254,14 +256,14 @@ class WorkerThread(threading.Thread):
# if it does - mark the conversion task as complete and return a success # if it does - mark the conversion task as complete and return a success
# this will allow send to kindle workflow to continue to work # this will allow send to kindle workflow to continue to work
if os.path.isfile(file_path + format_new_ext): if os.path.isfile(file_path + format_new_ext):
app.logger.info("Book id %d already converted to %s", bookid, format_new_ext) log.info("Book id %d already converted to %s", bookid, format_new_ext)
cur_book = db.session.query(db.Books).filter(db.Books.id == bookid).first() cur_book = db.session.query(db.Books).filter(db.Books.id == bookid).first()
self.queue[self.current]['path'] = file_path self.queue[self.current]['path'] = file_path
self.queue[self.current]['title'] = cur_book.title self.queue[self.current]['title'] = cur_book.title
self._handleSuccess() self._handleSuccess()
return file_path + format_new_ext return file_path + format_new_ext
else: else:
app.logger.info("Book id %d - target format of %s does not exist. Moving forward with convert.", bookid, format_new_ext) log.info("Book id %d - target format of %s does not exist. Moving forward with convert.", bookid, format_new_ext)
# check if converter-executable is existing # check if converter-executable is existing
if not os.path.exists(config.config_converterpath): if not os.path.exists(config.config_converterpath):
...@@ -317,13 +319,13 @@ class WorkerThread(threading.Thread): ...@@ -317,13 +319,13 @@ class WorkerThread(threading.Thread):
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",
error=conv_error.group(1), message=conv_error.group(2).strip()) error=conv_error.group(1), message=conv_error.group(2).strip())
app.logger.debug("convert_kindlegen: " + nextline) log.debug("convert_kindlegen: %s", nextline)
else: else:
while p.poll() is None: while p.poll() is None:
nextline = p.stdout.readline() nextline = p.stdout.readline()
if os.name == 'nt' and sys.version_info < (3, 0): if os.name == 'nt' and sys.version_info < (3, 0):
nextline = nextline.decode('windows-1252') nextline = nextline.decode('windows-1252')
app.logger.debug(nextline.strip('\r\n')) log.debug(nextline.strip('\r\n'))
# parse progress string from calibre-converter # parse progress string from calibre-converter
progress = re.search("(\d+)%\s.*", nextline) progress = re.search("(\d+)%\s.*", nextline)
if progress: if progress:
...@@ -353,7 +355,7 @@ class WorkerThread(threading.Thread): ...@@ -353,7 +355,7 @@ class WorkerThread(threading.Thread):
return file_path + format_new_ext return file_path + format_new_ext
else: else:
error_message = format_new_ext.upper() + ' format not found on disk' error_message = format_new_ext.upper() + ' format not found on disk'
app.logger.info("ebook converter failed with error while converting book") log.info("ebook converter failed with error while converting book")
if not error_message: if not error_message:
error_message = 'Ebook converter failed with unknown error' error_message = 'Ebook converter failed with unknown error'
self._handleError(error_message) self._handleError(error_message)
...@@ -449,7 +451,7 @@ class WorkerThread(threading.Thread): ...@@ -449,7 +451,7 @@ class WorkerThread(threading.Thread):
# _print_debug function # _print_debug function
if sys.version_info < (3, 0): if sys.version_info < (3, 0):
org_smtpstderr = smtplib.stderr org_smtpstderr = smtplib.stderr
smtplib.stderr = StderrLogger() smtplib.stderr = logger.StderrLogger('worker.smtp')
if use_ssl == 2: if use_ssl == 2:
self.asyncSMTP = email_SSL(obj['settings']["mail_server"], obj['settings']["mail_port"], timeout) self.asyncSMTP = email_SSL(obj['settings']["mail_server"], obj['settings']["mail_port"], timeout)
...@@ -457,9 +459,7 @@ class WorkerThread(threading.Thread): ...@@ -457,9 +459,7 @@ class WorkerThread(threading.Thread):
self.asyncSMTP = email(obj['settings']["mail_server"], obj['settings']["mail_port"], timeout) self.asyncSMTP = email(obj['settings']["mail_server"], obj['settings']["mail_port"], timeout)
# link to logginglevel # link to logginglevel
if config.config_log_level != logging.DEBUG: if logger.is_debug_enabled():
self.asyncSMTP.set_debuglevel(0)
else:
self.asyncSMTP.set_debuglevel(1) self.asyncSMTP.set_debuglevel(1)
if use_ssl == 1: if use_ssl == 1:
self.asyncSMTP.starttls() self.asyncSMTP.starttls()
...@@ -501,7 +501,7 @@ class WorkerThread(threading.Thread): ...@@ -501,7 +501,7 @@ class WorkerThread(threading.Thread):
return retVal return retVal
def _handleError(self, error_message): def _handleError(self, error_message):
app.logger.error(error_message) log.error(error_message)
self.UIqueue[self.current]['stat'] = STAT_FAIL self.UIqueue[self.current]['stat'] = STAT_FAIL
self.UIqueue[self.current]['progress'] = "100 %" self.UIqueue[self.current]['progress'] = "100 %"
self.UIqueue[self.current]['runtime'] = self._formatRuntime( self.UIqueue[self.current]['runtime'] = self._formatRuntime(
...@@ -513,22 +513,3 @@ class WorkerThread(threading.Thread): ...@@ -513,22 +513,3 @@ class WorkerThread(threading.Thread):
self.UIqueue[self.current]['progress'] = "100 %" self.UIqueue[self.current]['progress'] = "100 %"
self.UIqueue[self.current]['runtime'] = self._formatRuntime( self.UIqueue[self.current]['runtime'] = self._formatRuntime(
datetime.now() - self.queue[self.current]['starttime']) datetime.now() - self.queue[self.current]['starttime'])
# Enable logging of smtp lib debug output
class StderrLogger(object):
buffer = ''
def __init__(self):
self.logger = app.logger
def write(self, message):
try:
if message == '\n':
self.logger.debug(self.buffer.replace("\n","\\n"))
self.buffer = ''
else:
self.buffer += message
except:
self.logger.debug("Logging Error")
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment