Started migration of config to app database

This commit is contained in:
OzzieIsaacs 2017-01-22 16:44:37 +01:00
parent a2a48515d4
commit 4eee58c21c
14 changed files with 428 additions and 349 deletions

58
cps.py
View File

@ -1,9 +1,5 @@
#!/usr/bin/env python
import os import os
import sys import sys
from threading import Thread
from multiprocessing import Queue
import time import time
base_path = os.path.dirname(os.path.abspath(__file__)) base_path = os.path.dirname(os.path.abspath(__file__))
@ -12,50 +8,22 @@ base_path = os.path.dirname(os.path.abspath(__file__))
sys.path.insert(0, os.path.join(base_path, 'vendor')) sys.path.insert(0, os.path.join(base_path, 'vendor'))
from cps import web from cps import web
from cps import config # from cps import config
from tornado.wsgi import WSGIContainer from tornado.wsgi import WSGIContainer
from tornado.httpserver import HTTPServer from tornado.httpserver import HTTPServer
from tornado.ioloop import IOLoop from tornado.ioloop import IOLoop
global title_sort
def start_calibreweb(messagequeue):
web.global_queue = messagequeue
if config.DEVELOPMENT:
web.app.run(host="0.0.0.0", port=config.PORT, debug=True)
else:
http_server = HTTPServer(WSGIContainer(web.app))
http_server.listen(config.PORT)
IOLoop.instance().start()
print "Tornado finished"
http_server.stop()
def stop_calibreweb():
# Close Database connections for user and data
web.db.session.close()
web.db.engine.dispose()
web.ub.session.close()
web.ub.engine.dispose()
test=IOLoop.instance()
test.add_callback(test.stop)
print("Asked Tornado to exit")
if __name__ == '__main__': if __name__ == '__main__':
if config.DEVELOPMENT: '''if config.DEVELOPMENT:
web.app.run(host="0.0.0.0",port=config.PORT, debug=True) web.app.run(host="0.0.0.0", port=web.config.config_port, debug=True)
else: else:'''
while True: http_server = HTTPServer(WSGIContainer(web.app))
q = Queue() http_server.listen(web.config.config_port)
t = Thread(target=start_calibreweb, args=(q,)) IOLoop.instance().start()
t.start()
while True: #watching queue, if there is no call than sleep, otherwise break
if q.empty():
time.sleep(1)
else:
break
stop_calibreweb()
t.join()
if web.global_task == 0:
print("Performing restart of Calibre-web")
os.execl(sys.executable,sys.executable, *sys.argv)
else:
print("Performing shutdown of Calibre-web")
os._exit(0)

View File

@ -1,97 +0,0 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
import os
import sys
from configobj import ConfigObj
CONFIG_FILE = os.path.join(os.path.normpath(os.path.dirname(os.path.realpath(__file__))+os.sep+".."+os.sep), "config.ini")
CFG = ConfigObj(CONFIG_FILE)
CFG.encoding = 'UTF-8'
def CheckSection(sec):
""" Check if INI section exists, if not create it """
try:
CFG[sec]
return True
except:
CFG[sec] = {}
return False
def check_setting_str(config, cfg_name, item_name, def_val, log=True):
try:
my_val = config[cfg_name][item_name].decode('UTF-8')
if my_val == u"":
my_val = def_val
config[cfg_name][item_name] = my_val
except:
my_val = def_val
try:
config[cfg_name][item_name] = my_val
except:
config[cfg_name] = {}
config[cfg_name][item_name] = my_val
return my_val
def check_setting_int(config, cfg_name, item_name, def_val):
try:
my_val = int(config[cfg_name][item_name])
except:
my_val = def_val
try:
config[cfg_name][item_name] = my_val
except:
config[cfg_name] = {}
config[cfg_name][item_name] = my_val
return my_val
CheckSection('General')
DB_ROOT = check_setting_str(CFG, 'General', 'DB_ROOT', "")
APP_DB_ROOT = check_setting_str(CFG, 'General', 'APP_DB_ROOT', os.getcwd())
MAIN_DIR = check_setting_str(CFG, 'General', 'MAIN_DIR', os.getcwd())
LOG_DIR = check_setting_str(CFG, 'General', 'LOG_DIR', os.getcwd())
PORT = check_setting_int(CFG, 'General', 'PORT', 8083)
NEWEST_BOOKS = check_setting_str(CFG, 'General', 'NEWEST_BOOKS', 60)
RANDOM_BOOKS = check_setting_int(CFG, 'General', 'RANDOM_BOOKS', 4)
CheckSection('Advanced')
TITLE_REGEX = check_setting_str(CFG, 'Advanced', 'TITLE_REGEX', '^(A|The|An|Der|Die|Das|Den|Ein|Eine|Einen|Dem|Des|Einem|Eines)\s+')
DEVELOPMENT = bool(check_setting_int(CFG, 'Advanced', 'DEVELOPMENT', 0))
PUBLIC_REG = bool(check_setting_int(CFG, 'Advanced', 'PUBLIC_REG', 0))
UPLOADING = bool(check_setting_int(CFG, 'Advanced', 'UPLOADING', 0))
ANON_BROWSE = bool(check_setting_int(CFG, 'Advanced', 'ANON_BROWSE', 0))
SYS_ENCODING = "UTF-8"
if DB_ROOT == "":
print "Calibre database directory (DB_ROOT) is not configured"
sys.exit(1)
configval = {"DB_ROOT": DB_ROOT, "APP_DB_ROOT": APP_DB_ROOT, "MAIN_DIR": MAIN_DIR, "LOG_DIR": LOG_DIR, "PORT": PORT,
"NEWEST_BOOKS": NEWEST_BOOKS, "DEVELOPMENT": DEVELOPMENT, "TITLE_REGEX": TITLE_REGEX,
"PUBLIC_REG": PUBLIC_REG, "UPLOADING": UPLOADING, "ANON_BROWSE": ANON_BROWSE}
def save_config(configval):
new_config = ConfigObj(encoding='UTF-8')
new_config.filename = CONFIG_FILE
new_config['General'] = {}
new_config['General']['DB_ROOT'] = configval["DB_ROOT"]
new_config['General']['APP_DB_ROOT'] = configval["APP_DB_ROOT"]
new_config['General']['MAIN_DIR'] = configval["MAIN_DIR"]
new_config['General']['LOG_DIR'] = configval["LOG_DIR"]
new_config['General']['PORT'] = configval["PORT"]
new_config['General']['NEWEST_BOOKS'] = configval["NEWEST_BOOKS"]
new_config['Advanced'] = {}
new_config['Advanced']['TITLE_REGEX'] = configval["TITLE_REGEX"]
new_config['Advanced']['DEVELOPMENT'] = int(configval["DEVELOPMENT"])
new_config['Advanced']['PUBLIC_REG'] = int(configval["PUBLIC_REG"])
new_config['Advanced']['UPLOADING'] = int(configval["UPLOADING"])
new_config['Advanced']['ANON_BROWSE'] = int(configval["ANON_BROWSE"])
new_config.write()
return "Saved"
save_config(configval)

130
cps/db.py
View File

@ -5,13 +5,11 @@ from sqlalchemy import *
from sqlalchemy.ext.declarative import declarative_base from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy.orm import * from sqlalchemy.orm import *
import os import os
import config # import config
import re import re
import ast import ast
# calibre sort stuff global session
title_pat = re.compile(config.TITLE_REGEX, re.IGNORECASE)
def title_sort(title): def title_sort(title):
match = title_pat.search(title) match = title_pat.search(title)
@ -21,59 +19,32 @@ def title_sort(title):
return title.strip() return title.strip()
dbpath = os.path.join(config.DB_ROOT, "metadata.db")
engine = create_engine('sqlite:///{0}'.format(dbpath.encode('utf-8')), echo=False)
conn = engine.connect()
conn.connection.create_function('title_sort', 1, title_sort)
Base = declarative_base() Base = declarative_base()
books_authors_link = Table('books_authors_link', Base.metadata, books_authors_link = Table('books_authors_link', Base.metadata,
Column('book', Integer, ForeignKey('books.id'), primary_key=True), Column('book', Integer, ForeignKey('books.id'), primary_key=True),
Column('author', Integer, ForeignKey('authors.id'), primary_key=True) Column('author', Integer, ForeignKey('authors.id'), primary_key=True)
) )
books_tags_link = Table('books_tags_link', Base.metadata, books_tags_link = Table('books_tags_link', Base.metadata,
Column('book', Integer, ForeignKey('books.id'), primary_key=True), Column('book', Integer, ForeignKey('books.id'), primary_key=True),
Column('tag', Integer, ForeignKey('tags.id'), primary_key=True) Column('tag', Integer, ForeignKey('tags.id'), primary_key=True)
) )
books_series_link = Table('books_series_link', Base.metadata, books_series_link = Table('books_series_link', Base.metadata,
Column('book', Integer, ForeignKey('books.id'), primary_key=True), Column('book', Integer, ForeignKey('books.id'), primary_key=True),
Column('series', Integer, ForeignKey('series.id'), primary_key=True) Column('series', Integer, ForeignKey('series.id'), primary_key=True)
) )
books_ratings_link = Table('books_ratings_link', Base.metadata, books_ratings_link = Table('books_ratings_link', Base.metadata,
Column('book', Integer, ForeignKey('books.id'), primary_key=True), Column('book', Integer, ForeignKey('books.id'), primary_key=True),
Column('rating', Integer, ForeignKey('ratings.id'), primary_key=True) Column('rating', Integer, ForeignKey('ratings.id'), primary_key=True)
) )
books_languages_link = Table('books_languages_link', Base.metadata, books_languages_link = Table('books_languages_link', Base.metadata,
Column('book', Integer, ForeignKey('books.id'), primary_key=True), Column('book', Integer, ForeignKey('books.id'), primary_key=True),
Column('lang_code', Integer, ForeignKey('languages.id'), primary_key=True) Column('lang_code', Integer, ForeignKey('languages.id'), primary_key=True)
) )
cc = conn.execute("SELECT id, datatype FROM custom_columns")
cc_ids = []
cc_exceptions = ['datetime', 'int', 'comments', 'float', 'composite', 'series']
books_custom_column_links = {}
cc_classes = {}
for row in cc:
if row.datatype not in cc_exceptions:
books_custom_column_links[row.id] = Table('books_custom_column_' + str(row.id) + '_link', Base.metadata,
Column('book', Integer, ForeignKey('books.id'), primary_key=True),
Column('value', Integer, ForeignKey('custom_column_' + str(row.id) + '.id'), primary_key=True)
)
cc_ids.append([row.id, row.datatype])
if row.datatype == 'bool':
ccdict = {'__tablename__': 'custom_column_' + str(row.id),
'id': Column(Integer, primary_key=True),
'book': Column(Integer, ForeignKey('books.id')),
'value': Column(Boolean)}
else:
ccdict = {'__tablename__': 'custom_column_' + str(row.id),
'id': Column(Integer, primary_key=True),
'value': Column(String)}
cc_classes[row.id] = type('Custom_Column_' + str(row.id), (Base,), ccdict)
class Identifiers(Base): class Identifiers(Base):
@ -260,15 +231,6 @@ class Books(Base):
return u"<Books('{0},{1}{2}{3}{4}{5}{6}{7}{8}')>".format(self.title, self.sort, self.author_sort, return u"<Books('{0},{1}{2}{3}{4}{5}{6}{7}{8}')>".format(self.title, self.sort, self.author_sort,
self.timestamp, self.pubdate, self.series_index, self.timestamp, self.pubdate, self.series_index,
self.last_modified, self.path, self.has_cover) self.last_modified, self.path, self.has_cover)
for id in cc_ids:
if id[1] == 'bool':
setattr(Books, 'custom_column_' + str(id[0]), relationship(cc_classes[id[0]],
primaryjoin=(Books.id == cc_classes[id[0]].book),
backref='books'))
else:
setattr(Books, 'custom_column_' + str(id[0]), relationship(cc_classes[id[0]],
secondary = books_custom_column_links[id[0]],
backref='books'))
class Custom_Columns(Base): class Custom_Columns(Base):
@ -288,7 +250,57 @@ class Custom_Columns(Base):
display_dict = ast.literal_eval(self.display) display_dict = ast.literal_eval(self.display)
return display_dict return display_dict
# Base.metadata.create_all(engine) def setup_db(config):
Session = sessionmaker() global session
Session.configure(bind=engine) # calibre sort stuff
session = Session() title_pat = re.compile(config.config_title_regex, re.IGNORECASE)
if config.config_calibre_dir is None:
return
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()
conn.connection.create_function('title_sort', 1, title_sort)
cc = conn.execute("SELECT id, datatype FROM custom_columns")
cc_ids = []
cc_exceptions = ['datetime', 'int', 'comments', 'float', 'composite', 'series']
books_custom_column_links = {}
cc_classes = {}
for row in cc:
if row.datatype not in cc_exceptions:
books_custom_column_links[row.id] = Table('books_custom_column_' + str(row.id) + '_link', Base.metadata,
Column('book', Integer, ForeignKey('books.id'),
primary_key=True),
Column('value', Integer,
ForeignKey('custom_column_' + str(row.id) + '.id'),
primary_key=True)
)
cc_ids.append([row.id, row.datatype])
if row.datatype == 'bool':
ccdict = {'__tablename__': 'custom_column_' + str(row.id),
'id': Column(Integer, primary_key=True),
'book': Column(Integer, ForeignKey('books.id')),
'value': Column(Boolean)}
else:
ccdict = {'__tablename__': 'custom_column_' + str(row.id),
'id': Column(Integer, primary_key=True),
'value': Column(String)}
cc_classes[row.id] = type('Custom_Column_' + str(row.id), (Base,), ccdict)
for id in cc_ids:
if id[1] == 'bool':
setattr(Books, 'custom_column_' + str(id[0]), relationship(cc_classes[id[0]],
primaryjoin=(
Books.id == cc_classes[id[0]].book),
backref='books'))
else:
setattr(Books, 'custom_column_' + str(id[0]), relationship(cc_classes[id[0]],
secondary=books_custom_column_links[id[0]],
backref='books'))
# Base.metadata.create_all(engine)
Session = sessionmaker()
Session.configure(bind=engine)
session = Session()

View File

@ -2,7 +2,7 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
import db, ub import db, ub
import config # import config
from flask import current_app as app from flask import current_app as app
import logging import logging
import smtplib import smtplib
@ -33,11 +33,12 @@ def update_download(book_id, user_id):
ub.session.commit() ub.session.commit()
def make_mobi(book_id): 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": if sys.platform == "win32":
kindlegen = os.path.join(config.MAIN_DIR, "vendor", u"kindlegen.exe") kindlegen = os.path.join(vendorpath, u"kindlegen.exe")
else: else:
kindlegen = os.path.join(config.MAIN_DIR, "vendor", u"kindlegen") kindlegen = os.path.join(vendorpath, u"kindlegen")
if not os.path.exists(kindlegen): if not os.path.exists(kindlegen):
app.logger.error("make_mobi: kindlegen binary not found in: %s" % kindlegen) app.logger.error("make_mobi: kindlegen binary not found in: %s" % kindlegen)
return None return None
@ -47,7 +48,7 @@ def make_mobi(book_id):
app.logger.error("make_mobi: epub format not found for book id: %d" % book_id) app.logger.error("make_mobi: epub format not found for book id: %d" % book_id)
return None return None
file_path = os.path.join(config.DB_ROOT, book.path, data.name) file_path = os.path.join(calibrepath, book.path, data.name)
if os.path.exists(file_path + u".epub"): if os.path.exists(file_path + u".epub"):
p = subprocess.Popen((kindlegen + " \"" + file_path + u".epub\" ").encode(sys.getfilesystemencoding()), p = subprocess.Popen((kindlegen + " \"" + file_path + u".epub\" ").encode(sys.getfilesystemencoding()),
shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE, stdin=subprocess.PIPE) shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE, stdin=subprocess.PIPE)
@ -90,15 +91,13 @@ class StderrLogger(object):
else: else:
self.buffer=self.buffer+message self.buffer=self.buffer+message
def send_test_mail(kindle_mail): def send_raw_email(kindle_mail,msg):
settings = ub.get_mail_settings() settings = ub.get_mail_settings()
msg = MIMEMultipart()
msg['From'] = settings["mail_from"] msg['From'] = settings["mail_from"]
msg['To'] = kindle_mail msg['To'] = kindle_mail
msg['Subject'] = _('Calibre-web test email')
text = _('This email has been sent via calibre web.')
use_ssl = settings.get('mail_use_ssl', 0) use_ssl = int(settings.get('mail_use_ssl', 0))
# convert MIME message to string # convert MIME message to string
fp = StringIO() fp = StringIO()
@ -113,16 +112,14 @@ def send_test_mail(kindle_mail):
org_stderr = smtplib.stderr org_stderr = smtplib.stderr
smtplib.stderr = StderrLogger() smtplib.stderr = StderrLogger()
if int(use_ssl) == 2: if use_ssl == 2:
mailserver = smtplib.SMTP_SSL(settings["mail_server"], settings["mail_port"], timeout) mailserver = smtplib.SMTP_SSL(settings["mail_server"], settings["mail_port"], timeout)
else: else:
mailserver = smtplib.SMTP(settings["mail_server"], settings["mail_port"], timeout) mailserver = smtplib.SMTP(settings["mail_server"], settings["mail_port"], timeout)
mailserver.set_debuglevel(1) mailserver.set_debuglevel(1)
if int(use_ssl) == 1: if use_ssl == 1:
#mailserver.ehlo()
mailserver.starttls() mailserver.starttls()
#mailserver.ehlo()
if settings["mail_password"]: if settings["mail_password"]:
mailserver.login(settings["mail_login"], settings["mail_password"]) mailserver.login(settings["mail_login"], settings["mail_password"])
@ -138,7 +135,15 @@ def send_test_mail(kindle_mail):
return None return None
def send_mail(book_id, kindle_mail): def send_test_mail(kindle_mail):
msg = MIMEMultipart()
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)
def send_mail(book_id, kindle_mail,calibrepath):
"""Send email with attachments""" """Send email with attachments"""
is_mobi = False is_mobi = False
is_azw = False is_azw = False
@ -149,17 +154,10 @@ def send_mail(book_id, kindle_mail):
settings = ub.get_mail_settings() settings = ub.get_mail_settings()
# create MIME message # create MIME message
msg = MIMEMultipart() msg = MIMEMultipart()
msg['From'] = settings["mail_from"]
msg['To'] = kindle_mail
msg['Subject'] = _(u'Send to Kindle') msg['Subject'] = _(u'Send to Kindle')
text = _(u'This email has been sent via calibre web.') text = _(u'This email has been sent via calibre web.')
msg.attach(MIMEText(text.encode('UTF-8'), 'plain', 'UTF-8')) msg.attach(MIMEText(text.encode('UTF-8'), 'plain', 'UTF-8'))
use_ssl = settings.get('mail_use_ssl', 0)
# attach files
# msg.attach(self.get_attachment(file_path))
book = db.session.query(db.Books).filter(db.Books.id == book_id).first() 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) data = db.session.query(db.Data).filter(db.Data.book == book.id)
@ -167,11 +165,11 @@ def send_mail(book_id, kindle_mail):
for entry in data: for entry in data:
if entry.format == "MOBI": if entry.format == "MOBI":
formats["mobi"] = os.path.join(config.DB_ROOT, book.path, entry.name + ".mobi") formats["mobi"] = os.path.join(calibrepath, book.path, entry.name + ".mobi")
if entry.format == "EPUB": if entry.format == "EPUB":
formats["epub"] = os.path.join(config.DB_ROOT, book.path, entry.name + ".epub") formats["epub"] = os.path.join(calibrepath, book.path, entry.name + ".epub")
if entry.format == "PDF": if entry.format == "PDF":
formats["pdf"] = os.path.join(config.DB_ROOT, book.path, entry.name + ".pdf") formats["pdf"] = os.path.join(calibrepath, book.path, entry.name + ".pdf")
if len(formats) == 0: if len(formats) == 0:
return _("Could not find any formats suitable for sending by email") return _("Could not find any formats suitable for sending by email")
@ -179,7 +177,7 @@ def send_mail(book_id, kindle_mail):
if 'mobi' in formats: if 'mobi' in formats:
msg.attach(get_attachment(formats['mobi'])) msg.attach(get_attachment(formats['mobi']))
elif 'epub' in formats: elif 'epub' in formats:
filepath = make_mobi(book.id) filepath = make_mobi(book.id,calibrepath)
if filepath is not None: if filepath is not None:
msg.attach(get_attachment(filepath)) msg.attach(get_attachment(filepath))
elif filepath is None: elif filepath is None:
@ -191,40 +189,7 @@ def send_mail(book_id, kindle_mail):
else: else:
return _("Could not find any formats suitable for sending by email") return _("Could not find any formats suitable for sending by email")
# convert MIME message to string return send_raw_email(kindle_mail, msg)
fp = StringIO()
gen = Generator(fp, mangle_from_=False)
gen.flatten(msg)
msg = fp.getvalue()
# send email
try:
timeout=600 # set timeout to 5mins
org_stderr = smtplib.stderr
smtplib.stderr = StderrLogger()
if int(use_ssl) == 2:
mailserver = smtplib.SMTP_SSL(settings["mail_server"], settings["mail_port"], timeout)
else:
mailserver = smtplib.SMTP(settings["mail_server"], settings["mail_port"], timeout)
mailserver.set_debuglevel(1)
if int(use_ssl) == 1:
mailserver.starttls()
if settings["mail_password"]:
mailserver.login(settings["mail_login"], settings["mail_password"])
mailserver.sendmail(settings["mail_login"], kindle_mail, msg)
mailserver.quit()
smtplib.stderr = org_stderr
except (socket.error, smtplib.SMTPRecipientsRefused, smtplib.SMTPException), e:
app.logger.error(traceback.print_exc())
return _("Failed to send mail: %s" % str(e))
return None
def get_attachment(file_path): def get_attachment(file_path):
@ -273,10 +238,10 @@ def get_normalized_author(value):
return value return value
def update_dir_stucture(book_id): def update_dir_stucture(book_id,calibrepath):
db.session.connection().connection.connection.create_function("title_sort", 1, db.title_sort) 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() book = db.session.query(db.Books).filter(db.Books.id == book_id).first()
path = os.path.join(config.DB_ROOT, book.path) path = os.path.join(calibrepath, book.path)
authordir = book.path.split(os.sep)[0] authordir = book.path.split(os.sep)[0]
new_authordir = get_valid_filename(book.authors[0].name, False) new_authordir = get_valid_filename(book.authors[0].name, False)

File diff suppressed because one or more lines are too long

View File

@ -73,9 +73,66 @@
<td>{% if config.PUBLIC_REG %}<span class="glyphicon glyphicon-ok"></span>{% else %}<span class="glyphicon glyphicon-remove"></span>{% endif %}</td> <td>{% if config.PUBLIC_REG %}<span class="glyphicon glyphicon-ok"></span>{% else %}<span class="glyphicon glyphicon-remove"></span>{% endif %}</td>
<td>{% if config.ANON_BROWSE %}<span class="glyphicon glyphicon-ok"></span>{% else %}<span class="glyphicon glyphicon-remove"></span>{% endif %}</td> <td>{% if config.ANON_BROWSE %}<span class="glyphicon glyphicon-ok"></span>{% else %}<span class="glyphicon glyphicon-remove"></span>{% endif %}</td>
</table> </table>
<div class="btn btn-default"><a href="{{url_for('configuration')}}">{{_('Configuration')}}</a></div>
<h2>{{_('Administration')}}</h2> <h2>{{_('Administration')}}</h2>
{% if not config.DEVELOPMENT %} {% if not config.DEVELOPMENT %}
<div class="btn btn-default"><a href="{{url_for('shutdown')}}">{{_('Restart Calibre-web')}}</a></div> <div class="btn btn-default" data-toggle="modal" data-target="#RestartDialog">{{_('Restart Calibre-web')}}</a></div>
<div class="btn btn-default" data-toggle="modal" data-target="#ShutdownDialog">{{_('Stop Calibre-web')}}</a></div>
{% endif %} {% endif %}
</div> </div>
<!-- Modal -->
<div id="RestartDialog" class="modal fade" role="dialog">
<div class="modal-dialog modal-sm">
<!-- Modal content-->
<div class="modal-content">
<div class="modal-header bg-info">
</div>
<div class="modal-body text-center">
<p>{{_('Do you really want to restart Calibre-web?')}}</p>
<button type="button" class="btn btn-default" id="restart" data-dismiss="modal">{{_('Ok')}}</button>
<button type="button" class="btn btn-default" data-dismiss="modal">{{_('Back')}}</button>
</div>
</div>
</div>
</div>
<div id="ShutdownDialog" class="modal fade" role="dialog">
<div class="modal-dialog modal-sm">
<!-- Modal content-->
<div class="modal-content">
<div class="modal-header bg-info">
</div>
<div class="modal-body text-center">
<p>{{_('Do you really want to stop Calibre-web?')}}</p>
<button type="button" class="btn btn-default" id="shutdown" data-dismiss="modal">{{_('Ok')}}</button>
<button type="button" class="btn btn-default" data-dismiss="modal">{{_('Back')}}</button>
</div>
</div>
</div>
</div>
{% endblock %}
{% block js %}
<script type="text/javascript">
$("#restart").click(function() {
$.ajax({
dataType: 'json',
url: "{{url_for('shutdown')}}",
data: {"parameter":0},
//data: data,
success: function(data) {
return alert(data.text);}
});
});
$("#shutdown").click(function() {
$.ajax({
dataType: 'json',
url: "{{url_for('shutdown')}}",
data: {"parameter":1},
success: function(data) {
return alert(data.text);}
});
});
</script>
{% endblock %} {% endblock %}

View File

@ -0,0 +1,55 @@
{% extends "layout.html" %}
{% block body %}
<div class="discover">
<h1>{{title}}</h1>
<form role="form" method="POST" autocomplete="off">
<div class="form-group required">
<label for="config_calibre_dir">{{_('Location of Calibre database')}}</label>
<input type="text" class="form-control" name="config_calibre_dir" id="config_calibre_dir" value="{% if content.config_calibre_dir != None %}{{ content.config_calibre_dir }}{% endif %}" autocomplete="off">
</div>
<div class="form-group">
<label for="config_port">{{_('Server Port')}}</label>
<input type="text" class="form-control" name="config_port" id="port" value="{% if content.config_port != None %}{{ content.config_port }}{% endif %}" autocomplete="off" required>
</div>
<div class="form-group">
<label for="config_calibre_web_title">{{_('Title')}}</label>
<input type="text" class="form-control" name="config_calibre_web_title" id="config_calibre_web_title" value="{% if content.config_calibre_web_title != None %}{{ content.config_calibre_web_title }}{% endif %}" autocomplete="off" required>
</div>
<div class="form-group">
<label for="config_books_per_page">{{_('Books per page')}}</label>
<input type="text" class="form-control" name="config_books_per_page" id="config_books_per_page" value="{% if content.config_books_per_page != None %}{{ content.config_books_per_page }}{% endif %}" autocomplete="off">
</div>
<div class="form-group">
<label for="config_random_books">{{_('No. of random books to show')}}</label>
<input type="text" class="form-control" name="config_random_books" id="config_random_books" value="{% if content.config_random_books != None %}{{ content.config_random_books }}{% endif %}" autocomplete="off">
</div>
<div class="form-group">
<label for="config_title_regex">{{_('Regular expression for title sorting')}}</label>
<input type="text" class="form-control" name="config_title_regex" id="config_title_regex" value="{% if content.config_title_regex != None %}{{ content.config_title_regex }}{% endif %}" autocomplete="off">
</div>
<div class="form-group">
<label for="config_log_level">{{_('Log Level')}}</label>
<select name="config_log_level" id="config_log_level" class="form-control">
<option value="DEBUG" {% if content.config_log_level == 'DEBUG' %}selected{% endif %}>DEBUG</option>
<option value="INFO" {% if content.config_log_level == 'INFO' or content.config_log_level == None %}selected{% endif %}>INFO</option>
<option value="WARNING" {% if content.config_log_level == 'WARNING' %}selected{% endif %}>WARNING</option>
<option value="ERROR" {% if content.config_log_level == 'ERROR' %}selected{% endif %}>ERROR</option>
</select>
</div>
<div class="form-group">
<input type="checkbox" id="config_uploading" name="config_uploading" {% if content.config_uploading %}checked{% endif %}>
<label for="config_uploading">{{_('Enable uploading')}}</label>
</div>
<div class="form-group">
<input type="checkbox" id="config_anonbrowse" name="config_anonbrowse" {% if content.config_anonbrowse %}checked{% endif %}>
<label for="config_anonbrowse">{{_('Enable anonymous browsing')}}</label>
</div>
<div class="form-group">
<input type="checkbox" id="config_public_reg" name="config_public_reg" {% if content.config_public_reg %}checked{% endif %}>
<label for="config_public_reg">{{_('Enable public registration')}}</label>
</div>
<button type="submit" class="btn btn-default">{{_('Submit')}}</button>
</form>
</div>
{% endblock %}

View File

@ -191,7 +191,6 @@
<div class="btn-toolbar" role="toolbar"> <div class="btn-toolbar" role="toolbar">
<div class="btn-group" role="group" aria-label="Edit/Delete book"> <div class="btn-group" role="group" aria-label="Edit/Delete book">
<a href="{{ url_for('edit_book', book_id=entry.id) }}" class="btn btn-sm btn-warning" role="button"><span class="glyphicon glyphicon-edit"></span> {{_('Edit metadata')}}</a> <a href="{{ url_for('edit_book', book_id=entry.id) }}" class="btn btn-sm btn-warning" role="button"><span class="glyphicon glyphicon-edit"></span> {{_('Edit metadata')}}</a>
<!-- <a href="{{ url_for('edit_book', book_id=entry.id) }}" class="btn btn-sm btn-danger" role="button"><span class="glyphicon glyphicon-trash"></span> Delete</a> -->
</div> </div>
{% endif %} {% endif %}

View File

@ -44,7 +44,6 @@
{% endif %} {% endif %}
</div> </div>
</div> </div>
<!-- <p><a href="{{ url_for('edit_book', book_id=entry.id) }}">{{entry.authors[0].name}}: {{entry.title}}</a></p> -->
{% endfor %} {% endfor %}
</div> </div>
</div> </div>

View File

@ -36,7 +36,6 @@
{% endif %} {% endif %}
</div> </div>
</div> </div>
<!-- <p><a href="{{ url_for('edit_book', book_id=entry.id) }}">{{entry.authors[0].name}}: {{entry.title}}</a></p> -->
{% endfor %} {% endfor %}
</div> </div>
</div> </div>

View File

@ -41,23 +41,23 @@
</select> </select>
</div> </div>
<div class="form-group"> <div class="form-group">
<input type="checkbox" name="show_random" {% if content.random_books %}checked{% endif %}> <input type="checkbox" name="show_random" id="show_random" {% if content.random_books %}checked{% endif %}>
<label for="show_random">{{_('Show random books')}}</label> <label for="show_random">{{_('Show random books')}}</label>
</div> </div>
<div class="form-group"> <div class="form-group">
<input type="checkbox" name="show_hot" {% if content.hot_books %}checked{% endif %}> <input type="checkbox" name="show_hot" id="show_hot" {% if content.hot_books %}checked{% endif %}>
<label for="show_hot">{{_('Show hot books')}}</label> <label for="show_hot">{{_('Show hot books')}}</label>
</div> </div>
<div class="form-group"> <div class="form-group">
<input type="checkbox" name="show_language" {% if content.language_books %}checked{% endif %}> <input type="checkbox" name="show_language" id="show_language" {% if content.language_books %}checked{% endif %}>
<label for="show_language">{{_('Show language selection')}}</label> <label for="show_language">{{_('Show language selection')}}</label>
</div> </div>
<div class="form-group"> <div class="form-group">
<input type="checkbox" name="show_series" {% if content.series_books %}checked{% endif %}> <input type="checkbox" name="show_series" id="show_series" {% if content.series_books %}checked{% endif %}>
<label for="show_series">{{_('Show series selection')}}</label> <label for="show_series">{{_('Show series selection')}}</label>
</div> </div>
<div class="form-group"> <div class="form-group">
<input type="checkbox" name="show_category" {% if content.category_books %}checked{% endif %}> <input type="checkbox" name="show_category" id="show_category" {% if content.category_books %}checked{% endif %}>
<label for="show_category">{{_('Show category selection')}}</label> <label for="show_category">{{_('Show category selection')}}</label>
</div> </div>

View File

@ -7,12 +7,12 @@ from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy.orm import * from sqlalchemy.orm import *
from flask_login import AnonymousUserMixin from flask_login import AnonymousUserMixin
import os import os
import config # import config
import traceback import traceback
from werkzeug.security import generate_password_hash from werkzeug.security import generate_password_hash
from flask_babel import gettext as _ from flask_babel import gettext as _
dbpath = os.path.join(config.APP_DB_ROOT, "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) engine = create_engine('sqlite:///{0}'.format(dbpath), echo=False)
Base = declarative_base() Base = declarative_base()
@ -96,6 +96,23 @@ class UserBase():
def __repr__(self): def __repr__(self):
return '<User %r>' % self.nickname return '<User %r>' % self.nickname
class Config():
def __init__(self):
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
class User(UserBase,Base): class User(UserBase,Base):
__tablename__ = 'user' __tablename__ = 'user'
@ -118,11 +135,14 @@ class User(UserBase,Base):
class Anonymous(AnonymousUserMixin,UserBase): class Anonymous(AnonymousUserMixin,UserBase):
anon_browse = None
def __init__(self): def __init__(self):
self.loadSettings() self.loadSettings()
def loadSettings(self): def loadSettings(self):
data=session.query(User).filter(User.role.op('&')(ROLE_ANONYMOUS) == ROLE_ANONYMOUS).first() data=session.query(User).filter(User.role.op('&')(ROLE_ANONYMOUS) == ROLE_ANONYMOUS).first()
settings=session.query(Settings).first()
self.nickname = data.nickname self.nickname = data.nickname
self.role = data.role self.role = data.role
self.random_books = data.random_books self.random_books = data.random_books
@ -133,6 +153,7 @@ class Anonymous(AnonymousUserMixin,UserBase):
self.hot_books = data.hot_books self.hot_books = data.hot_books
self.default_language = data.default_language self.default_language = data.default_language
self.locale = data.locale self.locale = data.locale
self.anon_browse = settings.config_anonbrowse
def role_admin(self): def role_admin(self):
return False return False
@ -141,7 +162,7 @@ class Anonymous(AnonymousUserMixin,UserBase):
return False return False
def is_anonymous(self): def is_anonymous(self):
return config.ANON_BROWSE return self.anon_browse
class Shelf(Base): class Shelf(Base):
@ -187,6 +208,16 @@ class Settings(Base):
mail_login = Column(String) mail_login = Column(String)
mail_password = Column(String) mail_password = Column(String)
mail_from = Column(String) mail_from = Column(String)
config_calibre_dir = Column(String)
config_port = Column(Integer, default = 8083)
config_calibre_web_title = Column(String,default = u'Calibre-web')
config_books_per_page = Column(Integer, default = 60)
config_random_books = Column(Integer, default = 4)
config_title_regex = Column(String,default=u'^(A|The|An|Der|Die|Das|Den|Ein|Eine|Einen|Dem|Des|Einem|Eines)\s+')
config_log_level = Column(String, default=u'INFO')
config_uploading = Column(SmallInteger, default = 0)
config_anonbrowse = Column(SmallInteger, default = 0)
config_public_reg = Column(SmallInteger, default = 0)
def __repr__(self): def __repr__(self):
#return '<Smtp %r>' % (self.mail_server) #return '<Smtp %r>' % (self.mail_server)
@ -216,14 +247,22 @@ def migrate_Database():
conn.execute("ALTER TABLE user ADD column hot_books INTEGER DEFAULT 1") conn.execute("ALTER TABLE user ADD column hot_books INTEGER DEFAULT 1")
session.commit() session.commit()
try: try:
session.query(exists().where(BookShelf.order)).scalar() session.query(exists().where(Settings.config_calibre_dir)).scalar()
session.commit() session.commit()
except exc.OperationalError: # Database is not compatible, some rows are missing except exc.OperationalError: # Database is not compatible, some rows are missing
conn = engine.connect() conn = engine.connect()
conn.execute("ALTER TABLE book_shelf_link ADD column `order` INTEGER DEFAULT 1") conn.execute("ALTER TABLE Settings ADD column `config_calibre_dir` String")
conn.execute("ALTER TABLE Settings ADD column `config_port` INTEGER DEFAULT 8083")
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_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() session.commit()
def create_default_config(): def create_default_config():
settings = Settings() settings = Settings()
settings.mail_server = "mail.example.com" settings.mail_server = "mail.example.com"

View File

@ -7,7 +7,7 @@ from logging.handlers import RotatingFileHandler
import textwrap import textwrap
from flask import Flask, render_template, session, request, Response, redirect, url_for, send_from_directory, \ from flask import Flask, render_template, session, request, Response, redirect, url_for, send_from_directory, \
make_response, g, flash, abort make_response, g, flash, abort
import db, config, ub, helper import ub, helper
import os import os
import errno import errno
from sqlalchemy.sql.expression import func from sqlalchemy.sql.expression import func
@ -33,32 +33,24 @@ from uuid import uuid4
import os.path import os.path
import sys import sys
import subprocess import subprocess
import shutil
import re import re
from shutil import move import db
from shutil import move, copyfile
from tornado.ioloop import IOLoop
try: try:
from wand.image import Image from wand.image import Image
use_generic_pdf_cover = False use_generic_pdf_cover = False
except ImportError, e: except ImportError, e:
use_generic_pdf_cover = True use_generic_pdf_cover = True
from shutil import copyfile
from cgi import escape from cgi import escape
mimetypes.init() ########################################## Global variables ########################################################
mimetypes.add_type('application/xhtml+xml', '.xhtml') global global_task
mimetypes.add_type('application/epub+zip', '.epub') global_task = None
mimetypes.add_type('application/x-mobipocket-ebook', '.mobi')
mimetypes.add_type('application/x-mobipocket-ebook', '.prc')
mimetypes.add_type('application/vnd.amazon.ebook', '.azw')
mimetypes.add_type('application/x-cbr', '.cbr')
mimetypes.add_type('application/x-cbz', '.cbz')
mimetypes.add_type('application/x-cbt', '.cbt')
mimetypes.add_type('image/vnd.djvu', '.djvu')
########################################## Proxy Helper class ######################################################
class ReverseProxied(object): class ReverseProxied(object):
"""Wrap the application in this middleware and configure the """Wrap the application in this middleware and configure the
front-end server to add these headers, to let you quietly bind front-end server to add these headers, to let you quietly bind
@ -96,11 +88,23 @@ class ReverseProxied(object):
environ['HTTP_HOST'] = server environ['HTTP_HOST'] = server
return self.app(environ, start_response) return self.app(environ, start_response)
########################################## Main code ##############################################################
mimetypes.init()
mimetypes.add_type('application/xhtml+xml', '.xhtml')
mimetypes.add_type('application/epub+zip', '.epub')
mimetypes.add_type('application/x-mobipocket-ebook', '.mobi')
mimetypes.add_type('application/x-mobipocket-ebook', '.prc')
mimetypes.add_type('application/vnd.amazon.ebook', '.azw')
mimetypes.add_type('application/x-cbr', '.cbr')
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 = (Flask(__name__))
app.wsgi_app = ReverseProxied(app.wsgi_app) app.wsgi_app = ReverseProxied(app.wsgi_app)
formatter = logging.Formatter( '''formatter = logging.Formatter(
"[%(asctime)s] {%(pathname)s:%(lineno)d} %(levelname)s - %(message)s") "[%(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.LOG_DIR, "calibre-web.log"), maxBytes=50000, backupCount=1)
file_handler.setFormatter(formatter) file_handler.setFormatter(formatter)
@ -112,7 +116,7 @@ else:
app.logger.info('Starting Calibre Web...') app.logger.info('Starting Calibre Web...')
logging.getLogger("book_formats").addHandler(file_handler) logging.getLogger("book_formats").addHandler(file_handler)
logging.getLogger("book_formats").setLevel(logging.INFO) logging.getLogger("book_formats").setLevel(logging.INFO)'''
Principal(app) Principal(app)
@ -120,10 +124,6 @@ babel = Babel(app)
import uploader import uploader
global global_queue
global_queue = None
lm = LoginManager(app) lm = LoginManager(app)
lm.init_app(app) lm.init_app(app)
lm.login_view = 'login' lm.login_view = 'login'
@ -131,6 +131,10 @@ lm.anonymous_user = ub.Anonymous
app.secret_key = 'A0Zr98j/3yX R~XHH!jmN]LWX/,?RT' app.secret_key = 'A0Zr98j/3yX R~XHH!jmN]LWX/,?RT'
# establish connection to calibre-db
config=ub.Config()
db.setup_db(config)
@babel.localeselector @babel.localeselector
def get_locale(): def get_locale():
@ -190,7 +194,7 @@ def requires_basic_auth_if_no_ano(f):
@wraps(f) @wraps(f)
def decorated(*args, **kwargs): def decorated(*args, **kwargs):
auth = request.authorization auth = request.authorization
if config.ANON_BROWSE != 1: if config.config_anonbrowse != 1:
if not auth or not check_auth(auth.username, auth.password): if not auth or not check_auth(auth.username, auth.password):
return authenticate() return authenticate()
return f(*args, **kwargs) return f(*args, **kwargs)
@ -257,7 +261,7 @@ app.jinja_env.globals['url_for_other_page'] = url_for_other_page
def login_required_if_no_ano(func): def login_required_if_no_ano(func):
if config.ANON_BROWSE == 1: if config.config_anonbrowse == 1:
return func return func
return login_required(func) return login_required(func)
@ -284,7 +288,6 @@ def admin_required(f):
""" """
Checks if current_user.role == 1 Checks if current_user.role == 1
""" """
@wraps(f) @wraps(f)
def inner(*args, **kwargs): def inner(*args, **kwargs):
if current_user.role_admin(): if current_user.role_admin():
@ -293,6 +296,18 @@ def admin_required(f):
return inner return inner
def unconfigured(f):
"""
Checks if current_user.role == 1
"""
@wraps(f)
def inner(*args, **kwargs):
if not config.config_calibre_dir:
return f(*args, **kwargs)
abort(403)
return inner
def download_required(f): def download_required(f):
@wraps(f) @wraps(f)
@ -331,14 +346,14 @@ def fill_indexpage(page, database, db_filter, order):
else: else:
filter = True filter = True
if current_user.show_random_books(): if current_user.show_random_books():
random = db.session.query(db.Books).filter(filter).order_by(func.random()).limit(config.RANDOM_BOOKS) random = db.session.query(db.Books).filter(filter).order_by(func.random()).limit(config.config_random_books)
else: else:
random = false random = false
off = int(int(config.NEWEST_BOOKS) * (page - 1)) off = int(int(config.config_books_per_page) * (page - 1))
pagination = Pagination(page, config.NEWEST_BOOKS, pagination = Pagination(page, config.config_books_per_page,
len(db.session.query(database).filter(db_filter).filter(filter).all())) len(db.session.query(database).filter(db_filter).filter(filter).all()))
entries = db.session.query(database).filter(db_filter).filter(filter).order_by(order).offset(off).limit( entries = db.session.query(database).filter(db_filter).filter(filter).order_by(order).offset(off).limit(
config.NEWEST_BOOKS) config.config_books_per_page)
return entries, random, pagination return entries, random, pagination
@ -400,9 +415,12 @@ def modify_database_object(input_elements, db_book_object, db_object, db_session
def before_request(): def before_request():
g.user = current_user g.user = current_user
g.public_shelfes = ub.session.query(ub.Shelf).filter(ub.Shelf.is_public == 1).all() g.public_shelfes = ub.session.query(ub.Shelf).filter(ub.Shelf.is_public == 1).all()
g.allow_registration = config.PUBLIC_REG g.allow_registration = config.config_public_reg
g.allow_upload = config.UPLOADING g.allow_upload = config.config_uploading
'''#################################################################################################################
########################################## Routing functions #######################################################
#################################################################################################################'''
@app.route("/opds") @app.route("/opds")
@requires_basic_auth_if_no_ano @requires_basic_auth_if_no_ano
@ -467,8 +485,8 @@ def feed_new():
if not off: if not off:
off = 0 off = 0
entries = db.session.query(db.Books).filter(filter).order_by(db.Books.timestamp.desc()).offset(off).limit( entries = db.session.query(db.Books).filter(filter).order_by(db.Books.timestamp.desc()).offset(off).limit(
config.NEWEST_BOOKS) config.config_books_per_page)
pagination = Pagination((int(off)/(int(config.NEWEST_BOOKS))+1), config.NEWEST_BOOKS, 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())) len(db.session.query(db.Books).filter(filter).all()))
xml = render_template('feed.xml', entries=entries, pagination=pagination) xml = render_template('feed.xml', entries=entries, pagination=pagination)
response = make_response(xml) response = make_response(xml)
@ -486,8 +504,8 @@ def feed_discover():
filter = True filter = True
# if not off: # if not off:
# off = 0 # off = 0
entries = db.session.query(db.Books).filter(filter).order_by(func.random()).limit(config.NEWEST_BOOKS) entries = db.session.query(db.Books).filter(filter).order_by(func.random()).limit(config.config_books_per_page)
pagination = Pagination(1, config.NEWEST_BOOKS,int(config.NEWEST_BOOKS)) pagination = Pagination(1, config.config_books_per_page,int(config.config_books_per_page))
xml = render_template('feed.xml', entries=entries, pagination=pagination) xml = render_template('feed.xml', entries=entries, pagination=pagination)
response = make_response(xml) response = make_response(xml)
response.headers["Content-Type"] = "application/xml" response.headers["Content-Type"] = "application/xml"
@ -505,8 +523,8 @@ def feed_hot():
if not off: if not off:
off = 0 off = 0
entries = db.session.query(db.Books).filter(filter).filter(db.Books.ratings.any(db.Ratings.rating > 9)).offset( entries = db.session.query(db.Books).filter(filter).filter(db.Books.ratings.any(db.Ratings.rating > 9)).offset(
off).limit(config.NEWEST_BOOKS) off).limit(config.config_books_per_page)
pagination = Pagination((int(off)/(int(config.NEWEST_BOOKS))+1), config.NEWEST_BOOKS, 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())) 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) xml = render_template('feed.xml', entries=entries, pagination=pagination)
response = make_response(xml) response = make_response(xml)
@ -525,8 +543,8 @@ def feed_authorindex():
filter = True filter = True
if not off: if not off:
off = 0 off = 0
authors = db.session.query(db.Authors).order_by(db.Authors.sort).offset(off).limit(config.NEWEST_BOOKS) 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.NEWEST_BOOKS))+1), config.NEWEST_BOOKS, pagination = Pagination((int(off)/(int(config.config_books_per_page))+1), config.config_books_per_page,
len(db.session.query(db.Authors).all())) len(db.session.query(db.Authors).all()))
xml = render_template('feed.xml', authors=authors, pagination=pagination) xml = render_template('feed.xml', authors=authors, pagination=pagination)
response = make_response(xml) response = make_response(xml)
@ -545,8 +563,8 @@ def feed_author(id):
if not off: if not off:
off = 0 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.NEWEST_BOOKS) filter).offset(off).limit(config.config_books_per_page)
pagination = Pagination((int(off)/(int(config.NEWEST_BOOKS))+1), config.NEWEST_BOOKS, 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())) 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) xml = render_template('feed.xml', entries=entries, pagination=pagination)
response = make_response(xml) response = make_response(xml)
@ -560,8 +578,8 @@ def feed_categoryindex():
off = request.args.get("offset") off = request.args.get("offset")
if not off: if not off:
off = 0 off = 0
entries = db.session.query(db.Tags).order_by(db.Tags.name).offset(off).limit(config.NEWEST_BOOKS) 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.NEWEST_BOOKS))+1), config.NEWEST_BOOKS, pagination = Pagination((int(off)/(int(config.config_books_per_page))+1), config.config_books_per_page,
len(db.session.query(db.Tags).all())) len(db.session.query(db.Tags).all()))
xml = render_template('feed.xml', categorys=entries, pagination=pagination) xml = render_template('feed.xml', categorys=entries, pagination=pagination)
response = make_response(xml) response = make_response(xml)
@ -580,8 +598,8 @@ def feed_category(id):
if not off: if not off:
off = 0 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.NEWEST_BOOKS) db.Books.timestamp.desc()).filter(filter).offset(off).limit(config.config_books_per_page)
pagination = Pagination((int(off)/(int(config.NEWEST_BOOKS))+1), config.NEWEST_BOOKS, 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())) 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) xml = render_template('feed.xml', entries=entries, pagination=pagination)
response = make_response(xml) response = make_response(xml)
@ -599,8 +617,8 @@ def feed_seriesindex():
filter = True filter = True
if not off: if not off:
off = 0 off = 0
entries = db.session.query(db.Series).order_by(db.Series.name).offset(off).limit(config.NEWEST_BOOKS) 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.NEWEST_BOOKS))+1), config.NEWEST_BOOKS, pagination = Pagination((int(off)/(int(config.config_books_per_page))+1), config.config_books_per_page,
len(db.session.query(db.Series).all())) len(db.session.query(db.Series).all()))
xml = render_template('feed.xml', series=entries, pagination=pagination) xml = render_template('feed.xml', series=entries, pagination=pagination)
response = make_response(xml) response = make_response(xml)
@ -619,8 +637,8 @@ def feed_series(id):
if not off: if not off:
off = 0 off = 0
entries = db.session.query(db.Books).filter(db.Books.series.any(db.Series.id == id)).order_by( 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.NEWEST_BOOKS) db.Books.timestamp.desc()).filter(filter).offset(off).limit(config.config_books_per_page)
pagination = Pagination((int(off)/(int(config.NEWEST_BOOKS))+1), config.NEWEST_BOOKS, 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())) 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) xml = render_template('feed.xml', entries=entries, pagination=pagination)
response = make_response(xml) response = make_response(xml)
@ -754,15 +772,15 @@ def hot_books(page):
random = db.session.query(db.Books).filter(filter).order_by(func.random()).limit(config.RANDOM_BOOKS) random = db.session.query(db.Books).filter(filter).order_by(func.random()).limit(config.RANDOM_BOOKS)
else: else:
random = false random = false
off = int(int(config.NEWEST_BOOKS) * (page - 1)) off = int(int(config.config_books_per_page) * (page - 1))
all_books = ub.session.query(ub.Downloads, ub.func.count(ub.Downloads.book_id)).order_by( all_books = ub.session.query(ub.Downloads, ub.func.count(ub.Downloads.book_id)).order_by(
ub.func.count(ub.Downloads.book_id).desc()).group_by(ub.Downloads.book_id) ub.func.count(ub.Downloads.book_id).desc()).group_by(ub.Downloads.book_id)
hot_books = all_books.offset(off).limit(config.NEWEST_BOOKS) hot_books = all_books.offset(off).limit(config.config_books_per_page)
entries = list() entries = list()
for book in hot_books: for book in hot_books:
entries.append(db.session.query(db.Books).filter(filter).filter(db.Books.id == book.Downloads.book_id).first()) entries.append(db.session.query(db.Books).filter(filter).filter(db.Books.id == book.Downloads.book_id).first())
numBooks = entries.__len__() numBooks = entries.__len__()
pagination = Pagination(page, config.NEWEST_BOOKS, numBooks) pagination = Pagination(page, config.config_books_per_page, numBooks)
return render_template('index.html', random=random, entries=entries, pagination=pagination, return render_template('index.html', random=random, entries=entries, pagination=pagination,
title=_(u"Hot Books (most downloaded)")) title=_(u"Hot Books (most downloaded)"))
@ -958,13 +976,27 @@ def stats():
@app.route("/shutdown") @app.route("/shutdown")
@login_required @login_required
@admin_required
def shutdown(): def shutdown():
# logout_user() global global_task
# add restart command to queue task = int(request.args.get("parameter").strip())
global_queue.put("something") global_task = task
flash(_(u"Server restarts"), category="info") if task == 1 or task == 0: # valid commandos received
return redirect(url_for("index")) # close all database connections
db.session.close()
db.engine.dispose()
ub.session.close()
ub.engine.dispose()
# stop tornado server
server=IOLoop.instance()
server.add_callback(server.stop)
if task == 0:
text['text']=_(u'Performing Restart, please reload page')
else:
text['text']= _(u'Performing shutdown of server, please close window')
return json.dumps(text)
else:
abort(404)
@app.route("/search", methods=["GET"]) @app.route("/search", methods=["GET"])
@login_required_if_no_ano @login_required_if_no_ano
@ -1200,6 +1232,9 @@ def register():
def login(): def login():
error = None error = None
if config.config_calibre_dir == None:
return redirect(url_for('basic_configuration'))
if current_user is not None and current_user.is_authenticated: if current_user is not None and current_user.is_authenticated:
return redirect(url_for('index')) return redirect(url_for('index'))
@ -1234,7 +1269,7 @@ def send_to_kindle(book_id):
if settings.get("mail_server", "mail.example.com") == "mail.example.com": if settings.get("mail_server", "mail.example.com") == "mail.example.com":
flash(_(u"Please configure the SMTP mail settings first..."), category="error") flash(_(u"Please configure the SMTP mail settings first..."), category="error")
elif current_user.kindle_mail: elif current_user.kindle_mail:
result = helper.send_mail(book_id, current_user.kindle_mail) result = helper.send_mail(book_id, current_user.kindle_mail,config.config_calibre_dir)
if result is None: if result is None:
flash(_(u"Book successfully send to %(kindlemail)s", kindlemail=current_user.kindle_mail), flash(_(u"Book successfully send to %(kindlemail)s", kindlemail=current_user.kindle_mail),
category="success") category="success")
@ -1357,7 +1392,7 @@ def delete_shelf(shelf_id):
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()
flash(_("successfully deleted shelf %(name)s", name=cur_shelf.name, category="success")) flash(_(u"successfully deleted shelf %(name)s", name=cur_shelf.name, category="success"))
return redirect(url_for('index')) return redirect(url_for('index'))
@ -1486,9 +1521,56 @@ def admin():
@login_required @login_required
@admin_required @admin_required
def configuration(): def configuration():
content = ub.session.query(ub.User).all() return render_template("config_edit.html", content=config, title=_(u"Basic Configuration"))
settings = ub.session.query(ub.Settings).first()
return render_template("admin.html", content=content, email=settings, config=config, title=_(u"Admin page")) @app.route("/config", methods=["GET", "POST"] )
@unconfigured
def basic_configuration():
global global_task
if request.method == "POST":
to_save = request.form.to_dict()
content = ub.session.query(ub.Settings).first()
if "config_calibre_dir" in to_save:
content.config_calibre_dir = to_save["config_calibre_dir"]
if "config_port" in to_save:
content.config_port = to_save["config_port"]
if "config_calibre_web_title" in to_save:
content.config_calibre_web_title = to_save["config_calibre_web_title"]
if "config_calibre_web_title" in to_save:
content.config_calibre_web_title = to_save["config_calibre_web_title"]
if "config_title_regex" in to_save:
content.config_title_regex = to_save["config_title_regex"]
if "config_log_level" in to_save:
content.config_log_level = 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:
content.config_books_per_page = int(to_save["config_books_per_page"])
content.config_uploading = 0
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
if "config_anonbrowse" in to_save and to_save["config_anonbrowse"] == "on":
content.config_anonbrowse = 1
if "config_public_reg" in to_save and to_save["config_public_reg"] == "on":
content.config_public_reg = 1
try:
ub.session.commit()
flash(_(u"Calibre-web configuration updated"), category="success")
config.loadSettings()
except e:
flash(e, category="error")
return render_template("config_edit.html", content=config, title=_(u"Basic Configuration"))
ub.session.close()
ub.engine.dispose()
# stop tornado server
server = IOLoop.instance()
server.add_callback(server.stop)
global_task = 0
return render_template("config_edit.html", content=config, title=_(u"Basic Configuration"))
@app.route("/admin/user/new", methods=["GET", "POST"]) @app.route("/admin/user/new", methods=["GET", "POST"])
@ -1513,6 +1595,7 @@ def new_user():
content.nickname = to_save["nickname"] content.nickname = to_save["nickname"]
content.email = to_save["email"] content.email = to_save["email"]
content.default_language = to_save["default_language"] content.default_language = to_save["default_language"]
if "locale" in to_save:
content.locale = to_save["locale"] content.locale = to_save["locale"]
content.random_books = 0 content.random_books = 0
content.language_books = 0 content.language_books = 0
@ -1541,13 +1624,13 @@ def new_user():
try: try:
ub.session.add(content) ub.session.add(content)
ub.session.commit() ub.session.commit()
flash(_("User '%(user)s' created", user=content.nickname), category="success") flash(_(u"User '%(user)s' created", user=content.nickname), category="success")
return redirect(url_for('admin')) return redirect(url_for('admin'))
except IntegrityError: except IntegrityError:
ub.session.rollback() ub.session.rollback()
flash(_(u"Found an existing account for this email address or nickname."), category="error") flash(_(u"Found an existing account for this email address or nickname."), category="error")
return render_template("user_edit.html", new_user=1, content=content, translations=translations, return render_template("user_edit.html", new_user=1, content=content, translations=translations,
languages=languages, title=_("Add new user")) languages=languages, title=_(u"Add new user"))
@app.route("/admin/mailsettings", methods=["GET", "POST"]) @app.route("/admin/mailsettings", methods=["GET", "POST"])
@ -1575,7 +1658,7 @@ def edit_mailsettings():
category="success") category="success")
else: else:
flash(_(u"There was an error sending the Test E-Mail: %(res)s", res=result), category="error") flash(_(u"There was an error sending the Test E-Mail: %(res)s", res=result), category="error")
return render_template("email_edit.html", content=content, title=_("Edit mail settings")) return render_template("email_edit.html", content=content, title=_(u"Edit mail settings"))
@app.route("/admin/user/<int:user_id>", methods=["GET", "POST"]) @app.route("/admin/user/<int:user_id>", methods=["GET", "POST"])
@ -1863,13 +1946,13 @@ def edit_book(book_id):
for author in book.authors: for author in book.authors:
author_names.append(author.name) author_names.append(author.name)
for b in edited_books_id: for b in edited_books_id:
helper.update_dir_stucture(b) helper.update_dir_stucture(b,config.config_calibre_dir)
if "detail_view" in to_save: if "detail_view" in to_save:
return redirect(url_for('show_book', id=book.id)) return redirect(url_for('show_book', id=book.id))
else: else:
return render_template('edit_book.html', book=book, authors=author_names, cc=cc) return render_template('book_edit.html', book=book, authors=author_names, cc=cc)
else: else:
return render_template('edit_book.html', book=book, authors=author_names, cc=cc) return render_template('book_edit.html', book=book, authors=author_names, cc=cc)
else: else:
flash(_(u"Error opening eBook. File does not exist or file is not accessible:"), category="error") flash(_(u"Error opening eBook. File does not exist or file is not accessible:"), category="error")
return redirect(url_for("index")) return redirect(url_for("index"))
@ -1942,6 +2025,6 @@ def upload():
author_names.append(author.name) author_names.append(author.name)
cc = db.session.query(db.Custom_Columns).filter(db.Custom_Columns.datatype.notin_(db.cc_exceptions)).all() 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(): if current_user.role_edit() or current_user.role_admin():
return render_template('edit_book.html', book=db_book, authors=author_names, cc=cc) return render_template('book_edit.html', book=db_book, authors=author_names, cc=cc)
book_in_shelfs = [] book_in_shelfs = []
return render_template('detail.html', entry=db_book, cc=cc, title=db_book.title, books_shelfs=book_in_shelfs) return render_template('detail.html', entry=db_book, cc=cc, title=db_book.title, books_shelfs=book_in_shelfs)