From 2c615fdf050e5d360cd20b70c96b18ba842bb8aa Mon Sep 17 00:00:00 2001 From: OzzieIsaacs Date: Sat, 28 Jan 2017 20:16:40 +0100 Subject: [PATCH] Finalize graphical setup for calibre-web --- cps.py | 18 +- cps/db.py | 41 +++- cps/fb2.py | 2 +- cps/helper.py | 43 ++-- cps/templates/admin.html | 2 +- cps/templates/config_edit.html | 22 +- cps/templates/index.html | 2 +- cps/templates/stats.html | 8 +- cps/templates/user_edit.html | 18 +- cps/ub.py | 248 ++++++++++++------- cps/web.py | 429 +++++++++++++++++++-------------- 11 files changed, 508 insertions(+), 325 deletions(-) diff --git a/cps.py b/cps.py index c2913dc3..b8f51e02 100755 --- a/cps.py +++ b/cps.py @@ -3,27 +3,25 @@ import sys import time base_path = os.path.dirname(os.path.abspath(__file__)) - # Insert local directories into path sys.path.insert(0, os.path.join(base_path, 'vendor')) from cps import web -# from cps import config from tornado.wsgi import WSGIContainer from tornado.httpserver import HTTPServer from tornado.ioloop import IOLoop if __name__ == '__main__': - '''if config.DEVELOPMENT: - web.app.run(host="0.0.0.0", port=web.config.config_port, debug=True) - else:''' - http_server = HTTPServer(WSGIContainer(web.app)) - http_server.listen(web.config.config_port) - IOLoop.instance().start() + if web.ub.DEVELOPMENT: + web.app.run(host="0.0.0.0", port=web.ub.config.config_port, debug=True) + else: + http_server = HTTPServer(WSGIContainer(web.app)) + http_server.listen(web.ub.config.config_port) + IOLoop.instance().start() if web.global_task == 0: print("Performing restart of Calibre-web") - os.execl(sys.executable,sys.executable, *sys.argv) + os.execl(sys.executable, sys.executable, *sys.argv) else: print("Performing shutdown of Calibre-web") - os._exit(0) + sys.exit(0) diff --git a/cps/db.py b/cps/db.py index a16672ae..d918a032 100755 --- a/cps/db.py +++ b/cps/db.py @@ -7,12 +7,21 @@ from sqlalchemy.orm import * import os import re import ast -from ub import Config +from ub import config +import ub + +session = None +cc_exceptions = None +cc_classes = None +cc_ids = None +books_custom_column_links = None +engine = None + # user defined sort function for calibre databases (Series, etc.) def title_sort(title): # calibre sort stuff - config=Config() + # config=Config() title_pat = re.compile(config.config_title_regex, re.IGNORECASE) match = title_pat.search(title) if match: @@ -216,9 +225,10 @@ class Books(Base): series = relationship('Series', secondary=books_series_link, backref='books') ratings = relationship('Ratings', secondary=books_ratings_link, backref='books') languages = relationship('Languages', secondary=books_languages_link, backref='books') - identifiers=relationship('Identifiers', backref='books') + identifiers = relationship('Identifiers', backref='books') - def __init__(self, title, sort, author_sort, timestamp, pubdate, series_index, last_modified, path, has_cover, authors, tags): + def __init__(self, title, sort, author_sort, timestamp, pubdate, series_index, last_modified, path, has_cover, + authors, tags): # ToDO check Authors and tags necessary self.title = title self.sort = sort self.author_sort = author_sort @@ -253,19 +263,33 @@ class Custom_Columns(Base): return display_dict -def setup_db(config): +def setup_db(): global session global cc_exceptions global cc_classes global cc_ids global books_custom_column_links + global engine - if config.config_calibre_dir is None: - return + if config.config_calibre_dir is None or config.config_calibre_dir == u'': + return False dbpath = os.path.join(config.config_calibre_dir, "metadata.db") engine = create_engine('sqlite:///{0}'.format(dbpath.encode('utf-8')), echo=False) - conn = engine.connect() + try: + conn = engine.connect() + + except: + content = ub.session.query(ub.Settings).first() + content.config_calibre_dir = None + content.db_configured = False + ub.session.commit() + config.loadSettings() + return False + content = ub.session.query(ub.Settings).first() + content.db_configured = True + ub.session.commit() + config.loadSettings() conn.connection.create_function('title_sort', 1, title_sort) cc = conn.execute("SELECT id, datatype FROM custom_columns") @@ -310,3 +334,4 @@ def setup_db(config): Session = sessionmaker() Session.configure(bind=engine) session = Session() + return True \ No newline at end of file diff --git a/cps/fb2.py b/cps/fb2.py index 93e3dcc2..ccc85207 100644 --- a/cps/fb2.py +++ b/cps/fb2.py @@ -3,7 +3,7 @@ from lxml import etree import os import uploader - +# ToDo: Check usage of original_file_name def get_fb2_info(tmp_file_path, original_file_name, original_file_extension): ns = { diff --git a/cps/helper.py b/cps/helper.py index 7812131c..d861af38 100755 --- a/cps/helper.py +++ b/cps/helper.py @@ -1,8 +1,8 @@ #!/usr/bin/env python # -*- coding: utf-8 -*- -import db, ub -# import config +import db +import ub from flask import current_app as app import logging import smtplib @@ -33,8 +33,9 @@ def update_download(book_id, user_id): ub.session.commit() -def make_mobi(book_id,calibrepath): - vendorpath = os.path.join(os.path.normpath(os.path.dirname(os.path.realpath(__file__)) + os.sep + "../vendor" + os.sep)) +def make_mobi(book_id, calibrepath): + vendorpath = os.path.join(os.path.normpath(os.path.dirname(os.path.realpath(__file__)) + + os.sep + "../vendor" + os.sep)) if sys.platform == "win32": kindlegen = os.path.join(vendorpath, u"kindlegen.exe") else: @@ -80,18 +81,20 @@ def make_mobi(book_id,calibrepath): class StderrLogger(object): - buffer='' + buffer = '' + def __init__(self): self.logger = logging.getLogger('cps.web') def write(self, message): - if message=='\n': + if message == '\n': self.logger.debug(self.buffer) - self.buffer='' + self.buffer = '' else: - self.buffer=self.buffer+message + self.buffer += message -def send_raw_email(kindle_mail,msg): + +def send_raw_email(kindle_mail, msg): settings = ub.get_mail_settings() msg['From'] = settings["mail_from"] @@ -107,7 +110,7 @@ def send_raw_email(kindle_mail,msg): # send email try: - timeout=600 # set timeout to 5mins + timeout = 600 # set timeout to 5mins org_stderr = smtplib.stderr smtplib.stderr = StderrLogger() @@ -140,18 +143,11 @@ def send_test_mail(kindle_mail): msg['Subject'] = _(u'Calibre-web test email') text = _(u'This email has been sent via calibre web.') msg.attach(MIMEText(text.encode('UTF-8'), 'plain', 'UTF-8')) - return send_raw_email(kindle_mail,msg) + return send_raw_email(kindle_mail, msg) -def send_mail(book_id, kindle_mail,calibrepath): +def send_mail(book_id, kindle_mail, calibrepath): """Send email with attachments""" - is_mobi = False - is_azw = False - is_azw3 = False - is_epub = False - is_pdf = False - file_path = None - settings = ub.get_mail_settings() # create MIME message msg = MIMEMultipart() msg['Subject'] = _(u'Send to Kindle') @@ -177,7 +173,7 @@ def send_mail(book_id, kindle_mail,calibrepath): if 'mobi' in formats: msg.attach(get_attachment(formats['mobi'])) elif 'epub' in formats: - filepath = make_mobi(book.id,calibrepath) + filepath = make_mobi(book.id, calibrepath) if filepath is not None: msg.attach(get_attachment(filepath)) elif filepath is None: @@ -207,8 +203,7 @@ def get_attachment(file_path): return attachment except IOError: traceback.print_exc() - message = (_('The requested file could not be read. Maybe wrong '\ - 'permissions?')) + message = (_('The requested file could not be read. Maybe wrong permissions?')) # ToDo: What is this? return None @@ -218,7 +213,7 @@ def get_valid_filename(value, replace_whitespace=True): filename. Limits num characters to 128 max. """ value = value[:128] - re_slugify = re.compile('[^\w\s-]', re.UNICODE) + # re_slugify = re.compile('[^\w\s-]', re.UNICODE) value = unicodedata.normalize('NFKD', value) re_slugify = re.compile('[^\w\s-]', re.UNICODE) value = unicode(re_slugify.sub('', value).strip()) @@ -238,7 +233,7 @@ def get_normalized_author(value): return value -def update_dir_stucture(book_id,calibrepath): +def update_dir_stucture(book_id, calibrepath): db.session.connection().connection.connection.create_function("title_sort", 1, db.title_sort) book = db.session.query(db.Books).filter(db.Books.id == book_id).first() path = os.path.join(calibrepath, book.path) diff --git a/cps/templates/admin.html b/cps/templates/admin.html index 69cadaee..9ac1c858 100644 --- a/cps/templates/admin.html +++ b/cps/templates/admin.html @@ -75,7 +75,7 @@
{{_('Configuration')}}

{{_('Administration')}}

- {% if not config.DEVELOPMENT %} + {% if not development %}
{{_('Restart Calibre-web')}}
{{_('Stop Calibre-web')}}
{% endif %} diff --git a/cps/templates/config_edit.html b/cps/templates/config_edit.html index 80c5d7f7..2dac08f6 100644 --- a/cps/templates/config_edit.html +++ b/cps/templates/config_edit.html @@ -9,7 +9,7 @@
- +
@@ -17,11 +17,11 @@
- +
- +
@@ -31,10 +31,10 @@
@@ -48,8 +48,14 @@
-
+
+ {% if not origin %} + {{_('Back')}} + {% endif %} + {% if success %} + {{_('Login')}} + {% endif %}
{% endblock %} diff --git a/cps/templates/index.html b/cps/templates/index.html index 50755bbf..9abbaff1 100755 --- a/cps/templates/index.html +++ b/cps/templates/index.html @@ -1,6 +1,6 @@ {% extends "layout.html" %} {% block body %} -{% if g.user.show_random_books() %} +{% if g.user.show_detail_random() %}

{{_('Discover (Random Books)')}}

diff --git a/cps/templates/stats.html b/cps/templates/stats.html index 998ed65e..49c13fc4 100644 --- a/cps/templates/stats.html +++ b/cps/templates/stats.html @@ -12,19 +12,19 @@ Python - {{Versions['PythonVersion']}} + {{versions['PythonVersion']}} Kindlegen - {{Versions['KindlegenVersion']}} + {{versions['KindlegenVersion']}} ImageMagick - {{Versions['ImageVersion']}} + {{versions['ImageVersion']}} PyPDF2 - {{Versions['PyPdfVersion']}} + {{versions['PyPdfVersion']}} diff --git a/cps/templates/user_edit.html b/cps/templates/user_edit.html index d9b6f466..674ca2aa 100644 --- a/cps/templates/user_edit.html +++ b/cps/templates/user_edit.html @@ -41,25 +41,33 @@
- +
- +
- +
- +
- +
+
+ + +
+
+ + +
{% if g.user and g.user.role_admin() and not profile %} {% if not content.role_anonymous() %} diff --git a/cps/ub.py b/cps/ub.py index 0268751b..d5612fdb 100644 --- a/cps/ub.py +++ b/cps/ub.py @@ -8,24 +8,41 @@ from sqlalchemy.orm import * from flask_login import AnonymousUserMixin import os import traceback +import logging from werkzeug.security import generate_password_hash from flask_babel import gettext as _ -dbpath = os.path.join(os.path.normpath(os.path.dirname(os.path.realpath(__file__))+os.sep+".."+os.sep), "app.db") +dbpath = os.path.join(os.path.normpath(os.path.dirname(os.path.realpath(__file__)) + os.sep + ".." + os.sep), "app.db") engine = create_engine('sqlite:///{0}'.format(dbpath), echo=False) Base = declarative_base() ROLE_USER = 0 ROLE_ADMIN = 1 ROLE_DOWNLOAD = 2 -ROLE_UPLOAD = 4 +ROLE_UPLOAD = 4 ROLE_EDIT = 8 ROLE_PASSWD = 16 ROLE_ANONYMOUS = 32 + +DETAIL_RANDOM = 1 +SIDEBAR_LANGUAGE = 2 +SIDEBAR_SERIES = 4 +SIDEBAR_CATEGORY = 8 +SIDEBAR_HOT = 16 +SIDEBAR_RANDOM = 32 +SIDEBAR_AUTHOR = 64 + DEFAULT_PASS = "admin123" -class UserBase(): + +DEVELOPMENT = False + + + + +class UserBase: + @staticmethod def is_authenticated(self): return True @@ -78,57 +95,55 @@ class UserBase(): return self.default_language def show_random_books(self): - return self.random_books + if self.sidebar_view is not None: + return True if self.sidebar_view & SIDEBAR_RANDOM == SIDEBAR_RANDOM else False + else: + return False def show_language(self): - return self.language_books + if self.sidebar_view is not None: + return True if self.sidebar_view & SIDEBAR_LANGUAGE == SIDEBAR_LANGUAGE else False + else: + return False def show_hot_books(self): - return self.hot_books + if self.sidebar_view is not None: + return True if self.sidebar_view & SIDEBAR_HOT == SIDEBAR_HOT else False + else: + return False def show_series(self): - return self.series_books + if self.sidebar_view is not None: + return True if self.sidebar_view & SIDEBAR_SERIES == SIDEBAR_SERIES else False + else: + return False def show_category(self): - return self.category_books + if self.sidebar_view is not None: + return True if self.sidebar_view & SIDEBAR_CATEGORY == SIDEBAR_CATEGORY else False + else: + return False + + def show_author(self): + if self.sidebar_view is not None: + return True if self.sidebar_view & SIDEBAR_AUTHOR == SIDEBAR_AUTHOR else False + else: + return False + + def show_detail_random(self): + if self.sidebar_view is not None: + return True if self.sidebar_view & DETAIL_RANDOM == DETAIL_RANDOM else False + else: + return False + def __repr__(self): return '' % self.nickname -class Config(): - def __init__(self): - self.config_main_dir=os.path.join(os.path.normpath(os.path.dirname( - os.path.realpath(__file__)) + os.sep + ".." + os.sep)) - self.db_configured=None - self.loadSettings() - def loadSettings(self): - data=session.query(Settings).first() - self.config_calibre_dir = data.config_calibre_dir - self.config_port = data.config_port - self.config_calibre_web_title = data.config_calibre_web_title - self.config_books_per_page = data.config_books_per_page - self.config_random_books = data.config_random_books - self.config_title_regex = data.config_title_regex - self.config_log_level = data.config_log_level - self.config_uploading = data.config_uploading - self.config_anonbrowse = data.config_anonbrowse - self.config_public_reg = data.config_public_reg - if self.config_calibre_dir is not None and (self.db_configured is None or self.db_configured is True): - self.db_configured=True - else: - self.db_configured = False - - @property - def get_main_dir(self): - return self.config_main_dir - - @property - def is_Calibre_Configured(self): - return self.db_configured - - -class User(UserBase,Base): +# Baseclass for Users in Calibre-web, settings which are depending on certain users are stored here. It is derived from +# User Base (all access methods are declared there) +class User(UserBase, Base): __tablename__ = 'user' id = Column(Integer, primary_key=True) @@ -140,31 +155,31 @@ class User(UserBase,Base): shelf = relationship('Shelf', backref='user', lazy='dynamic') downloads = relationship('Downloads', backref='user', lazy='dynamic') locale = Column(String(2), default="en") - random_books = Column(Integer, default=1) - language_books = Column(Integer, default=1) - series_books = Column(Integer, default=1) - category_books = Column(Integer, default=1) - hot_books = Column(Integer, default=1) + sidebar_view = Column(Integer, default=1) + #language_books = Column(Integer, default=1) + #series_books = Column(Integer, default=1) + #category_books = Column(Integer, default=1) + #hot_books = Column(Integer, default=1) default_language = Column(String(3), default="all") -class Anonymous(AnonymousUserMixin,UserBase): - # anon_browse = None - +# Class for anonymous user is derived from User base and complets overrides methods and properties for the +# anonymous user +class Anonymous(AnonymousUserMixin, UserBase): def __init__(self): self.loadSettings() def loadSettings(self): - data=session.query(User).filter(User.role.op('&')(ROLE_ANONYMOUS) == ROLE_ANONYMOUS).first() - settings=session.query(Settings).first() + data = session.query(User).filter(User.role.op('&')(ROLE_ANONYMOUS) == ROLE_ANONYMOUS).first() + settings = session.query(Settings).first() self.nickname = data.nickname self.role = data.role - self.random_books = data.random_books + self.sidebar_view = data.sidebar_view self.default_language = data.default_language - self.language_books = data.language_books - self.series_books = data.series_books - self.category_books = data.category_books - self.hot_books = data.hot_books + #self.language_books = data.language_books + #self.series_books = data.series_books + #self.category_books = data.category_books + #self.hot_books = data.hot_books self.default_language = data.default_language self.locale = data.locale self.anon_browse = settings.config_anonbrowse @@ -179,6 +194,7 @@ class Anonymous(AnonymousUserMixin,UserBase): return self.anon_browse +# Baseclass representing Shelfs in calibre-web inapp.db class Shelf(Base): __tablename__ = 'shelf' @@ -190,6 +206,8 @@ class Shelf(Base): def __repr__(self): return '' % self.name + +# Baseclass representing Relationship between books and Shelfs in Calibre-web in app.db (N:M) class BookShelf(Base): __tablename__ = 'book_shelf_link' @@ -202,6 +220,7 @@ class BookShelf(Base): return '' % self.id +# Baseclass representing Downloads from calibre-web in app.db class Downloads(Base): __tablename__ = 'downloads' @@ -212,54 +231,79 @@ class Downloads(Base): def __repr__(self): return '' % (self.mail_server) pass +# Class holds all application specific settings in calibre-web +class Config: + def __init__(self): + self.config_main_dir = os.path.join(os.path.normpath(os.path.dirname( + os.path.realpath(__file__)) + os.sep + ".." + os.sep)) + self.db_configured = None + self.loadSettings() + + def loadSettings(self): + data = session.query(Settings).first() + self.config_calibre_dir = data.config_calibre_dir + self.config_port = data.config_port + self.config_calibre_web_title = data.config_calibre_web_title + self.config_books_per_page = data.config_books_per_page + self.config_random_books = data.config_random_books + self.config_title_regex = data.config_title_regex + self.config_log_level = data.config_log_level + self.config_uploading = data.config_uploading + self.config_anonbrowse = data.config_anonbrowse + self.config_public_reg = data.config_public_reg + if self.config_calibre_dir is not None: # and (self.db_configured is None or self.db_configured is True): + self.db_configured = True + else: + self.db_configured = False + + @property + def get_main_dir(self): + return self.config_main_dir + + #def is_Calibre_Configured(self): + # return self.db_configured + + +# Migrate database to current version, has to be updated after every database change. Currently migration from +# everywhere to curent should work. Migration is done by checking if relevant coloums are existing, and than adding +# rows with SQL commands def migrate_Database(): - if session.query(User).filter(User.role.op('&')(ROLE_ANONYMOUS) == ROLE_ANONYMOUS).first() is None: - create_anonymous_user() try: - session.query(exists().where(User.random_books)).scalar() + session.query(exists().where(User.locale)).scalar() session.commit() except exc.OperationalError: # Database is not compatible, some rows are missing conn = engine.connect() - conn.execute("ALTER TABLE user ADD column random_books INTEGER DEFAULT 1") conn.execute("ALTER TABLE user ADD column locale String(2) DEFAULT 'en'") conn.execute("ALTER TABLE user ADD column default_language String(3) DEFAULT 'all'") session.commit() - try: - session.query(exists().where(User.language_books)).scalar() - session.commit() - except exc.OperationalError: # Database is not compatible, some rows are missing - conn = engine.connect() - conn.execute("ALTER TABLE user ADD column language_books INTEGER DEFAULT 1") - conn.execute("ALTER TABLE user ADD column series_books INTEGER DEFAULT 1") - conn.execute("ALTER TABLE user ADD column category_books INTEGER DEFAULT 1") - conn.execute("ALTER TABLE user ADD column hot_books INTEGER DEFAULT 1") - session.commit() try: session.query(exists().where(Settings.config_calibre_dir)).scalar() session.commit() @@ -270,12 +314,34 @@ def migrate_Database(): conn.execute("ALTER TABLE Settings ADD column `config_calibre_web_title` String DEFAULT 'Calibre-web'") conn.execute("ALTER TABLE Settings ADD column `config_books_per_page` INTEGER DEFAULT 60") conn.execute("ALTER TABLE Settings ADD column `config_random_books` INTEGER DEFAULT 4") - conn.execute("ALTER TABLE Settings ADD column `config_title_regex` String DEFAULT '^(A|The|An|Der|Die|Das|Den|Ein|Eine|Einen|Dem|Des|Einem|Eines)\s+'") - conn.execute("ALTER TABLE Settings ADD column `config_log_level` String DEFAULT 'INFO'") + conn.execute("ALTER TABLE Settings ADD column `config_title_regex` String DEFAULT " + "'^(A|The|An|Der|Die|Das|Den|Ein|Eine|Einen|Dem|Des|Einem|Eines)\s+'") + conn.execute("ALTER TABLE Settings ADD column `config_log_level` SmallInteger DEFAULT '" + logging.INFO + "'") conn.execute("ALTER TABLE Settings ADD column `config_uploading` SmallInteger DEFAULT 0") conn.execute("ALTER TABLE Settings ADD column `config_anonbrowse` SmallInteger DEFAULT 0") conn.execute("ALTER TABLE Settings ADD column `config_public_reg` SmallInteger DEFAULT 0") session.commit() + try: + create = False + session.query(exists().where(User.sidebar_view)).scalar() + session.commit() + except exc.OperationalError: # Database is not compatible, some rows are missing + conn = engine.connect() + conn.execute("ALTER TABLE user ADD column `sidebar_view` Integer DEFAULT 1") + session.commit() + create=True + try: + if create: + conn.execute("SELET language_books FROM user") + session.commit() + except exc.OperationalError: + conn = engine.connect() + conn.execute("UPDATE user SET 'sidebar_view' = (random_books*"+str(SIDEBAR_RANDOM)+"+ language_books *"+ + str(SIDEBAR_LANGUAGE)+"+ series_books *"+str(SIDEBAR_SERIES)+"+ category_books *"+str(SIDEBAR_CATEGORY)+ + "+ hot_books *"+str(SIDEBAR_HOT)+"+"+str(SIDEBAR_AUTHOR)+"+"+str(DETAIL_RANDOM)+")") + session.commit() + if session.query(User).filter(User.role.op('&')(ROLE_ANONYMOUS) == ROLE_ANONYMOUS).first() is None: + create_anonymous_user() def create_default_config(): settings = Settings() @@ -307,10 +373,12 @@ def get_mail_settings(): return data + +# Generate user Guest (translated text), as anoymous user, no rights def create_anonymous_user(): user = User() user.nickname = _("Guest") - user.email='no@email' + user.email = 'no@email' user.role = ROLE_ANONYMOUS user.password = generate_password_hash('1') @@ -322,10 +390,14 @@ def create_anonymous_user(): pass +# Generate User admin with admin123 password, and access to everything def create_admin_user(): user = User() user.nickname = "admin" user.role = ROLE_USER + ROLE_ADMIN + ROLE_DOWNLOAD + ROLE_UPLOAD + ROLE_EDIT + ROLE_PASSWD + user.sidebar_view = DETAIL_RANDOM + SIDEBAR_LANGUAGE + SIDEBAR_SERIES + SIDEBAR_CATEGORY + SIDEBAR_HOT + \ + SIDEBAR_RANDOM + SIDEBAR_AUTHOR + user.password = generate_password_hash(DEFAULT_PASS) session.add(user) @@ -335,10 +407,13 @@ def create_admin_user(): session.rollback() pass + +# Open session for database connection Session = sessionmaker() Session.configure(bind=engine) session = Session() +# generate database and admin and guest user, if no database is existing if not os.path.exists(dbpath): try: Base.metadata.create_all(engine) @@ -349,3 +424,6 @@ if not os.path.exists(dbpath): pass else: migrate_Database() + +# Generate global Settings Object accecable from every file +config = Config() diff --git a/cps/web.py b/cps/web.py index dd88e66b..035e3631 100755 --- a/cps/web.py +++ b/cps/web.py @@ -1,13 +1,14 @@ #!/usr/bin/env python # -*- coding: utf-8 -*- - import mimetypes import logging from logging.handlers import RotatingFileHandler import textwrap from flask import Flask, render_template, session, request, Response, redirect, url_for, send_from_directory, \ make_response, g, flash, abort -import ub, helper +import ub +from ub import config +import helper import os import errno from sqlalchemy.sql.expression import func @@ -18,7 +19,8 @@ from flask_login import LoginManager, login_user, logout_user, login_required, c from flask_principal import Principal, Identity, AnonymousIdentity, identity_changed from flask_babel import Babel from flask_babel import gettext as _ -import requests, zipfile +import requests +import zipfile from werkzeug.security import generate_password_hash, check_password_hash from babel import Locale as LC from babel import negotiate_locale @@ -40,16 +42,17 @@ from tornado.ioloop import IOLoop try: from wand.image import Image + use_generic_pdf_cover = False except ImportError, e: use_generic_pdf_cover = True from cgi import escape -########################################## Global variables ######################################################## +# Global variables global_task = None -########################################## Proxy Helper class ###################################################### +# Proxy Helper class class ReverseProxied(object): """Wrap the application in this middleware and configure the front-end server to add these headers, to let you quietly bind @@ -68,8 +71,8 @@ class ReverseProxied(object): } """ - def __init__(self, app): - self.app = app + def __init__(self, application): + self.app = application def __call__(self, environ, start_response): script_name = environ.get('HTTP_X_SCRIPT_NAME', '') @@ -87,7 +90,8 @@ class ReverseProxied(object): environ['HTTP_HOST'] = server return self.app(environ, start_response) -########################################## Main code ############################################################## + +# Main code mimetypes.init() mimetypes.add_type('application/xhtml+xml', '.xhtml') mimetypes.add_type('application/epub+zip', '.epub') @@ -99,23 +103,19 @@ mimetypes.add_type('application/x-cbz', '.cbz') mimetypes.add_type('application/x-cbt', '.cbt') mimetypes.add_type('image/vnd.djvu', '.djvu') - app = (Flask(__name__)) app.wsgi_app = ReverseProxied(app.wsgi_app) '''formatter = logging.Formatter( "[%(asctime)s] {%(pathname)s:%(lineno)d} %(levelname)s - %(message)s") -file_handler = RotatingFileHandler(os.path.join(config.LOG_DIR, "calibre-web.log"), maxBytes=50000, backupCount=1) +file_handler = RotatingFileHandler(os.path.join(config.get_main_dir, "calibre-web.log"), maxBytes=50000, backupCount=1) file_handler.setFormatter(formatter) app.logger.addHandler(file_handler) -if config.DEVELOPMENT: - app.logger.setLevel(logging.DEBUG) -else: - app.logger.setLevel(logging.INFO) +app.logger.setLevel(config.config_log_level) app.logger.info('Starting Calibre Web...') logging.getLogger("book_formats").addHandler(file_handler) -logging.getLogger("book_formats").setLevel(logging.INFO)''' +logging.getLogger("book_formats").setLevel(config.config_log_level)''' Principal(app) @@ -123,10 +123,6 @@ babel = Babel(app) import uploader -# establish connection to calibre-db -config=ub.Config() -db.setup_db(config) - lm = LoginManager(app) lm.init_app(app) lm.login_view = 'login' @@ -134,7 +130,7 @@ lm.anonymous_user = ub.Anonymous app.secret_key = 'A0Zr98j/3yX R~XHH!jmN]LWX/,?RT' - +db.setup_db() @babel.localeselector def get_locale(): @@ -142,7 +138,7 @@ def get_locale(): user = getattr(g, 'user', None) if user is not None and hasattr(user, "locale"): return user.locale - translations=[item.language for item in babel.list_translations()]+ ['en'] + translations = [item.language for item in babel.list_translations()] + ['en'] preferred = [x.replace('-', '_') for x in request.accept_languages.values()] return negotiate_locale(preferred, translations) @@ -155,14 +151,15 @@ def get_timezone(): @lm.user_loader -def load_user(id): - return ub.session.query(ub.User).filter(ub.User.id == int(id)).first() +def load_user(user_id): + return ub.session.query(ub.User).filter(ub.User.id == int(user_id)).first() @lm.header_loader def load_user_from_header(header_val): if header_val.startswith('Basic '): header_val = header_val.replace('Basic ', '', 1) + basic_username = basic_password = '' try: header_val = base64.b64decode(header_val) basic_username = header_val.split(':')[0] @@ -215,7 +212,7 @@ class Pagination(object): @property def previous_offset(self): - return int((self.page-2) * self.per_page) + return int((self.page - 2) * self.per_page) @property def last_offset(self): @@ -239,11 +236,9 @@ class Pagination(object): def iter_pages(self, left_edge=2, left_current=2, right_current=5, right_edge=2): last = 0 - for num in xrange(1, self.pages + 1): - if num <= left_edge or \ - (num > self.page - left_current - 1 and \ - num < self.page + right_current) or \ - num > self.pages - right_edge: + for num in xrange(1, self.pages + 1): # ToDo: can be simplified + if num <= left_edge or (num > self.page - left_current - 1 and num < self.page + right_current) \ + or num > self.pages - right_edge: if last + 1 != num: yield None yield num @@ -275,12 +270,13 @@ def shortentitle_filter(s): s = textwrap.wrap(s, 60, break_long_words=False)[0] + ' [...]' return s + @app.template_filter('mimetype') def mimetype_filter(val): try: - s = mimetypes.types_map['.'+val] + s = mimetypes.types_map['.' + val] except: - s= 'application/octet-stream' + s = 'application/octet-stream' return s @@ -288,22 +284,27 @@ def admin_required(f): """ Checks if current_user.role == 1 """ + @wraps(f) def inner(*args, **kwargs): if current_user.role_admin(): return f(*args, **kwargs) abort(403) + return inner + def unconfigured(f): """ Checks if current_user.role == 1 """ + @wraps(f) def inner(*args, **kwargs): - if config.is_Calibre_Configured: + if not config.db_configured: return f(*args, **kwargs) abort(403) + return inner @@ -313,6 +314,7 @@ def download_required(f): if current_user.role_download() or current_user.role_admin(): return f(*args, **kwargs) abort(403) + return inner @@ -322,6 +324,7 @@ def upload_required(f): if current_user.role_upload() or current_user.role_admin(): return f(*args, **kwargs) abort(403) + return inner @@ -331,6 +334,7 @@ def edit_required(f): if current_user.role_edit() or current_user.role_admin(): return f(*args, **kwargs) abort(403) + return inner @@ -340,7 +344,7 @@ def fill_indexpage(page, database, db_filter, order): filter = db.Books.languages.any(db.Languages.lang_code == current_user.filter_language()) else: filter = True - if current_user.show_random_books(): + if current_user.show_detail_random(): random = db.session.query(db.Books).filter(filter).order_by(func.random()).limit(config.config_random_books) else: random = false @@ -405,19 +409,23 @@ def modify_database_object(input_elements, db_book_object, db_object, db_session # add element to book db_book_object.append(new_element) + def render_title_template(*args, **kwargs): return render_template(instance=config.config_calibre_web_title, *args, **kwargs) - @app.before_request def before_request(): + if ub.DEVELOPMENT: + reload(ub) g.user = current_user - g.public_shelfes = ub.session.query(ub.Shelf).filter(ub.Shelf.is_public == 1).all() g.allow_registration = config.config_public_reg g.allow_upload = config.config_uploading + if not config.db_configured and request.endpoint not in ('basic_configuration', 'login') and '/static/' not in request.path: + return redirect(url_for('basic_configuration')) -########################################## Routing functions ####################################################### + +# Routing functions @app.route("/opds") @requires_basic_auth_if_no_ano @@ -440,6 +448,7 @@ def feed_osd(): response.headers["Content-Type"] = "application/xml" return response + @app.route("/opds/search/") @requires_basic_auth_if_no_ano def feed_cc_search(query): @@ -462,7 +471,7 @@ def feed_search(term): db.Books.authors.any(db.Authors.name.like("%" + term + "%")), db.Books.title.like("%" + term + "%"))).filter(filter).all() entriescount = len(entries) if len(entries) > 0 else 1 - pagination = Pagination( 1,entriescount,entriescount) + pagination = Pagination(1, entriescount, entriescount) xml = render_template('feed.xml', searchterm=term, entries=entries, pagination=pagination) else: xml = render_template('feed.xml', searchterm="") @@ -483,7 +492,7 @@ def feed_new(): off = 0 entries = db.session.query(db.Books).filter(filter).order_by(db.Books.timestamp.desc()).offset(off).limit( config.config_books_per_page) - pagination = Pagination((int(off)/(int(config.config_books_per_page))+1), config.config_books_per_page, + pagination = Pagination((int(off) / (int(config.config_books_per_page)) + 1), config.config_books_per_page, len(db.session.query(db.Books).filter(filter).all())) xml = render_template('feed.xml', entries=entries, pagination=pagination) response = make_response(xml) @@ -502,7 +511,7 @@ def feed_discover(): # if not off: # off = 0 entries = db.session.query(db.Books).filter(filter).order_by(func.random()).limit(config.config_books_per_page) - pagination = Pagination(1, config.config_books_per_page,int(config.config_books_per_page)) + pagination = Pagination(1, config.config_books_per_page, int(config.config_books_per_page)) xml = render_template('feed.xml', entries=entries, pagination=pagination) response = make_response(xml) response.headers["Content-Type"] = "application/xml" @@ -521,8 +530,9 @@ def feed_hot(): off = 0 entries = db.session.query(db.Books).filter(filter).filter(db.Books.ratings.any(db.Ratings.rating > 9)).offset( off).limit(config.config_books_per_page) - pagination = Pagination((int(off)/(int(config.config_books_per_page))+1), config.config_books_per_page, - len(db.session.query(db.Books).filter(filter).filter(db.Books.ratings.any(db.Ratings.rating > 9)).all())) + pagination = Pagination((int(off) / (int(config.config_books_per_page)) + 1), config.config_books_per_page, + len(db.session.query(db.Books).filter(filter).filter( + db.Books.ratings.any(db.Ratings.rating > 9)).all())) xml = render_template('feed.xml', entries=entries, pagination=pagination) response = make_response(xml) response.headers["Content-Type"] = "application/xml" @@ -541,7 +551,7 @@ def feed_authorindex(): if not off: off = 0 authors = db.session.query(db.Authors).order_by(db.Authors.sort).offset(off).limit(config.config_books_per_page) - pagination = Pagination((int(off)/(int(config.config_books_per_page))+1), config.config_books_per_page, + pagination = Pagination((int(off) / (int(config.config_books_per_page)) + 1), config.config_books_per_page, len(db.session.query(db.Authors).all())) xml = render_template('feed.xml', authors=authors, pagination=pagination) response = make_response(xml) @@ -559,10 +569,11 @@ def feed_author(id): filter = True if not off: off = 0 - entries = db.session.query(db.Books).filter(db.Books.authors.any(db.Authors.id == id )).filter( + entries = db.session.query(db.Books).filter(db.Books.authors.any(db.Authors.id == id)).filter( filter).offset(off).limit(config.config_books_per_page) - pagination = Pagination((int(off)/(int(config.config_books_per_page))+1), config.config_books_per_page, - len(db.session.query(db.Books).filter(db.Books.authors.any(db.Authors.id == id )).filter(filter).all())) + pagination = Pagination((int(off) / (int(config.config_books_per_page)) + 1), config.config_books_per_page, + len(db.session.query(db.Books).filter(db.Books.authors.any(db.Authors.id == id)).filter( + filter).all())) xml = render_template('feed.xml', entries=entries, pagination=pagination) response = make_response(xml) response.headers["Content-Type"] = "application/xml" @@ -576,7 +587,7 @@ def feed_categoryindex(): if not off: off = 0 entries = db.session.query(db.Tags).order_by(db.Tags.name).offset(off).limit(config.config_books_per_page) - pagination = Pagination((int(off)/(int(config.config_books_per_page))+1), config.config_books_per_page, + pagination = Pagination((int(off) / (int(config.config_books_per_page)) + 1), config.config_books_per_page, len(db.session.query(db.Tags).all())) xml = render_template('feed.xml', categorys=entries, pagination=pagination) response = make_response(xml) @@ -594,10 +605,11 @@ def feed_category(id): filter = True if not off: off = 0 - entries = db.session.query(db.Books).filter(db.Books.tags.any(db.Tags.id==id)).order_by( + entries = db.session.query(db.Books).filter(db.Books.tags.any(db.Tags.id == id)).order_by( db.Books.timestamp.desc()).filter(filter).offset(off).limit(config.config_books_per_page) - pagination = Pagination((int(off)/(int(config.config_books_per_page))+1), config.config_books_per_page, - len(db.session.query(db.Books).filter(db.Books.tags.any(db.Tags.id==id)).filter(filter).all())) + pagination = Pagination((int(off) / (int(config.config_books_per_page)) + 1), config.config_books_per_page, + len(db.session.query(db.Books).filter(db.Books.tags.any(db.Tags.id == id)).filter( + filter).all())) xml = render_template('feed.xml', entries=entries, pagination=pagination) response = make_response(xml) response.headers["Content-Type"] = "application/xml" @@ -615,7 +627,7 @@ def feed_seriesindex(): if not off: off = 0 entries = db.session.query(db.Series).order_by(db.Series.name).offset(off).limit(config.config_books_per_page) - pagination = Pagination((int(off)/(int(config.config_books_per_page))+1), config.config_books_per_page, + pagination = Pagination((int(off) / (int(config.config_books_per_page)) + 1), config.config_books_per_page, len(db.session.query(db.Series).all())) xml = render_template('feed.xml', series=entries, pagination=pagination) response = make_response(xml) @@ -635,8 +647,9 @@ def feed_series(id): off = 0 entries = db.session.query(db.Books).filter(db.Books.series.any(db.Series.id == id)).order_by( db.Books.timestamp.desc()).filter(filter).offset(off).limit(config.config_books_per_page) - pagination = Pagination((int(off)/(int(config.config_books_per_page))+1), config.config_books_per_page, - len(db.session.query(db.Books).filter(db.Books.series.any(db.Series.id == id)).filter(filter).all())) + pagination = Pagination((int(off) / (int(config.config_books_per_page)) + 1), config.config_books_per_page, + len(db.session.query(db.Books).filter(db.Books.series.any(db.Series.id == id)).filter( + filter).all())) xml = render_template('feed.xml', entries=entries, pagination=pagination) response = make_response(xml) response.headers["Content-Type"] = "application/xml" @@ -661,18 +674,20 @@ def get_opds_download_link(book_id, format): response.headers["Content-Disposition"] = "attachment; filename=\"%s.%s\"" % (data.name, format) return response + @app.route("/ajax/book/") @requires_basic_auth_if_no_ano def get_metadata_calibre_companion(uuid): - entry = db.session.query(db.Books).filter(db.Books.uuid.like("%"+uuid+"%")).first() - if entry is not None : - js = render_template('json.txt',entry=entry) + entry = db.session.query(db.Books).filter(db.Books.uuid.like("%" + uuid + "%")).first() + if entry is not None: + js = render_template('json.txt', entry=entry) response = make_response(js) response.headers["Content-Type"] = "application/json; charset=utf-8" return response else: return "" + @app.route("/get_authors_json", methods=['GET', 'POST']) @login_required_if_no_ano def get_authors_json(): @@ -752,11 +767,11 @@ def get_matching_tags(): @app.route('/page/') @login_required_if_no_ano def index(page): - if config.is_Calibre_Configured == False: - return redirect(url_for('basic_configuration')) + #if not config.db_configured: + # return redirect(url_for('basic_configuration')) entries, random, pagination = fill_indexpage(page, db.Books, True, db.Books.timestamp.desc()) return render_title_template('index.html', random=random, entries=entries, pagination=pagination, - title=_(u"Latest Books")) + title=_(u"Latest Books")) @app.route("/hot", defaults={'page': 1}) @@ -781,15 +796,16 @@ def hot_books(page): numBooks = entries.__len__() pagination = Pagination(page, config.config_books_per_page, numBooks) return render_title_template('index.html', random=random, entries=entries, pagination=pagination, - title=_(u"Hot Books (most downloaded)")) + title=_(u"Hot Books (most downloaded)")) @app.route("/discover", defaults={'page': 1}) @app.route('/discover/page/') @login_required_if_no_ano def discover(page): - entries, random, pagination = fill_indexpage(page, db.Books, func.randomblob(2), db.Books.timestamp.desc()) - return render_title_template('discover.html', entries=entries, pagination=pagination, instance=config.config_calibre_web_title, title=_(u"Random Books")) + entries, random, pagination = fill_indexpage(page, db.Books, True, func.randomblob(2)) + pagination = Pagination(1, config.config_books_per_page,config.config_books_per_page) + return render_title_template('discover.html', entries=entries, pagination=pagination, title=_(u"Random Books")) @app.route("/author") @@ -819,7 +835,7 @@ def author(name): entries = db.session.query(db.Books).filter(db.Books.authors.any(db.Authors.name.like("%" + name + "%"))).filter( filter).all() - return render_title_template('index.html', random=random, entries=entries,title=_(u"Author: %(nam)s", nam=name)) + return render_title_template('index.html', random=random, entries=entries, title=_(u"Author: %(nam)s", nam=name)) @app.route("/series") @@ -843,7 +859,7 @@ def series(name, page): db.Books.series_index) if entries: return render_title_template('index.html', random=random, pagination=pagination, entries=entries, - title=_(u"Series: %(serie)s", serie=name)) + title=_(u"Series: %(serie)s", serie=name)) else: flash(_(u"Error opening eBook. File does not exist or file is not accessible:"), category="error") return redirect(url_for("index")) @@ -876,7 +892,7 @@ def language_overview(): func.count('books_languages_link.book').label('bookcount')).group_by( 'books_languages_link.lang_code').all() return render_title_template('languages.html', languages=languages, lang_counter=lang_counter, - title=_(u"Available languages")) + title=_(u"Available languages")) @app.route("/language/", defaults={'page': 1}) @@ -891,7 +907,7 @@ def language(name, page): except: name = _(isoLanguages.get(part3=name).name) return render_title_template('index.html', random=random, entries=entries, pagination=pagination, - title=_(u"Language: %(name)s", name=name)) + title=_(u"Language: %(name)s", name=name)) @app.route("/category") @@ -914,7 +930,7 @@ def category(name, page): entries, random, pagination = fill_indexpage(page, db.Books, db.Books.tags.any(db.Tags.name == name), db.Books.timestamp.desc()) return render_title_template('index.html', random=random, entries=entries, pagination=pagination, - title=_(u"Category: %(name)s", name=name)) + title=_(u"Category: %(name)s", name=name)) @app.route("/book/") @@ -949,7 +965,6 @@ def show_book(id): @app.route("/admin") @login_required def admin_forbidden(): - return "Admin ONLY!" abort(403) @@ -958,22 +973,23 @@ def admin_forbidden(): def stats(): counter = len(db.session.query(db.Books).all()) authors = len(db.session.query(db.Authors).all()) - Versions=uploader.book_formats.get_versions() + versions = uploader.book_formats.get_versions() vendorpath = os.path.join(config.get_main_dir + "vendor" + os.sep) if sys.platform == "win32": kindlegen = os.path.join(vendorpath, u"kindlegen.exe") else: kindlegen = os.path.join(vendorpath, u"kindlegen") - kindlegen_version=_('not installed') + versions['KindlegenVersion'] = _('not installed') if os.path.exists(kindlegen): - p = subprocess.Popen(kindlegen, shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE, stdin=subprocess.PIPE) - check = p.wait() + p = subprocess.Popen(kindlegen, shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE, + stdin=subprocess.PIPE) + p.wait() for lines in p.stdout.readlines(): if re.search('Amazon kindlegen\(', lines): - Versions['KindlegenVersion'] = lines - Versions['PythonVersion']=sys.version - return render_title_template('stats.html', bookcounter=counter, authorcounter=authors, Versions=Versions, - title=_(u"Statistics")) + versions['KindlegenVersion'] = lines + versions['PythonVersion'] = sys.version + return render_title_template('stats.html', bookcounter=counter, authorcounter=authors, versions=versions, + title=_(u"Statistics")) @app.route("/shutdown") @@ -983,23 +999,25 @@ def shutdown(): global global_task task = int(request.args.get("parameter").strip()) global_task = task - if task == 1 or task == 0: # valid commandos received + if task == 1 or task == 0: # valid commandos received # close all database connections db.session.close() db.engine.dispose() ub.session.close() ub.engine.dispose() # stop tornado server - server=IOLoop.instance() + server = IOLoop.instance() server.add_callback(server.stop) + showtext = {} if task == 0: - text['text']=_(u'Performing Restart, please reload page') + showtext['text'] = _(u'Performing Restart, please reload page') else: - text['text']= _(u'Performing shutdown of server, please close window') - return json.dumps(text) + showtext['text'] = _(u'Performing shutdown of server, please close window') + return json.dumps(showtext) else: abort(404) + @app.route("/search", methods=["GET"]) @login_required_if_no_ano def search(): @@ -1082,7 +1100,7 @@ def advanced_search(): except: lang.name = _(isoLanguages.get(part3=lang.lang_code).name) else: - languages=None + languages = None return render_title_template('search_form.html', tags=tags, languages=languages, series=series, title=_(u"search")) @@ -1091,6 +1109,7 @@ def advanced_search(): def get_cover(cover_path): return send_from_directory(os.path.join(config.config_calibre_dir, cover_path), "cover.jpg") + @app.route("/opds/thumb_240_240/") @app.route("/opds/cover_240_240/") @app.route("/opds/cover_90_90/") @@ -1169,16 +1188,18 @@ def get_download_link(book_id, format): book = db.session.query(db.Books).filter(db.Books.id == book_id).first() data = db.session.query(db.Data).filter(db.Data.book == book.id).filter(db.Data.format == format.upper()).first() if data: - if current_user.is_authenticated: # collect downloaded books only for registered user and not for anonymous user + # collect downloaded books only for registered user and not for anonymous user + if current_user.is_authenticated: helper.update_download(book_id, int(current_user.id)) author = helper.get_normalized_author(book.author_sort) file_name = book.title if len(author) > 0: file_name = author + '-' + file_name file_name = helper.get_valid_filename(file_name) - response = make_response(send_from_directory(os.path.join(config.config_calibre_dir, book.path), data.name + "." + format)) + response = make_response( + send_from_directory(os.path.join(config.config_calibre_dir, book.path), data.name + "." + format)) try: - response.headers["Content-Type"]=mimetypes.types_map['.'+format] + response.headers["Content-Type"] = mimetypes.types_map['.' + format] except: pass response.headers["Content-Disposition"] = \ @@ -1192,9 +1213,9 @@ def get_download_link(book_id, format): else: abort(404) + @app.route('/register', methods=['GET', 'POST']) def register(): - error = None if not config.config_public_reg: abort(404) if current_user is not None and current_user.is_authenticated: @@ -1232,8 +1253,7 @@ def register(): @app.route('/login', methods=['GET', 'POST']) def login(): - error = None - if config.is_Calibre_Configured == False: + if not config.db_configured: return redirect(url_for('basic_configuration')) if current_user is not None and current_user.is_authenticated: return redirect(url_for('index')) @@ -1268,7 +1288,7 @@ def send_to_kindle(book_id): if settings.get("mail_server", "mail.example.com") == "mail.example.com": flash(_(u"Please configure the SMTP mail settings first..."), category="error") elif current_user.kindle_mail: - result = helper.send_mail(book_id, current_user.kindle_mail,config.config_calibre_dir) + result = helper.send_mail(book_id, current_user.kindle_mail, config.config_calibre_dir) if result is None: flash(_(u"Book successfully send to %(kindlemail)s", kindlemail=current_user.kindle_mail), category="success") @@ -1287,12 +1307,12 @@ def add_to_shelf(shelf_id, book_id): if not shelf.is_public and not shelf.user_id == int(current_user.id): flash("Sorry you are not allowed to add a book to the the shelf: %s" % shelf.name) return redirect(url_for('index')) - maxO = ub.session.query(func.max(ub.BookShelf.order)).filter(ub.BookShelf.shelf == shelf_id).first() - if maxO[0] is None: + maxOrder = ub.session.query(func.max(ub.BookShelf.order)).filter(ub.BookShelf.shelf == shelf_id).first() + if maxOrder[0] is None: maxOrder = 0 else: - maxOrder = maxO[0] - ins = ub.BookShelf(shelf=shelf.id, book_id=book_id, order=maxOrder+1) + maxOrder = maxOrder[0] + ins = ub.BookShelf(shelf=shelf.id, book_id=book_id, order=maxOrder + 1) ub.session.add(ins) ub.session.commit() @@ -1332,10 +1352,11 @@ def create_shelf(): shelf.is_public = 1 shelf.name = to_save["title"] shelf.user_id = int(current_user.id) - existing_shelf = ub.session.query(ub.Shelf).filter(or_((ub.Shelf.name == to_save["title"])&( ub.Shelf.is_public == 1), - (ub.Shelf.name == to_save["title"])& (ub.Shelf.user_id == int(current_user.id)))).first() + existing_shelf = ub.session.query(ub.Shelf).filter( + or_((ub.Shelf.name == to_save["title"]) & (ub.Shelf.is_public == 1), + (ub.Shelf.name == to_save["title"]) & (ub.Shelf.user_id == int(current_user.id)))).first() if existing_shelf: - flash(_(u"A shelf with the name '%(title)s' already exists.",title=to_save["title"]), category="error") + flash(_(u"A shelf with the name '%(title)s' already exists.", title=to_save["title"]), category="error") else: try: ub.session.add(shelf) @@ -1347,16 +1368,19 @@ def create_shelf(): else: return render_title_template('shelf_edit.html', shelf=shelf, title=_(u"create a shelf")) + @app.route("/shelf/edit/", methods=["GET", "POST"]) @login_required def edit_shelf(shelf_id): shelf = ub.session.query(ub.Shelf).filter(ub.Shelf.id == shelf_id).first() if request.method == "POST": to_save = request.form.to_dict() - existing_shelf = ub.session.query(ub.Shelf).filter(or_((ub.Shelf.name == to_save["title"])&( ub.Shelf.is_public == 1), - (ub.Shelf.name == to_save["title"])& (ub.Shelf.user_id == int(current_user.id)))).filter(ub.Shelf.id!=shelf_id).first() + existing_shelf = ub.session.query(ub.Shelf).filter( + or_((ub.Shelf.name == to_save["title"]) & (ub.Shelf.is_public == 1), + (ub.Shelf.name == to_save["title"]) & (ub.Shelf.user_id == int(current_user.id)))).filter( + ub.Shelf.id != shelf_id).first() if existing_shelf: - flash(_(u"A shelf with the name '%(title)s' already exists.",title=to_save["title"]), category="error") + flash(_(u"A shelf with the name '%(title)s' already exists.", title=to_save["title"]), category="error") else: shelf.name = to_save["title"] if "is_public" in to_save: @@ -1365,7 +1389,7 @@ def edit_shelf(shelf_id): shelf.is_public = 0 try: ub.session.commit() - flash(_(u"Shelf %(title)s changed",title=to_save["title"]), category="success") + flash(_(u"Shelf %(title)s changed", title=to_save["title"]), category="success") except: flash(_(u"There was an error"), category="error") return render_title_template('shelf_edit.html', shelf=shelf, title=_(u"Edit a shelf")) @@ -1373,15 +1397,12 @@ def edit_shelf(shelf_id): return render_title_template('shelf_edit.html', shelf=shelf, title=_(u"Edit a shelf")) - @app.route("/shelf/delete/") @login_required def delete_shelf(shelf_id): cur_shelf = ub.session.query(ub.Shelf).filter(ub.Shelf.id == shelf_id).first() - deleted = 0 if current_user.role == ub.ROLE_ADMIN: deleted = ub.session.query(ub.Shelf).filter(ub.Shelf.id == shelf_id).delete() - else: deleted = ub.session.query(ub.Shelf).filter(ub.or_(ub.and_(ub.Shelf.user_id == int(current_user.id), ub.Shelf.id == shelf_id), @@ -1407,12 +1428,14 @@ def show_shelf(shelf_id): ub.Shelf.id == shelf_id))).first() result = list() if shelf: - books_in_shelf = ub.session.query(ub.BookShelf).filter(ub.BookShelf.shelf == shelf_id).order_by(ub.BookShelf.order.asc()).all() + books_in_shelf = ub.session.query(ub.BookShelf).filter(ub.BookShelf.shelf == shelf_id).order_by( + ub.BookShelf.order.asc()).all() for book in books_in_shelf: cur_book = db.session.query(db.Books).filter(db.Books.id == book.book_id).first() result.append(cur_book) - return render_title_template('shelf.html', entries=result, title=_(u"Shelf: '%(name)s'", name=shelf.name), shelf=shelf) + return render_title_template('shelf.html', entries=result, title=_(u"Shelf: '%(name)s'", name=shelf.name), + shelf=shelf) @app.route("/shelf/order/", methods=["GET", "POST"]) @@ -1422,10 +1445,10 @@ def order_shelf(shelf_id): to_save = request.form.to_dict() books_in_shelf = ub.session.query(ub.BookShelf).filter(ub.BookShelf.shelf == shelf_id).order_by( ub.BookShelf.order.asc()).all() - counter=0 + counter = 0 for book in books_in_shelf: setattr(book, 'order', to_save[str(book.book_id)]) - counter+=1 + counter += 1 ub.session.commit() if current_user.is_anonymous(): shelf = ub.session.query(ub.Shelf).filter(ub.Shelf.is_public == 1, ub.Shelf.id == shelf_id).first() @@ -1436,7 +1459,7 @@ def order_shelf(shelf_id): ub.Shelf.id == shelf_id))).first() result = list() 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) \ .order_by(ub.BookShelf.order.asc()).all() for book in books_in_shelf2: cur_book = db.session.query(db.Books).filter(db.Books.id == book.book_id).first() @@ -1459,7 +1482,7 @@ def profile(): lang.name = _(isoLanguages.get(part3=lang.lang_code).name) translations = babel.list_translations() + [LC('en')] for book in content.downloads: - downloadBook=db.session.query(db.Books).filter(db.Books.id == book.book_id).first() + downloadBook = db.session.query(db.Books).filter(db.Books.id == book.book_id).first() if downloadBook: downloads.append(db.session.query(db.Books).filter(db.Books.id == book.book_id).first()) else: @@ -1481,21 +1504,21 @@ def profile(): content.default_language = to_save["default_language"] if to_save["locale"]: content.locale = to_save["locale"] - content.random_books = 0 - content.language_books = 0 - content.series_books = 0 - content.category_books = 0 - content.hot_books = 0 - if "show_random" in to_save and to_save["show_random"] == "on": - content.random_books = 1 - if "show_language" in to_save and to_save["show_language"] == "on": - content.language_books = 1 - if "show_series" in to_save and to_save["show_series"] == "on": - content.series_books = 1 - if "show_category" in to_save and to_save["show_category"] == "on": - content.category_books = 1 - if "show_hot" in to_save and to_save["show_hot"] == "on": - content.hot_books = 1 + content.sidebar_view = 0 + if "show_random" in to_save: + content.sidebar_view += ub.SIDEBAR_RANDOM + if "show_language" in to_save: + content.sidebar_view += ub.SIDEBAR_LANGUAGE + if "show_series" in to_save: + content.sidebar_view += ub.SIDEBAR_SERIES + if "show_category" in to_save: + content.sidebar_view += ub.SIDEBAR_CATEGORY + if "show_hot" in to_save: + content.sidebar_view += ub.SIDEBAR_HOT + if "show_author" in to_save: + content.sidebar_view += ub.SIDEBAR_AUTHOR + if "show_detail_random" in to_save: + content.sidebar_view += ub.DETAIL_RANDOM if "default_language" in to_save: content.default_language = to_save["default_language"] try: @@ -1504,10 +1527,11 @@ def profile(): ub.session.rollback() flash(_(u"Found an existing account for this email address."), category="error") return render_title_template("user_edit.html", content=content, downloads=downloads, - title=_(u"%(name)s's profile", name=current_user.nickname)) + title=_(u"%(name)s's profile", name=current_user.nickname)) flash(_(u"Profile updated"), category="success") - return render_title_template("user_edit.html", translations=translations, profile=1, languages=languages, content=content, - downloads=downloads,title=_(u"%(name)s's profile", name=current_user.nickname)) + return render_title_template("user_edit.html", translations=translations, profile=1, languages=languages, + content=content, + downloads=downloads, title=_(u"%(name)s's profile", name=current_user.nickname)) @app.route("/admin/view") @@ -1516,29 +1540,36 @@ def profile(): def admin(): content = ub.session.query(ub.User).all() settings = ub.session.query(ub.Settings).first() - return render_title_template("admin.html", content=content, email=settings, config=config, title=_(u"Admin page")) + return render_title_template("admin.html", content=content, email=settings, config=config, + development=ub.DEVELOPMENT, title=_(u"Admin page")) + @app.route("/admin/config", methods=["GET", "POST"]) @login_required @admin_required def configuration(): - return configuration_helper() + return configuration_helper(0) -@app.route("/config", methods=["GET", "POST"] ) + +@app.route("/config", methods=["GET", "POST"]) @unconfigured def basic_configuration(): - return configuration_helper() + return configuration_helper(1) -def configuration_helper(): + +def configuration_helper(origin): global global_task - reboot_required= False + reboot_required = False + db_change = False + success = False if request.method == "POST": to_save = request.form.to_dict() - content = ub.session.query(ub.Settings).first() # ToDo replace content with config ? + content = ub.session.query(ub.Settings).first() + # ToDo: check lib vaild, and change without restart if "config_calibre_dir" in to_save: if content.config_calibre_dir != to_save["config_calibre_dir"]: content.config_calibre_dir = to_save["config_calibre_dir"] - reboot_required = True + db_change = True if "config_port" in to_save: if content.config_port != int(to_save["config_port"]): content.config_port = int(to_save["config_port"]) @@ -1550,8 +1581,7 @@ def configuration_helper(): content.config_title_regex = to_save["config_title_regex"] reboot_required = True if "config_log_level" in to_save: - content.config_log_level = to_save["config_log_level"] - # ToDo check reboot required + content.config_log_level = int(to_save["config_log_level"]) if "config_random_books" in to_save: content.config_random_books = int(to_save["config_random_books"]) if "config_books_per_page" in to_save: @@ -1560,22 +1590,32 @@ def configuration_helper(): content.config_anonbrowse = 0 content.config_public_reg = 0 if "config_uploading" in to_save and to_save["config_uploading"] == "on": - content.config_uploading = 1 + content.config_uploading = 1 if "config_anonbrowse" in to_save and to_save["config_anonbrowse"] == "on": - content.config_anonbrowse = 1 + content.config_anonbrowse = 1 if "config_public_reg" in to_save and to_save["config_public_reg"] == "on": - content.config_public_reg = 1 + content.config_public_reg = 1 try: + if db_change: + if config.db_configured: + db.session.close() + db.engine.dispose() ub.session.commit() flash(_(u"Calibre-web configuration updated"), category="success") config.loadSettings() + app.logger.setLevel(config.config_log_level) + logging.getLogger("book_formats").setLevel(config.config_log_level) except e: flash(e, category="error") - return render_title_template("config_edit.html", content=config, title=_(u"Basic Configuration")) - + return render_title_template("config_edit.html", content=config, origin=origin, + title=_(u"Basic Configuration")) + if db_change: + reload(db) + if not db.setup_db(): + flash(_(u'DB location is not valid, please enter correct path'), category="error") + return render_title_template("config_edit.html", content=config, origin=origin, + title=_(u"Basic Configuration")) if reboot_required: - if config.is_Calibre_Configured: - db.session.close() # db.engine.dispose() # ToDo verify correct ub.session.close() ub.engine.dispose() @@ -1583,7 +1623,11 @@ def configuration_helper(): server = IOLoop.instance() server.add_callback(server.stop) global_task = 0 - return render_title_template("config_edit.html", content=config, title=_(u"Basic Configuration")) + app.logger.info('Reboot required, restarting') + if origin: + success = True + return render_title_template("config_edit.html", origin=origin, success=success, content=config, + title=_(u"Basic Configuration")) @app.route("/admin/user/new", methods=["GET", "POST"]) @@ -1603,26 +1647,29 @@ def new_user(): to_save = request.form.to_dict() if not to_save["nickname"] or not to_save["email"] or not to_save["password"]: flash(_(u"Please fill out all fields!"), category="error") - return render_title_template("user_edit.html", new_user=1, content=content, title=_(u"Add new user")) + return render_title_template("user_edit.html", new_user=1, content=content, translations=translations, + title=_(u"Add new user")) content.password = generate_password_hash(to_save["password"]) content.nickname = to_save["nickname"] content.email = to_save["email"] content.default_language = to_save["default_language"] if "locale" in to_save: content.locale = to_save["locale"] - content.random_books = 0 - content.language_books = 0 - content.series_books = 0 - content.category_books = 0 - content.hot_books = 0 + content.sidebar_view = 0 + if "show_random" in to_save: + content.sidebar_view += ub.SIDEBAR_RANDOM if "show_language" in to_save: - content.language_books = to_save["show_language"] + content.sidebar_view += ub.SIDEBAR_LANGUAGE if "show_series" in to_save: - content.series_books = to_save["show_series"] + content.sidebar_view += ub.SIDEBAR_SERIES if "show_category" in to_save: - content.category_books = to_save["show_category"] + content.sidebar_view += ub.SIDEBAR_CATEGORY if "show_hot" in to_save: - content.hot_books = to_save["show_hot"] + content.sidebar_view += ub.SIDEBAR_HOT + if "show_author" in to_save: + content.sidebar_view += ub.SIDEBAR_AUTHOR + if "show_detail_random" in to_save: + content.sidebar_view += ub.DETAIL_RANDOM content.role = 0 if "admin_role" in to_save: content.role = content.role + ub.ROLE_ADMIN @@ -1643,7 +1690,7 @@ def new_user(): ub.session.rollback() flash(_(u"Found an existing account for this email address or nickname."), category="error") return render_title_template("user_edit.html", new_user=1, content=content, translations=translations, - languages=languages, title=_(u"Add new user")) + languages=languages, title=_(u"Add new user")) @app.route("/admin/mailsettings", methods=["GET", "POST"]) @@ -1665,7 +1712,7 @@ def edit_mailsettings(): except e: flash(e, category="error") if "test" in to_save and to_save["test"]: - result=helper.send_test_mail(current_user.kindle_mail) + result = helper.send_test_mail(current_user.kindle_mail) if result is None: flash(_(u"Test E-Mail successfully send to %(kindlemail)s", kindlemail=current_user.kindle_mail), category="success") @@ -1689,7 +1736,7 @@ def edit_user(user_id): lang.name = _(isoLanguages.get(part3=lang.lang_code).name) translations = babel.list_translations() + [LC('en')] for book in content.downloads: - downloadBook=db.session.query(db.Books).filter(db.Books.id == book.book_id).first() + downloadBook = db.session.query(db.Books).filter(db.Books.id == book.book_id).first() if downloadBook: downloads.append(db.session.query(db.Books).filter(db.Books.id == book.book_id).first()) else: @@ -1729,21 +1776,42 @@ def edit_user(user_id): content.role = content.role + ub.ROLE_PASSWD elif "passwd_role" not in to_save and content.role_passwd(): content.role = content.role - ub.ROLE_PASSWD - content.random_books = 0 - content.language_books = 0 - content.series_books = 0 - content.category_books = 0 - content.hot_books = 0 - if "show_random" in to_save and to_save["show_random"] == "on": - content.random_books = 1 - if "show_language" in to_save and to_save["show_language"] == "on": - content.language_books = 1 - if "show_series" in to_save and to_save["show_series"] == "on": - content.series_books = 1 - if "show_category" in to_save and to_save["show_category"] == "on": - content.category_books = 1 - if "show_hot" in to_save and to_save["show_hot"] == "on": - content.hot_books = 1 + + if "show_random" in to_save and not content.show_random_books(): + content.sidebar_view += ub.SIDEBAR_RANDOM + elif "show_random" not in to_save and content.show_random_books(): + content.sidebar_view -= ub.SIDEBAR_RANDOM + + if "show_language" in to_save and not content.show_language(): + content.sidebar_view += ub.SIDEBAR_LANGUAGE + elif "show_language" not in to_save and content.show_language(): + content.sidebar_view -= ub.SIDEBAR_LANGUAGE + + if "show_series" in to_save and not content.show_series(): + content.sidebar_view += ub.SIDEBAR_SERIES + elif "show_series" not in to_save and content.show_series(): + content.sidebar_view -= ub.SIDEBAR_SERIES + + if "show_category" in to_save and not content.show_category(): + content.sidebar_view += ub.SIDEBAR_CATEGORY + elif "show_category" not in to_save and content.show_category(): + content.sidebar_view -= ub.SIDEBAR_CATEGORY + + if "show_hot" in to_save and not content.show_hot_books(): + content.sidebar_view += ub.SIDEBAR_HOT + elif "show_hot" not in to_save and content.show_hot_books(): + content.sidebar_view -= ub.SIDEBAR_HOT + + if "show_author" in to_save and not content.show_author(): + content.sidebar_view += ub.SIDEBAR_AUTHOR + elif "show_author" not in to_save and content.show_author(): + content.sidebar_view -= ub.SIDEBAR_AUTHOR + + if "show_detail_random" in to_save and not content.show_detail_random(): + content.sidebar_view += ub.DETAIL_RANDOM + elif "show_detail_random" not in to_save and content.show_detail_random(): + content.sidebar_view -= ub.DETAIL_RANDOM + if "default_language" in to_save: content.default_language = to_save["default_language"] if "locale" in to_save and to_save["locale"]: @@ -1759,7 +1827,8 @@ def edit_user(user_id): ub.session.rollback() flash(_(u"An unknown error occured."), category="error") return render_title_template("user_edit.html", translations=translations, languages=languages, new_user=0, - content=content, downloads=downloads, title=_(u"Edit User %(nick)s", nick=content.nickname)) + content=content, downloads=downloads, + title=_(u"Edit User %(nick)s", nick=content.nickname)) @app.route("/admin/book/", methods=['GET', 'POST']) @@ -1959,13 +2028,15 @@ def edit_book(book_id): for author in book.authors: author_names.append(author.name) for b in edited_books_id: - helper.update_dir_stucture(b,config.config_calibre_dir) + helper.update_dir_stucture(b, config.config_calibre_dir) if "detail_view" in to_save: return redirect(url_for('show_book', id=book.id)) else: - return render_title_template('book_edit.html', book=book, authors=author_names, cc=cc, title=_(u"edit metadata")) + return render_title_template('book_edit.html', book=book, authors=author_names, cc=cc, + title=_(u"edit metadata")) else: - return render_title_template('book_edit.html', book=book, authors=author_names, cc=cc, title=_(u"edit metadata")) + return render_title_template('book_edit.html', book=book, authors=author_names, cc=cc, + title=_(u"edit metadata")) else: flash(_(u"Error opening eBook. File does not exist or file is not accessible:"), category="error") return redirect(url_for("index")) @@ -2038,6 +2109,8 @@ def upload(): author_names.append(author.name) cc = db.session.query(db.Custom_Columns).filter(db.Custom_Columns.datatype.notin_(db.cc_exceptions)).all() if current_user.role_edit() or current_user.role_admin(): - return render_title_template('book_edit.html', book=db_book, authors=author_names, cc=cc, title=_(u"edit metadata")) + return render_title_template('book_edit.html', book=db_book, authors=author_names, cc=cc, + title=_(u"edit metadata")) book_in_shelfs = [] - return render_title_template('detail.html', entry=db_book, cc=cc,title=db_book.title, books_shelfs=book_in_shelfs, ) + return render_title_template('detail.html', entry=db_book, cc=cc, title=db_book.title, + books_shelfs=book_in_shelfs, )