diff --git a/.gitignore b/.gitignore index 255f4b37..16abdd7a 100644 --- a/.gitignore +++ b/.gitignore @@ -17,6 +17,7 @@ eggs/ *.db *.log config.ini +cps/static/[0-9]* .idea/ *.bak diff --git a/cps.py b/cps.py index 4e0b711d..1c81e01e 100755 --- a/cps.py +++ b/cps.py @@ -2,10 +2,14 @@ import os import sys +from threading import Thread +from multiprocessing import Queue +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')) +sys.path.insert(0, os.path.join(base_path, 'vendor')) from cps import web from cps import config @@ -15,11 +19,47 @@ from tornado.ioloop import IOLoop global title_sort + def title_sort(title): return title -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() \ No newline at end of file + + +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 config.DEVELOPMENT: + web.app.run(host="0.0.0.0",port=config.PORT, debug=True) + else: + while True: + q = Queue() + t = Thread(target=start_calibreweb, args=(q,)) + 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() + diff --git a/cps/book_formats.py b/cps/book_formats.py index 894c71c5..bd7d17c5 100644 --- a/cps/book_formats.py +++ b/cps/book_formats.py @@ -1,18 +1,22 @@ +import logging +import uploader +import os +from flask_babel import gettext as _ + __author__ = 'lemmsh' -import logging logger = logging.getLogger("book_formats") -import uploader -import os try: from wand.image import Image + from wand import version as ImageVersion use_generic_pdf_cover = False except ImportError, e: logger.warning('cannot import Image, generating pdf covers for pdf uploads will not work: %s', e) use_generic_pdf_cover = True try: from PyPDF2 import PdfFileReader + from PyPDF2 import __version__ as PyPdfVersion use_pdf_meta = True except ImportError, e: logger.warning('cannot import PyPDF2, extracting pdf metadata will not work: %s', e) @@ -37,9 +41,9 @@ def process(tmp_file_path, original_file_name, original_file_extension): try: if ".PDF" == original_file_extension.upper(): return pdf_meta(tmp_file_path, original_file_name, original_file_extension) - if ".EPUB" == original_file_extension.upper() and use_epub_meta == True: + if ".EPUB" == original_file_extension.upper() and use_epub_meta is True: return epub.get_epub_info(tmp_file_path, original_file_name, original_file_extension) - if ".FB2" == original_file_extension.upper() and use_fb2_meta == True: + if ".FB2" == original_file_extension.upper() and use_fb2_meta is True: return fb2.get_fb2_info(tmp_file_path, original_file_name, original_file_extension) except Exception, e: logger.warning('cannot parse metadata, using default: %s', e) @@ -47,29 +51,28 @@ def process(tmp_file_path, original_file_name, original_file_extension): return default_meta(tmp_file_path, original_file_name, original_file_extension) - def default_meta(tmp_file_path, original_file_name, original_file_extension): return uploader.BookMeta( - file_path = tmp_file_path, - extension = original_file_extension, - title = original_file_name, - author = "Unknown", - cover = None, - description = "", - tags = "", - series = "", + file_path=tmp_file_path, + extension=original_file_extension, + title=original_file_name, + author="Unknown", + cover=None, + description="", + tags="", + series="", series_id="") def pdf_meta(tmp_file_path, original_file_name, original_file_extension): - if (use_pdf_meta): + if use_pdf_meta: pdf = PdfFileReader(open(tmp_file_path, 'rb')) doc_info = pdf.getDocumentInfo() else: doc_info = None - if (doc_info is not None): + if doc_info is not None: author = doc_info.author if doc_info.author is not None else "Unknown" title = doc_info.title if doc_info.title is not None else original_file_name subject = doc_info.subject @@ -78,16 +81,17 @@ def pdf_meta(tmp_file_path, original_file_name, original_file_extension): title = original_file_name subject = "" return uploader.BookMeta( - file_path = tmp_file_path, - extension = original_file_extension, - title = title, - author = author, - cover = pdf_preview(tmp_file_path, original_file_name), - description = subject, - tags = "", - series = "", + file_path=tmp_file_path, + extension=original_file_extension, + title=title, + author=author, + cover=pdf_preview(tmp_file_path, original_file_name), + description=subject, + tags="", + series="", series_id="") + def pdf_preview(tmp_file_path, tmp_dir): if use_generic_pdf_cover: return None @@ -97,3 +101,14 @@ def pdf_preview(tmp_file_path, tmp_dir): img.compression_quality = 88 img.save(filename=os.path.join(tmp_dir, cover_file_name)) return cover_file_name + +def get_versions(): + if not use_generic_pdf_cover: + IVersion=ImageVersion.MAGICK_VERSION + else: + IVersion=_('not installed') + if use_pdf_meta: + PVersion=PyPdfVersion + else: + PVersion=_('not installed') + return {'ImageVersion':IVersion,'PyPdfVersion':PVersion} \ No newline at end of file diff --git a/cps/config.py b/cps/config.py index 127a0e61..ec3fbd9a 100755 --- a/cps/config.py +++ b/cps/config.py @@ -5,9 +5,10 @@ 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") +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' +CFG.encoding = 'UTF-8' + def CheckSection(sec): """ Check if INI section exists, if not create it """ @@ -18,6 +19,7 @@ def CheckSection(sec): 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') @@ -62,24 +64,16 @@ 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" +SYS_ENCODING = "UTF-8" if DB_ROOT == "": print "Calibre database directory (DB_ROOT) is not configured" sys.exit(1) -configval={} -configval["DB_ROOT"] = DB_ROOT -configval["APP_DB_ROOT"] = APP_DB_ROOT -configval["MAIN_DIR"] = MAIN_DIR -configval["LOG_DIR"] = LOG_DIR -configval["PORT"] = PORT -configval["NEWEST_BOOKS"] = NEWEST_BOOKS -configval["DEVELOPMENT"] = DEVELOPMENT -configval["TITLE_REGEX"] = TITLE_REGEX -configval["PUBLIC_REG"] = PUBLIC_REG -configval["UPLOADING"] = UPLOADING -configval["ANON_BROWSE"] = ANON_BROWSE +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') diff --git a/cps/db.py b/cps/db.py index 9a89b4b7..86b7c92f 100755 --- a/cps/db.py +++ b/cps/db.py @@ -9,8 +9,10 @@ import config import re import ast -#calibre sort stuff +# calibre sort stuff title_pat = re.compile(config.TITLE_REGEX, re.IGNORECASE) + + def title_sort(title): match = title_pat.search(title) if match: @@ -52,7 +54,7 @@ books_languages_link = Table('books_languages_link', Base.metadata, cc = conn.execute("SELECT id, datatype FROM custom_columns") cc_ids = [] -cc_exceptions = [ 'datetime', 'int', 'comments', 'float', 'composite','series' ] +cc_exceptions = ['datetime', 'int', 'comments', 'float', 'composite', 'series'] books_custom_column_links = {} cc_classes = {} for row in cc: @@ -61,18 +63,19 @@ for row in cc: 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]) + 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')), + '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)} + 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 Comments(Base): __tablename__ = 'comments' @@ -100,6 +103,7 @@ class Tags(Base): def __repr__(self): return u"".format(self.name) + class Authors(Base): __tablename__ = 'authors' @@ -116,6 +120,7 @@ class Authors(Base): def __repr__(self): return u"".format(self.name, self.sort, self.link) + class Series(Base): __tablename__ = 'series' @@ -130,30 +135,33 @@ class Series(Base): def __repr__(self): return u"".format(self.name, self.sort) + class Ratings(Base): __tablename__ = 'ratings' id = Column(Integer, primary_key=True) rating = Column(Integer) - def __init__(self,rating): + def __init__(self, rating): self.rating = rating def __repr__(self): return u"".format(self.rating) + class Languages(Base): __tablename__ = 'languages' id = Column(Integer, primary_key=True) lang_code = Column(String) - def __init__(self,lang_code): + def __init__(self, lang_code): self.lang_code = lang_code def __repr__(self): return u"".format(self.lang_code) + class Data(Base): __tablename__ = 'data' @@ -172,6 +180,7 @@ class Data(Base): def __repr__(self): return u"".format(self.book, self.format, self.uncompressed_size, self.name) + class Books(Base): __tablename__ = 'books' @@ -207,17 +216,24 @@ class Books(Base): self.has_cover = has_cover def __repr__(self): - return u"".format(self.title, self.sort, self.author_sort, self.timestamp, self.pubdate, self.series_index, self.last_modified ,self.path, self.has_cover) + return u"".format(self.title, self.sort, self.author_sort, + self.timestamp, self.pubdate, self.series_index, + 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')) + 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')) + 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): __tablename__ = 'custom_columns' - id = Column(Integer,primary_key=True) + id = Column(Integer, primary_key=True) label = Column(String) name = Column(String) datatype = Column(String) @@ -231,9 +247,7 @@ class Custom_Columns(Base): display_dict = ast.literal_eval(self.display) return display_dict -#Base.metadata.create_all(engine) +# Base.metadata.create_all(engine) Session = sessionmaker() Session.configure(bind=engine) session = Session() - - diff --git a/cps/epub.py b/cps/epub.py index 03fb30bb..3c79b82b 100644 --- a/cps/epub.py +++ b/cps/epub.py @@ -3,8 +3,9 @@ from lxml import etree import os import uploader + def extractCover(zip, coverFile, tmp_file_name): - if (coverFile is None): + if coverFile is None: return None else: cf = zip.read("OPS/" + coverFile) @@ -16,35 +17,34 @@ def extractCover(zip, coverFile, tmp_file_name): return tmp_cover_name - def get_epub_info(tmp_file_path, original_file_name, original_file_extension): ns = { - 'n':'urn:oasis:names:tc:opendocument:xmlns:container', - 'pkg':'http://www.idpf.org/2007/opf', - 'dc':'http://purl.org/dc/elements/1.1/' + 'n': 'urn:oasis:names:tc:opendocument:xmlns:container', + 'pkg': 'http://www.idpf.org/2007/opf', + 'dc': 'http://purl.org/dc/elements/1.1/' } zip = zipfile.ZipFile(tmp_file_path) txt = zip.read('META-INF/container.xml') tree = etree.fromstring(txt) - cfname = tree.xpath('n:rootfiles/n:rootfile/@full-path',namespaces=ns)[0] + cfname = tree.xpath('n:rootfiles/n:rootfile/@full-path', namespaces=ns)[0] cf = zip.read(cfname) tree = etree.fromstring(cf) - p = tree.xpath('/pkg:package/pkg:metadata',namespaces=ns)[0] + p = tree.xpath('/pkg:package/pkg:metadata', namespaces=ns)[0] epub_metadata = {} for s in ['title', 'description', 'creator']: - tmp = p.xpath('dc:%s/text()'%(s),namespaces=ns) - if (len(tmp) > 0): - epub_metadata[s] = p.xpath('dc:%s/text()'%(s),namespaces=ns)[0] + tmp = p.xpath('dc:%s/text()' % s, namespaces=ns) + if len(tmp) > 0: + epub_metadata[s] = p.xpath('dc:%s/text()' % s, namespaces=ns)[0] else: epub_metadata[s] = "Unknown" - coversection = tree.xpath("/pkg:package/pkg:manifest/pkg:item[@id='cover']/@href",namespaces=ns) - if (len(coversection) > 0): + coversection = tree.xpath("/pkg:package/pkg:manifest/pkg:item[@id='cover']/@href", namespaces=ns) + if len(coversection) > 0: coverfile = extractCover(zip, coversection[0], tmp_file_path) else: coverfile = None @@ -53,15 +53,13 @@ def get_epub_info(tmp_file_path, original_file_name, original_file_extension): else: title = epub_metadata['title'] - return uploader.BookMeta( - file_path = tmp_file_path, - extension = original_file_extension, - title = title, - author = epub_metadata['creator'], - cover = coverfile, - description = epub_metadata['description'], - tags = "", - series = "", + file_path=tmp_file_path, + extension=original_file_extension, + title=title, + author=epub_metadata['creator'], + cover=coverfile, + description=epub_metadata['description'], + tags="", + series="", series_id="") - diff --git a/cps/fb2.py b/cps/fb2.py index 79449c4d..93e3dcc2 100644 --- a/cps/fb2.py +++ b/cps/fb2.py @@ -7,29 +7,31 @@ import uploader def get_fb2_info(tmp_file_path, original_file_name, original_file_extension): ns = { - 'fb':'http://www.gribuser.ru/xml/fictionbook/2.0', - 'l':'http://www.w3.org/1999/xlink', + 'fb': 'http://www.gribuser.ru/xml/fictionbook/2.0', + 'l': 'http://www.w3.org/1999/xlink', } fb2_file = open(tmp_file_path) tree = etree.fromstring(fb2_file.read()) authors = tree.xpath('/fb:FictionBook/fb:description/fb:title-info/fb:author', namespaces=ns) + def get_author(element): - return element.xpath('fb:first-name/text()', namespaces=ns)[0] + ' ' + element.xpath('fb:middle-name/text()', namespaces=ns)[0] + ' ' + element.xpath('fb:last-name/text()', namespaces=ns)[0] + return element.xpath('fb:first-name/text()', namespaces=ns)[0] + ' ' + element.xpath('fb:middle-name/text()', + namespaces=ns)[0] + ' ' + element.xpath('fb:last-name/text()', namespaces=ns)[0] author = ", ".join(map(get_author, authors)) title = unicode(tree.xpath('/fb:FictionBook/fb:description/fb:title-info/fb:book-title/text()', namespaces=ns)[0]) - description = unicode(tree.xpath('/fb:FictionBook/fb:description/fb:publish-info/fb:book-name/text()', namespaces=ns)[0]) + description = unicode(tree.xpath('/fb:FictionBook/fb:description/fb:publish-info/fb:book-name/text()', + namespaces=ns)[0]) return uploader.BookMeta( - file_path = tmp_file_path, - extension = original_file_extension, - title = title, - author = author, - cover = None, - description = description, - tags = "", - series = "", + file_path=tmp_file_path, + extension=original_file_extension, + title=title, + author=author, + cover=None, + description=description, + tags="", + series="", series_id="") - diff --git a/cps/helper.py b/cps/helper.py index 0fd9b95e..bc687573 100755 --- a/cps/helper.py +++ b/cps/helper.py @@ -4,8 +4,10 @@ import db, ub import config from flask import current_app as app +import logging import smtplib +import tempfile import socket import sys import os @@ -21,16 +23,19 @@ from email.generator import Generator from flask_babel import gettext as _ import subprocess + def update_download(book_id, user_id): - check = ub.session.query(ub.Downloads).filter(ub.Downloads.user_id == user_id).filter(ub.Downloads.book_id == book_id).first() + check = ub.session.query(ub.Downloads).filter(ub.Downloads.user_id == user_id).filter(ub.Downloads.book_id == + book_id).first() if not check: new_download = ub.Downloads(user_id=user_id, book_id=book_id) ub.session.add(new_download) ub.session.commit() + def make_mobi(book_id): - if sys.platform =="win32": + if sys.platform == "win32": kindlegen = os.path.join(config.MAIN_DIR, "vendor", u"kindlegen.exe") else: kindlegen = os.path.join(config.MAIN_DIR, "vendor", u"kindlegen") @@ -45,9 +50,17 @@ def make_mobi(book_id): file_path = os.path.join(config.DB_ROOT, book.path, data.name) if os.path.exists(file_path + u".epub"): - p = subprocess.Popen((kindlegen + " \"" + file_path + u".epub\" ").encode(sys.getfilesystemencoding()), shell=True, stdout=subprocess.PIPE, - stderr=subprocess.PIPE, stdin=subprocess.PIPE) - check = p.wait() + p = subprocess.Popen((kindlegen + " \"" + file_path + u".epub\" ").encode(sys.getfilesystemencoding()), + shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE, stdin=subprocess.PIPE) + # Poll process for new output until finished + while True: + nextline = p.stdout.readline() + if nextline == '' and p.poll() is not None: + break + if nextline != "\r\n": + app.logger.debug(nextline.strip('\r\n')) + + check = p.returncode if not check or check < 2: book.data.append(db.Data( name=book.data[0].name, @@ -64,8 +77,67 @@ def make_mobi(book_id): app.logger.error("make_mobie: epub not found: %s.epub" % file_path) return None + +class StderrLogger(object): + + buffer='' + def __init__(self): + self.logger = logging.getLogger('cps.web') + + def write(self, message): + if message=='\n': + self.logger.debug(self.buffer) + self.buffer='' + else: + self.buffer=self.buffer+message + +def send_test_mail(kindle_mail): + settings = ub.get_mail_settings() + msg = MIMEMultipart() + msg['From'] = settings["mail_from"] + 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) + + # convert MIME message to string + 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() + + mailserver = smtplib.SMTP(settings["mail_server"], settings["mail_port"],timeout) + mailserver.set_debuglevel(1) + + if int(use_ssl) == 1: + mailserver.ehlo() + mailserver.starttls() + mailserver.ehlo() + + 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 send_mail(book_id, kindle_mail): - '''Send email with attachments''' + """Send email with attachments""" is_mobi = False is_azw = False is_azw3 = False @@ -84,7 +156,7 @@ def send_mail(book_id, kindle_mail): use_ssl = settings.get('mail_use_ssl', 0) # attach files - #msg.attach(self.get_attachment(file_path)) + # msg.attach(self.get_attachment(file_path)) 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) @@ -125,8 +197,13 @@ def send_mail(book_id, kindle_mail): # send email try: - mailserver = smtplib.SMTP(settings["mail_server"],settings["mail_port"]) - mailserver.set_debuglevel(0) + timeout=600 # set timeout to 5mins + + org_stderr = smtplib.stderr + smtplib.stderr = StderrLogger() + + mailserver = smtplib.SMTP(settings["mail_server"], settings["mail_port"],timeout) + mailserver.set_debuglevel(1) if int(use_ssl) == 1: mailserver.ehlo() @@ -137,6 +214,9 @@ def send_mail(book_id, kindle_mail): 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)) @@ -145,7 +225,7 @@ def send_mail(book_id, kindle_mail): def get_attachment(file_path): - '''Get file as MIMEBase message''' + """Get file as MIMEBase message""" try: file_ = open(file_path, 'rb') @@ -163,6 +243,7 @@ def get_attachment(file_path): 'permissions?')) return None + def get_valid_filename(value, replace_whitespace=True): """ Returns the given string converted to a string that can be used for a clean @@ -178,6 +259,7 @@ def get_valid_filename(value, replace_whitespace=True): value = value.replace(u"\u00DF", "ss") return value + def get_normalized_author(value): """ Normalizes sorted author name @@ -187,13 +269,14 @@ def get_normalized_author(value): value = " ".join(value.split(", ")[::-1]) return value + def update_dir_stucture(book_id): - 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() path = os.path.join(config.DB_ROOT, book.path) 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) titledir = book.path.split(os.sep)[1] new_titledir = get_valid_filename(book.title, False) + " (" + str(book_id) + ")" @@ -208,4 +291,3 @@ def update_dir_stucture(book_id): os.renames(path, new_author_path) book.path = new_authordir + os.sep + book.path.split(os.sep)[1] db.session.commit() - diff --git a/cps/templates/email_edit.html b/cps/templates/email_edit.html index d55ee64d..518a71ab 100644 --- a/cps/templates/email_edit.html +++ b/cps/templates/email_edit.html @@ -27,7 +27,8 @@ - + + {{_('Back')}} diff --git a/cps/templates/stats.html b/cps/templates/stats.html index 81b5787e..998ed65e 100644 --- a/cps/templates/stats.html +++ b/cps/templates/stats.html @@ -1,7 +1,45 @@ {% extends "layout.html" %} {% block body %} -
-

{{bookcounter}} {{_('Books in this Library')}}

-

{{authorcounter}} {{_('Authors in this Library')}}

-
+

{{_('Linked libraries')}}

+ + + + + + + + + + + + + + + + + + + + + + + + + + +
{{_('Program library')}}{{_('Installed Version')}}
Python{{Versions['PythonVersion']}}
Kindlegen{{Versions['KindlegenVersion']}}
ImageMagick{{Versions['ImageVersion']}}
PyPDF2{{Versions['PyPdfVersion']}}
+ +

{{_('Calibre library statistics')}}

+ + + + + + + + + + + +
{{bookcounter}}{{_('Books in this Library')}}
{{authorcounter}}{{_('Authors in this Library')}}
{% endblock %} diff --git a/cps/templates/user_edit.html b/cps/templates/user_edit.html index 32fafa44..96b83209 100644 --- a/cps/templates/user_edit.html +++ b/cps/templates/user_edit.html @@ -40,7 +40,6 @@ {% endfor %} - {% if g.user and g.user.role_admin() and not profile %}
@@ -62,6 +61,8 @@
+ {% if g.user and g.user.role_admin() and not profile %} +
diff --git a/cps/translations/de/LC_MESSAGES/messages.mo b/cps/translations/de/LC_MESSAGES/messages.mo index 88d8dfd9..166abe5a 100644 Binary files a/cps/translations/de/LC_MESSAGES/messages.mo and b/cps/translations/de/LC_MESSAGES/messages.mo differ diff --git a/cps/translations/de/LC_MESSAGES/messages.po b/cps/translations/de/LC_MESSAGES/messages.po index 35e59362..1ce85918 100644 --- a/cps/translations/de/LC_MESSAGES/messages.po +++ b/cps/translations/de/LC_MESSAGES/messages.po @@ -7,7 +7,7 @@ msgid "" msgstr "" "Project-Id-Version: PROJECT VERSION\n" "Report-Msgid-Bugs-To: EMAIL@ADDRESS\n" -"POT-Creation-Date: 2016-11-12 09:44+0100\n" +"POT-Creation-Date: 2016-12-23 08:39+0100\n" "PO-Revision-Date: 2016-07-12 19:54+0200\n" "Last-Translator: FULL NAME \n" "Language: de\n" @@ -18,250 +18,272 @@ msgstr "" "Content-Transfer-Encoding: 8bit\n" "Generated-By: Babel 2.3.4\n" -#: cps/helper.py:80 cps/templates/detail.html:113 -msgid "Send to Kindle" -msgstr "An Kindle senden" +#: cps/book_formats.py:108 cps/book_formats.py:112 cps/web.py:923 +msgid "not installed" +msgstr "Nicht installiert" -#: cps/helper.py:81 +#: cps/helper.py:93 +msgid "Calibre-web test email" +msgstr "Calibre-web Test E-Mail" + +#: cps/helper.py:94 cps/helper.py:147 msgid "This email has been sent via calibre web." msgstr "Die E-Mail wurde via calibre-web versendet" -#: cps/helper.py:103 cps/helper.py:118 +#: cps/helper.py:128 cps/helper.py:216 +#, python-format +msgid "Failed to send mail: %s" +msgstr "E-Mail: %s konnte nicht gesendet werden" + +#: cps/helper.py:146 cps/templates/detail.html:113 +msgid "Send to Kindle" +msgstr "An Kindle senden" + +#: cps/helper.py:169 cps/helper.py:184 msgid "Could not find any formats suitable for sending by email" msgstr "" "Konnte keine Formate finden welche für das versenden per E-Mail geeignet " "sind" -#: cps/helper.py:112 +#: cps/helper.py:178 msgid "Could not convert epub to mobi" -msgstr "Konnte .epub nicht nach .mobi convertieren" - -#: cps/helper.py:142 -#, python-format -msgid "Failed to send mail: %s" -msgstr "E-Mail: %s konnte nicht gesendet werden" +msgstr "Konnte .epub nicht nach .mobi konvertieren" -#: cps/helper.py:162 +#: cps/helper.py:236 msgid "The requested file could not be read. Maybe wrong permissions?" msgstr "Die angeforderte Datei konnte nicht gelesen werden. Falsche Dateirechte?" -#: cps/web.py:639 +#: cps/web.py:717 msgid "Latest Books" msgstr "Letzte Bücher" -#: cps/web.py:661 +#: cps/web.py:742 msgid "Hot Books (most downloaded)" msgstr "Beliebte Bücher (die meisten Downloads)" -#: cps/templates/index.xml:41 cps/web.py:668 +#: cps/templates/index.xml:41 cps/web.py:750 msgid "Random Books" msgstr "Zufällige Bücher" -#: cps/web.py:679 +#: cps/web.py:763 msgid "Author list" msgstr "Autorenliste" -#: cps/web.py:695 +#: cps/web.py:780 #, python-format msgid "Author: %(nam)s" msgstr "Autor: %(nam)s" -#: cps/templates/index.xml:65 cps/web.py:706 +#: cps/templates/index.xml:65 cps/web.py:793 msgid "Series list" msgstr "Liste Serien" -#: cps/web.py:714 +#: cps/web.py:804 #, python-format msgid "Series: %(serie)s" msgstr "Serie: %(serie)s" -#: cps/web.py:716 cps/web.py:796 cps/web.py:914 cps/web.py:1524 +#: cps/web.py:806 cps/web.py:902 cps/web.py:1061 cps/web.py:1729 msgid "Error opening eBook. File does not exist or file is not accessible:" msgstr "" "Buch öffnen fehlgeschlagen. Datei existiert nicht, oder ist nicht " "zugänglich." -#: cps/web.py:742 +#: cps/web.py:837 msgid "Available languages" msgstr "Verfügbare Sprachen" -#: cps/web.py:754 +#: cps/web.py:852 #, python-format msgid "Language: %(name)s" msgstr "Sprache: %(name)s" -#: cps/templates/index.xml:57 cps/web.py:765 +#: cps/templates/index.xml:57 cps/web.py:865 msgid "Category list" msgstr "Kategorieliste" -#: cps/web.py:772 +#: cps/web.py:875 #, python-format msgid "Category: %(name)s" msgstr "Kategorie: %(name)s" -#: cps/web.py:810 +#: cps/web.py:931 msgid "Statistics" msgstr "Statistiken" -#: cps/web.py:898 cps/web.py:905 cps/web.py:912 +#: cps/web.py:939 +msgid "Server restarts" +msgstr "Server startet neu" + +#: cps/web.py:1037 cps/web.py:1044 cps/web.py:1051 cps/web.py:1058 msgid "Read a Book" msgstr "Lese ein Buch" -#: cps/web.py:951 cps/web.py:1179 +#: cps/web.py:1100 cps/web.py:1365 msgid "Please fill out all fields!" msgstr "Bitte alle Felder ausfüllen!" -#: cps/web.py:967 +#: cps/web.py:1116 msgid "An unknown error occured. Please try again later." msgstr "Es ist ein unbekannter Fehler aufgetreten. Bitte später erneut versuchen." -#: cps/web.py:972 +#: cps/web.py:1121 msgid "This username or email address is already in use." msgstr "Der Benutzername oder die E-Mailadresse ist in bereits in Benutzung." -#: cps/web.py:975 +#: cps/web.py:1124 msgid "register" msgstr "Registieren" -#: cps/web.py:990 +#: cps/web.py:1140 #, python-format msgid "you are now logged in as: '%(nickname)s'" msgstr "Du bist nun eingeloggt als '%(nickname)s'" -#: cps/web.py:993 +#: cps/web.py:1143 msgid "Wrong Username or Password" msgstr "Flascher Benutzername oder Passwort" -#: cps/web.py:995 +#: cps/web.py:1145 msgid "login" msgstr "Login" -#: cps/web.py:1011 +#: cps/web.py:1162 msgid "Please configure the SMTP mail settings first..." msgstr "Bitte zuerst die SMTP Mail Einstellung konfigurieren ..." -#: cps/web.py:1015 +#: cps/web.py:1166 #, python-format msgid "Book successfully send to %(kindlemail)s" msgstr "Buch erfolgreich versandt an %(kindlemail)s" -#: cps/web.py:1018 +#: cps/web.py:1170 #, python-format msgid "There was an error sending this book: %(res)s" msgstr "Beim Senden des Buchs trat ein Fehler auf: %(res)s" -#: cps/web.py:1020 +#: cps/web.py:1172 msgid "Please configure your kindle email address first..." msgstr "Bitte die Kindle E-Mail Adresse zuuerst konfigurieren..." -#: cps/web.py:1035 +#: cps/web.py:1188 #, python-format msgid "Book has been added to shelf: %(sname)s" msgstr "Das Buch wurde dem Bücherregal: %(sname)s hinzugefügt" -#: cps/web.py:1054 +#: cps/web.py:1209 #, python-format msgid "Book has been removed from shelf: %(sname)s" msgstr "Das Buch wurde aus dem Bücherregal: %(sname)s entfernt" -#: cps/web.py:1070 +#: cps/web.py:1226 #, python-format msgid "A shelf with the name '%(title)s' already exists." msgstr "Es existiert bereits ein Bücheregal mit dem Titel '%(title)s'" -#: cps/web.py:1075 +#: cps/web.py:1231 #, python-format msgid "Shelf %(title)s created" msgstr "Bücherregal %(title)s erzeugt" -#: cps/web.py:1077 +#: cps/web.py:1233 msgid "There was an error" msgstr "Es trat ein Fehler auf" -#: cps/web.py:1078 cps/web.py:1080 +#: cps/web.py:1234 cps/web.py:1236 msgid "create a shelf" msgstr "Bücherregal erzeugen" -#: cps/web.py:1096 +#: cps/web.py:1256 #, python-format msgid "successfully deleted shelf %(name)s" msgstr "Bücherregal %(name)s erfolgreich gelöscht" -#: cps/web.py:1113 +#: cps/web.py:1277 #, python-format msgid "Shelf: '%(name)s'" msgstr "Bücherregal: '%(name)s'" -#: cps/web.py:1150 +#: cps/web.py:1332 msgid "Found an existing account for this email address." msgstr "Es existiert ein Benutzerkonto für diese E-Mailadresse" -#: cps/web.py:1151 cps/web.py:1153 +#: cps/web.py:1334 cps/web.py:1337 #, python-format msgid "%(name)s's profile" msgstr "%(name)s's Profil" -#: cps/web.py:1152 +#: cps/web.py:1335 msgid "Profile updated" msgstr "Profil aktualisiert" -#: cps/web.py:1161 +#: cps/web.py:1346 msgid "User list" msgstr "Benutzerliste" -#: cps/templates/user_list.html:32 cps/web.py:1180 +#: cps/templates/user_list.html:32 cps/web.py:1366 msgid "Add new user" msgstr "Neuen Benutzer hinzufügen" -#: cps/web.py:1213 +#: cps/web.py:1399 #, python-format msgid "User '%(user)s' created" msgstr "Benutzer '%(user)s' angelegt" -#: cps/web.py:1217 +#: cps/web.py:1403 msgid "Found an existing account for this email address or nickname." msgstr "" "Es existiert ein Benutzerkonto für diese Emailadresse oder den " "Benutzernamen" -#: cps/web.py:1238 +#: cps/web.py:1426 cps/web.py:1437 msgid "Mail settings updated" msgstr "E-Mail Einstellungen aktualisiert" -#: cps/web.py:1241 +#: cps/web.py:1432 +#, python-format +msgid "Test E-Mail successfully send to %(kindlemail)s" +msgstr "Test E-Mail erfolgreich an %(kindlemail)s versendet" + +#: cps/web.py:1435 +#, python-format +msgid "There was an error sending the Test E-Mail: %(res)s" +msgstr "Fehler beim versenden der Test E-Mail: %(res)s" + +#: cps/web.py:1438 msgid "Edit mail settings" msgstr "E-Mail Einstellungen editieren" -#: cps/web.py:1263 +#: cps/web.py:1461 #, python-format msgid "User '%(nick)s' deleted" msgstr "Benutzer '%(nick)s' gelöscht" -#: cps/web.py:1318 +#: cps/web.py:1516 #, python-format msgid "User '%(nick)s' updated" msgstr "Benutzer '%(nick)s' aktualisiert" -#: cps/web.py:1321 +#: cps/web.py:1519 msgid "An unknown error occured." msgstr "Es ist ein unbekanter Fehler aufgetreten" -#: cps/web.py:1322 +#: cps/web.py:1521 #, python-format msgid "Edit User %(nick)s" msgstr "Benutzer %(nick)s bearbeiten" -#: cps/web.py:1556 +#: cps/web.py:1759 #, python-format msgid "Failed to create path %s (Permission denied)." msgstr "Fehler beim Erzeugen des Pfads %s (Zugriff verweigert)" -#: cps/web.py:1561 +#: cps/web.py:1764 #, python-format msgid "Failed to store file %s (Permission denied)." msgstr "Fehler beim speichern der Datei %s (Zugriff verweigert)" -#: cps/web.py:1566 +#: cps/web.py:1769 #, python-format msgid "Failed to delete file %s (Permission denied)." msgstr "Fehler beim Löschen von Datei %s (Zugriff verweigert)" @@ -346,14 +368,14 @@ msgstr "Nein" msgid "view book after edit" msgstr "Buch nach Bearbeitung ansehen" -#: cps/templates/edit_book.html:105 cps/templates/email_edit.html:30 -#: cps/templates/login.html:19 cps/templates/search_form.html:33 -#: cps/templates/shelf_edit.html:15 cps/templates/user_edit.html:93 +#: cps/templates/edit_book.html:105 cps/templates/login.html:19 +#: cps/templates/search_form.html:33 cps/templates/shelf_edit.html:15 +#: cps/templates/user_edit.html:94 msgid "Submit" msgstr "Abschicken" -#: cps/templates/edit_book.html:106 cps/templates/email_edit.html:31 -#: cps/templates/user_edit.html:95 +#: cps/templates/edit_book.html:106 cps/templates/email_edit.html:32 +#: cps/templates/user_edit.html:96 msgid "Back" msgstr "Zurück" @@ -381,6 +403,14 @@ msgstr "SMTP Passwort" msgid "From e-mail" msgstr "Absenderadresse" +#: cps/templates/email_edit.html:30 +msgid "Save settings" +msgstr "Einstellungen speichern" + +#: cps/templates/email_edit.html:31 +msgid "Save settings and send Test E-Mail" +msgstr "Einstellungen speichern und Test E-Mail versenden" + #: cps/templates/feed.xml:14 msgid "Next" msgstr "Nächste" @@ -511,6 +541,14 @@ msgstr "Passwort" msgid "Remember me" msgstr "Merken" +#: cps/templates/read.html:136 +msgid "Reflow text when sidebars are open." +msgstr "Text umbrechen wenn Seitenleiste geöffnet ist" + +#: cps/templates/readpdf.html:29 +msgid "PDF.js viewer" +msgstr "PDF.js Viewer" + #: cps/templates/register.html:4 msgid "Register a new account" msgstr "Neues Benutzerkonto erzeugen" @@ -551,6 +589,10 @@ msgstr "Tags ausschließen" msgid "Delete this Shelf" msgstr "Lösche dieses Bücherregal" +#: cps/templates/shelf.html:7 +msgid "Edit Shelf name" +msgstr "Bücherregal umbenennen" + #: cps/templates/shelf_edit.html:7 msgid "Title" msgstr "Titel" @@ -559,11 +601,27 @@ msgstr "Titel" msgid "should the shelf be public?" msgstr "Soll das Bücherregal öffentlich sein?" -#: cps/templates/stats.html:4 +#: cps/templates/stats.html:3 +msgid "Linked libraries" +msgstr "Dynamische Bibliotheken" + +#: cps/templates/stats.html:8 +msgid "Program library" +msgstr "Programm Bibliotheken" + +#: cps/templates/stats.html:9 +msgid "Installed Version" +msgstr "Installierte Version" + +#: cps/templates/stats.html:32 +msgid "Calibre library statistics" +msgstr "Calibre Bibliothek Statistiken" + +#: cps/templates/stats.html:37 msgid "Books in this Library" msgstr "Bücher in dieser Bibliothek" -#: cps/templates/stats.html:5 +#: cps/templates/stats.html:41 msgid "Authors in this Library" msgstr "Autoren in dieser Bibliothek" @@ -579,51 +637,51 @@ msgstr "Zeige nur Bücher mit dieser Sprache" msgid "Show all" msgstr "Zeige alle" -#: cps/templates/user_edit.html:46 +#: cps/templates/user_edit.html:45 msgid "Show random books" msgstr "Zeige Zufällige Bücher" -#: cps/templates/user_edit.html:50 +#: cps/templates/user_edit.html:49 msgid "Show hot books" msgstr "Zeige Auswahl Beliebte Bücher" -#: cps/templates/user_edit.html:54 +#: cps/templates/user_edit.html:53 msgid "Show language selection" msgstr "Zeige Sprachauswahl" -#: cps/templates/user_edit.html:58 +#: cps/templates/user_edit.html:57 msgid "Show series selection" msgstr "Zeige Auswahl Serien" -#: cps/templates/user_edit.html:62 +#: cps/templates/user_edit.html:61 msgid "Show category selection" msgstr "Zeige Kategorie Auswahl" -#: cps/templates/user_edit.html:67 +#: cps/templates/user_edit.html:68 msgid "Admin user" msgstr "Admin Benutzer" -#: cps/templates/user_edit.html:71 +#: cps/templates/user_edit.html:72 msgid "Allow Downloads" msgstr "Downloads erlauben" -#: cps/templates/user_edit.html:75 +#: cps/templates/user_edit.html:76 msgid "Allow Uploads" msgstr "Uploads erlauben" -#: cps/templates/user_edit.html:79 +#: cps/templates/user_edit.html:80 msgid "Allow Edit" msgstr "Bearbeiten erlauben" -#: cps/templates/user_edit.html:83 +#: cps/templates/user_edit.html:84 msgid "Allow Changing Password" msgstr "Passwort ändern erlauben" -#: cps/templates/user_edit.html:89 +#: cps/templates/user_edit.html:90 msgid "Delete this user" msgstr "Benutzer löschen" -#: cps/templates/user_edit.html:100 +#: cps/templates/user_edit.html:101 msgid "Recent Downloads" msgstr "Letzte Downloads" @@ -674,6 +732,3 @@ msgstr "SMTP Einstellungen ändern" msgid "Latin" msgstr "Latein" -#~ msgid "Version" -#~ msgstr "Version" - diff --git a/cps/translations/fr/LC_MESSAGES/messages.mo b/cps/translations/fr/LC_MESSAGES/messages.mo index 12c99864..83cec987 100644 Binary files a/cps/translations/fr/LC_MESSAGES/messages.mo and b/cps/translations/fr/LC_MESSAGES/messages.mo differ diff --git a/cps/translations/fr/LC_MESSAGES/messages.po b/cps/translations/fr/LC_MESSAGES/messages.po index 91428086..fa75ccdb 100644 --- a/cps/translations/fr/LC_MESSAGES/messages.po +++ b/cps/translations/fr/LC_MESSAGES/messages.po @@ -1,4 +1,4 @@ -# Translations template for PROJECT. +# French translations for PROJECT. # Copyright (C) 2016 ORGANIZATION # This file is distributed under the same license as the PROJECT project. # FIRST AUTHOR , 2016. @@ -7,256 +7,281 @@ msgid "" msgstr "" "Project-Id-Version: PROJECT VERSION\n" "Report-Msgid-Bugs-To: EMAIL@ADDRESS\n" -"POT-Creation-Date: 2016-11-13 16:45+0100\n" +"POT-Creation-Date: 2016-12-23 08:39+0100\n" "PO-Revision-Date: 2016-11-13 18:35+0100\n" +"Last-Translator: Nicolas Roudninski \n" +"Language: fr\n" "Language-Team: \n" +"Plural-Forms: nplurals=2; plural=(n > 1)\n" "MIME-Version: 1.0\n" -"Content-Type: text/plain; charset=UTF-8\n" +"Content-Type: text/plain; charset=utf-8\n" "Content-Transfer-Encoding: 8bit\n" "Generated-By: Babel 2.3.4\n" -"X-Generator: Poedit 1.8.11\n" -"Last-Translator: Nicolas Roudninski \n" -"Plural-Forms: nplurals=2; plural=(n > 1);\n" -"Language: fr_FR\n" -#: cps/helper.py:80 cps/templates/detail.html:113 -msgid "Send to Kindle" -msgstr "Envoyer ver Kindle" +#: cps/book_formats.py:108 cps/book_formats.py:112 cps/web.py:923 +msgid "not installed" +msgstr "" + +#: cps/helper.py:93 +msgid "Calibre-web test email" +msgstr "" -#: cps/helper.py:81 +#: cps/helper.py:94 cps/helper.py:147 msgid "This email has been sent via calibre web." msgstr "Ce message a été envoyé depuis calibre web." -#: cps/helper.py:103 cps/helper.py:118 +#: cps/helper.py:128 cps/helper.py:216 +#, python-format +msgid "Failed to send mail: %s" +msgstr "Impossible d'envoyer le courriel : %s" + +#: cps/helper.py:146 cps/templates/detail.html:113 +msgid "Send to Kindle" +msgstr "Envoyer ver Kindle" + +#: cps/helper.py:169 cps/helper.py:184 msgid "Could not find any formats suitable for sending by email" msgstr "Impossible de trouver un format adapté à envoyer par courriel" -#: cps/helper.py:112 +#: cps/helper.py:178 msgid "Could not convert epub to mobi" msgstr "Impossible de convertir epub vers mobi" -#: cps/helper.py:142 -#, python-format -msgid "Failed to send mail: %s" -msgstr "Impossible d'envoyer le courriel : %s" - -#: cps/helper.py:162 +#: cps/helper.py:236 msgid "The requested file could not be read. Maybe wrong permissions?" -msgstr "Le fichier demandé ne peux pas être lu. Peut-être de mauvaises permissions ?" +msgstr "" +"Le fichier demandé ne peux pas être lu. Peut-être de mauvaises " +"permissions ?" -#: cps/web.py:639 +#: cps/web.py:717 msgid "Latest Books" msgstr "Derniers livres" -#: cps/web.py:661 +#: cps/web.py:742 msgid "Hot Books (most downloaded)" msgstr "Livres populaires (les plus téléchargés)" -#: cps/templates/index.xml:41 cps/web.py:668 +#: cps/templates/index.xml:41 cps/web.py:750 msgid "Random Books" msgstr "Livres au hasard" -#: cps/web.py:679 +#: cps/web.py:763 msgid "Author list" msgstr "Liste des auteurs" -#: cps/web.py:695 +#: cps/web.py:780 #, python-format msgid "Author: %(nam)s" msgstr "Auteur : %(nam)s" -#: cps/templates/index.xml:65 cps/web.py:706 +#: cps/templates/index.xml:65 cps/web.py:793 msgid "Series list" msgstr "Liste des séries" -#: cps/web.py:714 +#: cps/web.py:804 #, python-format msgid "Series: %(serie)s" msgstr "Séries : %(serie)s" -#: cps/web.py:716 cps/web.py:796 cps/web.py:914 cps/web.py:1524 +#: cps/web.py:806 cps/web.py:902 cps/web.py:1061 cps/web.py:1729 msgid "Error opening eBook. File does not exist or file is not accessible:" -msgstr "Erreur d'ouverture du livre numérique. Le fichier n'existe pas ou n'est pas accessible :" +msgstr "" +"Erreur d'ouverture du livre numérique. Le fichier n'existe pas ou n'est " +"pas accessible :" -#: cps/web.py:742 +#: cps/web.py:837 msgid "Available languages" msgstr "Langues disponibles" -#: cps/web.py:754 +#: cps/web.py:852 #, python-format msgid "Language: %(name)s" msgstr "Langue : %(name)s" -#: cps/templates/index.xml:57 cps/web.py:765 +#: cps/templates/index.xml:57 cps/web.py:865 msgid "Category list" msgstr "Liste des catégories" -#: cps/web.py:772 +#: cps/web.py:875 #, python-format msgid "Category: %(name)s" msgstr "Catégorie : %(name)s" -#: cps/web.py:810 +#: cps/web.py:931 msgid "Statistics" msgstr "Statistiques" -#: cps/web.py:898 cps/web.py:905 cps/web.py:912 +#: cps/web.py:939 +msgid "Server restarts" +msgstr "" + +#: cps/web.py:1037 cps/web.py:1044 cps/web.py:1051 cps/web.py:1058 msgid "Read a Book" msgstr "Lire un livre" -#: cps/web.py:951 cps/web.py:1179 +#: cps/web.py:1100 cps/web.py:1365 msgid "Please fill out all fields!" msgstr "SVP, complétez tous les champs !" -#: cps/web.py:967 +#: cps/web.py:1116 msgid "An unknown error occured. Please try again later." msgstr "Une erreur a eu lieu. Merci de réessayez plus tard." -#: cps/web.py:972 +#: cps/web.py:1121 msgid "This username or email address is already in use." msgstr "Ce nom d'utilisateur ou cette adresse de courriel est déjà utilisée." -#: cps/web.py:975 +#: cps/web.py:1124 msgid "register" msgstr "S'enregistrer" -#: cps/web.py:990 +#: cps/web.py:1140 #, python-format msgid "you are now logged in as: '%(nickname)s'" msgstr "Vous êtes maintenant connecté sous : '%(nickname)s'" -#: cps/web.py:993 +#: cps/web.py:1143 msgid "Wrong Username or Password" msgstr "Mauvais nom d'utilisateur ou mot de passe" -#: cps/web.py:995 +#: cps/web.py:1145 msgid "login" msgstr "Connexion" -#: cps/web.py:1011 +#: cps/web.py:1162 msgid "Please configure the SMTP mail settings first..." msgstr "Veillez configurer les paramètres smtp d'abord..." -#: cps/web.py:1015 +#: cps/web.py:1166 #, python-format msgid "Book successfully send to %(kindlemail)s" msgstr "Livres envoyés à %(kindlemail)s avec succès" -#: cps/web.py:1018 +#: cps/web.py:1170 #, python-format msgid "There was an error sending this book: %(res)s" msgstr "Il y a eu une erreur en envoyant ce livre : %(res)s" -#: cps/web.py:1020 +#: cps/web.py:1172 msgid "Please configure your kindle email address first..." msgstr "Veuillez configurer votre adresse kindle d'abord..." -#: cps/web.py:1035 +#: cps/web.py:1188 #, python-format msgid "Book has been added to shelf: %(sname)s" msgstr "Le livre a bien été ajouté à l'étagère : %(sname)s" -#: cps/web.py:1054 +#: cps/web.py:1209 #, python-format msgid "Book has been removed from shelf: %(sname)s" msgstr "Le livre a été supprimé de l'étagère %(sname)s" -#: cps/web.py:1070 +#: cps/web.py:1226 #, python-format msgid "A shelf with the name '%(title)s' already exists." msgstr "Une étagère de ce nom '%(title)s' existe déjà." -#: cps/web.py:1075 +#: cps/web.py:1231 #, python-format msgid "Shelf %(title)s created" msgstr "Étagère %(title)s créée" -#: cps/web.py:1077 +#: cps/web.py:1233 msgid "There was an error" msgstr "Il y a eu une erreur" -#: cps/web.py:1078 cps/web.py:1080 +#: cps/web.py:1234 cps/web.py:1236 msgid "create a shelf" msgstr "Créer une étagère" -#: cps/web.py:1096 +#: cps/web.py:1256 #, python-format msgid "successfully deleted shelf %(name)s" msgstr "L'étagère %(name)s a été supprimé avec succès" -#: cps/web.py:1113 +#: cps/web.py:1277 #, python-format msgid "Shelf: '%(name)s'" msgstr "Étagère : '%(name)s'" -#: cps/web.py:1150 +#: cps/web.py:1332 msgid "Found an existing account for this email address." msgstr "Un compte avec cette adresse de courriel existe déjà." -#: cps/web.py:1151 cps/web.py:1153 +#: cps/web.py:1334 cps/web.py:1337 #, python-format msgid "%(name)s's profile" msgstr "Profil de %(name)s" -#: cps/web.py:1152 +#: cps/web.py:1335 msgid "Profile updated" msgstr "Profil mis à jour" -#: cps/web.py:1161 +#: cps/web.py:1346 msgid "User list" msgstr "Liste des ustilisateurs" -#: cps/templates/user_list.html:32 cps/web.py:1180 +#: cps/templates/user_list.html:32 cps/web.py:1366 msgid "Add new user" msgstr "Ajouter un nouvel utilisateur" -#: cps/web.py:1213 +#: cps/web.py:1399 #, python-format msgid "User '%(user)s' created" msgstr "Utilisateur '%(user)s' créé" -#: cps/web.py:1217 +#: cps/web.py:1403 msgid "Found an existing account for this email address or nickname." msgstr "Un compte avec cette adresse de courriel ou ce surnom existe déjà." -#: cps/web.py:1238 +#: cps/web.py:1426 cps/web.py:1437 msgid "Mail settings updated" msgstr "Paramètres de courriel mis à jour" -#: cps/web.py:1241 +#: cps/web.py:1432 +#, python-format +msgid "Test E-Mail successfully send to %(kindlemail)s" +msgstr "" + +#: cps/web.py:1435 +#, python-format +msgid "There was an error sending the Test E-Mail: %(res)s" +msgstr "" + +#: cps/web.py:1438 msgid "Edit mail settings" msgstr "Éditer les paramètres de courriel" -#: cps/web.py:1263 +#: cps/web.py:1461 #, python-format msgid "User '%(nick)s' deleted" msgstr "Utilisateur '%(nick)s' supprimé" -#: cps/web.py:1318 +#: cps/web.py:1516 #, python-format msgid "User '%(nick)s' updated" msgstr "Utilisateur '%(nick)s' mis à jour" -#: cps/web.py:1321 +#: cps/web.py:1519 msgid "An unknown error occured." msgstr "Oups ! Une erreur inconnue a eu lieu." -#: cps/web.py:1322 +#: cps/web.py:1521 #, python-format msgid "Edit User %(nick)s" msgstr "Éditer l'utilisateur %(nick)s" -#: cps/web.py:1556 +#: cps/web.py:1759 #, python-format msgid "Failed to create path %s (Permission denied)." msgstr "Impossible de créer le chemin %s (permission refusée)" -#: cps/web.py:1561 +#: cps/web.py:1764 #, python-format msgid "Failed to store file %s (Permission denied)." msgstr "Impossible d'enregistrer le fichier %s (permission refusée)" -#: cps/web.py:1566 +#: cps/web.py:1769 #, python-format msgid "Failed to delete file %s (Permission denied)." msgstr "Impossible de supprimer le fichier %s (permission refusée)" @@ -341,14 +366,14 @@ msgstr "Non" msgid "view book after edit" msgstr "Voir le livre après l'édition" -#: cps/templates/edit_book.html:105 cps/templates/email_edit.html:30 -#: cps/templates/login.html:19 cps/templates/search_form.html:33 -#: cps/templates/shelf_edit.html:15 cps/templates/user_edit.html:93 +#: cps/templates/edit_book.html:105 cps/templates/login.html:19 +#: cps/templates/search_form.html:33 cps/templates/shelf_edit.html:15 +#: cps/templates/user_edit.html:94 msgid "Submit" msgstr "Soumettre" -#: cps/templates/edit_book.html:106 cps/templates/email_edit.html:31 -#: cps/templates/user_edit.html:95 +#: cps/templates/edit_book.html:106 cps/templates/email_edit.html:32 +#: cps/templates/user_edit.html:96 msgid "Back" msgstr "Retour" @@ -376,6 +401,14 @@ msgstr "Mot de passe smtp" msgid "From e-mail" msgstr "Expéditeur des courriels" +#: cps/templates/email_edit.html:30 +msgid "Save settings" +msgstr "" + +#: cps/templates/email_edit.html:31 +msgid "Save settings and send Test E-Mail" +msgstr "" + #: cps/templates/feed.xml:14 msgid "Next" msgstr "Suivant" @@ -506,6 +539,14 @@ msgstr "Mot de passe" msgid "Remember me" msgstr "Se rappeler de moi" +#: cps/templates/read.html:136 +msgid "Reflow text when sidebars are open." +msgstr "" + +#: cps/templates/readpdf.html:29 +msgid "PDF.js viewer" +msgstr "" + #: cps/templates/register.html:4 msgid "Register a new account" msgstr "Enregistrer un nouveau compte" @@ -546,6 +587,10 @@ msgstr "Exclure des étiquettes" msgid "Delete this Shelf" msgstr "Effacer cette étagère" +#: cps/templates/shelf.html:7 +msgid "Edit Shelf name" +msgstr "" + #: cps/templates/shelf_edit.html:7 msgid "Title" msgstr "Titre" @@ -554,11 +599,27 @@ msgstr "Titre" msgid "should the shelf be public?" msgstr "Cette étagère doit-elle être publique ?" -#: cps/templates/stats.html:4 +#: cps/templates/stats.html:3 +msgid "Linked libraries" +msgstr "" + +#: cps/templates/stats.html:8 +msgid "Program library" +msgstr "" + +#: cps/templates/stats.html:9 +msgid "Installed Version" +msgstr "" + +#: cps/templates/stats.html:32 +msgid "Calibre library statistics" +msgstr "" + +#: cps/templates/stats.html:37 msgid "Books in this Library" msgstr "Livres dans la bibiothèque" -#: cps/templates/stats.html:5 +#: cps/templates/stats.html:41 msgid "Authors in this Library" msgstr "Auteurs dans la bibliothèque" @@ -574,51 +635,51 @@ msgstr "Montrer les livres dans la langue" msgid "Show all" msgstr "Montrer tout" -#: cps/templates/user_edit.html:46 +#: cps/templates/user_edit.html:45 msgid "Show random books" msgstr "Montrer des livres au hasard" -#: cps/templates/user_edit.html:50 +#: cps/templates/user_edit.html:49 msgid "Show hot books" msgstr "Montrer les livres populaires" -#: cps/templates/user_edit.html:54 +#: cps/templates/user_edit.html:53 msgid "Show language selection" msgstr "Montrer la sélection de la langue" -#: cps/templates/user_edit.html:58 +#: cps/templates/user_edit.html:57 msgid "Show series selection" msgstr "Montrer la sélection des séries" -#: cps/templates/user_edit.html:62 +#: cps/templates/user_edit.html:61 msgid "Show category selection" msgstr "Montrer la sélection des catégories" -#: cps/templates/user_edit.html:67 +#: cps/templates/user_edit.html:68 msgid "Admin user" msgstr "Utilisateur admin" -#: cps/templates/user_edit.html:71 +#: cps/templates/user_edit.html:72 msgid "Allow Downloads" msgstr "Permettre les téléchargements" -#: cps/templates/user_edit.html:75 +#: cps/templates/user_edit.html:76 msgid "Allow Uploads" msgstr "Permettre les téléversements" -#: cps/templates/user_edit.html:79 +#: cps/templates/user_edit.html:80 msgid "Allow Edit" msgstr "Permettre l'édition" -#: cps/templates/user_edit.html:83 +#: cps/templates/user_edit.html:84 msgid "Allow Changing Password" msgstr "Permettre le changement de mot de passe" -#: cps/templates/user_edit.html:89 +#: cps/templates/user_edit.html:90 msgid "Delete this user" msgstr "Supprimer cet utilisateur" -#: cps/templates/user_edit.html:100 +#: cps/templates/user_edit.html:101 msgid "Recent Downloads" msgstr "Téléchargements récents" @@ -668,3 +729,4 @@ msgstr "Changer les paramètre smtp" msgid "Latin" msgstr "Latin" + diff --git a/cps/ub.py b/cps/ub.py index bb046e1d..89fbd579 100644 --- a/cps/ub.py +++ b/cps/ub.py @@ -21,18 +21,19 @@ ROLE_EDIT = 8 ROLE_PASSWD = 16 DEFAULT_PASS = "admin123" + class User(Base): __tablename__ = 'user' - id = Column(Integer, primary_key = True) - nickname = Column(String(64), unique = True) - email = Column(String(120), unique = True, default = "") - role = Column(SmallInteger, default = ROLE_USER) + id = Column(Integer, primary_key=True) + nickname = Column(String(64), unique=True) + email = Column(String(120), unique=True, default="") + role = Column(SmallInteger, default=ROLE_USER) password = Column(String) kindle_mail = Column(String(120), default="") - shelf = relationship('Shelf', backref = 'user', lazy = 'dynamic') - whislist = relationship('Whislist', backref = 'user', lazy = 'dynamic') - downloads = relationship('Downloads', backref= 'user', lazy = 'dynamic') + shelf = relationship('Shelf', backref='user', lazy='dynamic') + whislist = relationship('Whislist', 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) @@ -43,26 +44,31 @@ class User(Base): def is_authenticated(self): return True + def role_admin(self): if self.role is not None: return True if self.role & ROLE_ADMIN == ROLE_ADMIN else False else: return False + def role_download(self): if self.role is not None: return True if self.role & ROLE_DOWNLOAD == ROLE_DOWNLOAD else False else: return False + def role_upload(self): if self.role is not None: return True if self.role & ROLE_UPLOAD == ROLE_UPLOAD else False else: return False + def role_edit(self): if self.role is not None: return True if self.role & ROLE_EDIT == ROLE_EDIT else False else: return False + def role_passwd(self): if self.role is not None: return True if self.role & ROLE_PASSWD == ROLE_PASSWD else False @@ -96,20 +102,20 @@ class User(Base): def show_category(self): return self.category_books - def __repr__(self): - return '' % (self.nickname) + return '' % self.nickname + class Shelf(Base): __tablename__ = 'shelf' - id = Column(Integer, primary_key = True) + id = Column(Integer, primary_key=True) name = Column(String) is_public = Column(Integer, default=0) user_id = Column(Integer, ForeignKey('user.id')) def __repr__(self): - return '' % (self.name) + return '' % self.name class Whislist(Base): @@ -124,7 +130,7 @@ class Whislist(Base): pass def __repr__(self): - return '' % (self.name) + return '' % self.name class BookShelf(Base): @@ -135,7 +141,7 @@ class BookShelf(Base): shelf = Column(Integer, ForeignKey('shelf.id')) def __repr__(self): - return '' % (self.id) + return '' % self.id class Downloads(Base): @@ -146,7 +152,8 @@ class Downloads(Base): user_id = Column(Integer, ForeignKey('user.id')) def __repr__(self): - return '' % (self.title) + return '' % self.title + class Settings(Base): __tablename__ = 'settings' @@ -174,12 +182,13 @@ class Settings(Base): #return '' % (self.mail_server) pass + def migrate_Database(): try: session.query(exists().where(User.random_books)).scalar() session.commit() - except exc.OperationalError: # Database is not compatible, some rows are missing - conn=engine.connect() + 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'") @@ -208,23 +217,25 @@ def create_default_config(): session.add(settings) session.commit() + def get_mail_settings(): settings = session.query(Settings).first() if not settings: - return {} + return {} data = { - 'mail_server': settings.mail_server, - 'mail_port': settings.mail_port, - 'mail_use_ssl': settings.mail_use_ssl, - 'mail_login': settings.mail_login, - 'mail_password': settings.mail_password, - 'mail_from': settings.mail_from + 'mail_server': settings.mail_server, + 'mail_port': settings.mail_port, + 'mail_use_ssl': settings.mail_use_ssl, + 'mail_login': settings.mail_login, + 'mail_password': settings.mail_password, + 'mail_from': settings.mail_from } return data + def create_admin_user(): user = User() user.nickname = "admin" @@ -251,4 +262,3 @@ if not os.path.exists(dbpath): pass else: migrate_Database() - diff --git a/cps/uploader.py b/cps/uploader.py index df85befd..0da801fa 100644 --- a/cps/uploader.py +++ b/cps/uploader.py @@ -9,6 +9,8 @@ BookMeta = namedtuple('BookMeta', 'file_path, extension, title, author, cover, d """ :rtype: BookMeta """ + + def upload(file): tmp_dir = os.path.join(gettempdir(), 'calibre_web') @@ -23,7 +25,3 @@ def upload(file): file.save(tmp_file_path) meta = book_formats.process(tmp_file_path, filename_root, file_extension) return meta - - - - diff --git a/cps/web.py b/cps/web.py index 349fac19..501c1ab2 100755 --- a/cps/web.py +++ b/cps/web.py @@ -5,8 +5,8 @@ import mimetypes import logging from logging.handlers import RotatingFileHandler import textwrap -mimetypes.add_type('application/xhtml+xml','.xhtml') -from flask import Flask, render_template, session, request, Response, redirect, url_for, send_from_directory, make_response, g, flash, abort +from flask import Flask, render_template, session, request, Response, redirect, url_for, send_from_directory, \ + make_response, g, flash, abort import db, config, ub, helper import os import errno @@ -31,10 +31,15 @@ import datetime from iso639 import languages as isoLanguages from uuid import uuid4 import os.path +import sys +import subprocess import shutil import re +from shutil import move + try: from wand.image import Image + use_generic_pdf_cover = False except ImportError, e: use_generic_pdf_cover = True @@ -42,8 +47,11 @@ except ImportError, e: from shutil import copyfile from cgi import escape +mimetypes.add_type('application/xhtml+xml', '.xhtml') + + 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 this to a URL other than / and to an HTTP scheme that is different than what is used locally. @@ -58,7 +66,8 @@ class ReverseProxied(object): proxy_set_header X-Scheme $scheme; proxy_set_header X-Script-Name /myprefix; } - ''' + """ + def __init__(self, app): self.app = app @@ -78,49 +87,69 @@ class ReverseProxied(object): environ['HTTP_HOST'] = server return self.app(environ, start_response) + 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=10000, backupCount=1) -file_handler.setLevel(logging.INFO) +file_handler = RotatingFileHandler(os.path.join(config.LOG_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.info('Starting Calibre Web...') logging.getLogger("book_formats").addHandler(file_handler) logging.getLogger("book_formats").setLevel(logging.INFO) - Principal(app) babel = Babel(app) +import uploader + +global global_queue +global_queue = None + class Anonymous(AnonymousUserMixin): def __init__(self): self.nickname = 'Guest' self.role = -1 + def role_admin(self): return False + def role_download(self): return False + def role_upload(self): return False + def role_edit(self): return False + def filter_language(self): return 'all' + def show_random_books(self): return True + def show_hot_books(self): return True + def show_series(self): return True + def show_category(self): return True + def show_language(self): return True + def is_anonymous(self): return config.ANON_BROWSE @@ -138,21 +167,24 @@ LANGUAGES = { 'fr': 'Français' } + @babel.localeselector def get_locale(): # if a user is logged in, use the locale from the user settings user = getattr(g, 'user', None) if user is not None and hasattr(user, "locale"): - return user.locale + return user.locale preferred = [x.replace('-', '_') for x in request.accept_languages.values()] return negotiate_locale(preferred, LANGUAGES.keys()) + @babel.timezoneselector def get_timezone(): user = getattr(g, 'user', None) if user is not None: return user.timezone + @lm.user_loader def load_user(id): return ub.session.query(ub.User).filter(ub.User.id == int(id)).first() @@ -173,6 +205,7 @@ def load_user_from_header(header_val): return user return + def check_auth(username, password): user = ub.session.query(ub.User).filter(ub.User.nickname == username).first() if user and check_password_hash(user.password, password): @@ -180,11 +213,13 @@ def check_auth(username, password): else: return False + def authenticate(): return Response( - 'Could not verify your access level for that URL.\n' - 'You have to login with proper credentials', 401, - {'WWW-Authenticate': 'Basic realm="Login Required"'}) + 'Could not verify your access level for that URL.\n' + 'You have to login with proper credentials', 401, + {'WWW-Authenticate': 'Basic realm="Login Required"'}) + def requires_basic_auth_if_no_ano(f): @wraps(f) @@ -194,11 +229,12 @@ def requires_basic_auth_if_no_ano(f): if not auth or not check_auth(auth.username, auth.password): return authenticate() return f(*args, **kwargs) + return decorated -#simple pagination for the feed -class Pagination(object): +# simple pagination for the feed +class Pagination(object): def __init__(self, page, per_page, total_count): self.page = page self.per_page = per_page @@ -221,88 +257,104 @@ class Pagination(object): 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: + (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 last = num -##pagination links in jinja + +# pagination links in jinja def url_for_other_page(page): args = request.view_args.copy() args['page'] = page return url_for(request.endpoint, **args) + app.jinja_env.globals['url_for_other_page'] = url_for_other_page + def login_required_if_no_ano(func): if config.ANON_BROWSE == 1: return func return login_required(func) -## custom jinja filters + +# custom jinja filters @app.template_filter('shortentitle') def shortentitle_filter(s): if len(s) > 60: s = s.split(':', 1)[0] if len(s) > 60: - s = textwrap.wrap(s, 60, break_long_words=False)[0]+' [...]' + s = textwrap.wrap(s, 60, break_long_words=False)[0] + ' [...]' return s + 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 download_required(f): @wraps(f) def inner(*args, **kwargs): if current_user.role_download() or current_user.role_admin(): return f(*args, **kwargs) abort(403) + return inner + + def upload_required(f): @wraps(f) def inner(*args, **kwargs): if current_user.role_upload() or current_user.role_admin(): return f(*args, **kwargs) abort(403) + return inner + + def edit_required(f): @wraps(f) def inner(*args, **kwargs): if current_user.role_edit() or current_user.role_admin(): return f(*args, **kwargs) abort(403) + return inner + # Fill indexpage with all requested data from database -def fill_indexpage(page,database, db_filter, order) : +def fill_indexpage(page, database, db_filter, order): if current_user.filter_language() != "all": filter = db.Books.languages.any(db.Languages.lang_code == current_user.filter_language()) else: - filter= True + filter = True if current_user.show_random_books(): random = db.session.query(db.Books).filter(filter).order_by(func.random()).limit(config.RANDOM_BOOKS) - else : + else: random = false - off = int(int(config.NEWEST_BOOKS) * (page-1)) - pagination = Pagination(page, config.NEWEST_BOOKS, 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(config.NEWEST_BOOKS) + off = int(int(config.NEWEST_BOOKS) * (page - 1)) + pagination = Pagination(page, config.NEWEST_BOOKS, + 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( + config.NEWEST_BOOKS) return entries, random, pagination - - -def modify_database_object(input_elements, db_book_object, db_object, db_session,type): +def modify_database_object(input_elements, db_book_object, db_object, db_session, type): input_elements = [x for x in input_elements if x != ''] # we have all input element (authors, series, tags) names now # 1. search for elements to remove @@ -312,7 +364,7 @@ def modify_database_object(input_elements, db_book_object, db_object, db_session for inp_element in input_elements: if inp_element == c_elements.name: found = True - break; + break # if the element was not found in the new list, add it to remove list if not found: del_elements.append(c_elements) @@ -323,7 +375,7 @@ def modify_database_object(input_elements, db_book_object, db_object, db_session for c_elements in db_book_object: if inp_element == c_elements.name: found = True - break; + break if not found: add_elements.append(inp_element) # if there are elements to remove, we remove them now @@ -335,20 +387,20 @@ def modify_database_object(input_elements, db_book_object, db_object, db_session # if there are elements to add, we add them now! if len(add_elements) > 0: if type == 'languages': - db_filter=db_object.lang_code + db_filter = db_object.lang_code else: - db_filter=db_object.name + db_filter = db_object.name for add_element in add_elements: # check if a element with that name exists new_element = db_session.query(db_object).filter(db_filter == add_element).first() # if no element is found add it - if new_element == None: - if type=='author': + if new_element is None: + if type == 'author': new_element = db_object(add_element, add_element, "") else: - if type=='series': + if type == 'series': new_element = db_object(add_element, add_element) - else: # type should be tag, or languages + else: # type should be tag, or languages new_element = db_object(add_element) db_session.add(new_element) new_element = db.session.query(db_object).filter(db_filter == add_element).first() @@ -363,6 +415,7 @@ def before_request(): g.allow_registration = config.PUBLIC_REG g.allow_upload = config.UPLOADING + @app.route("/feed") @requires_basic_auth_if_no_ano def feed_index(): @@ -371,18 +424,20 @@ def feed_index(): else: filter = True xml = render_template('index.xml') - response= make_response(xml) + response = make_response(xml) response.headers["Content-Type"] = "application/xml" return response + @app.route("/feed/osd") @requires_basic_auth_if_no_ano def feed_osd(): xml = render_template('osd.xml') - response= make_response(xml) + response = make_response(xml) response.headers["Content-Type"] = "application/xml" return response + @app.route("/feed/search", methods=["GET"]) @requires_basic_auth_if_no_ano def feed_search(): @@ -392,15 +447,18 @@ def feed_search(): else: filter = True if term: - entries = db.session.query(db.Books).filter(db.or_(db.Books.tags.any(db.Tags.name.like("%"+term+"%")),db.Books.authors.any(db.Authors.name.like("%"+term+"%")),db.Books.title.like("%"+term+"%"))).filter(filter).all() + entries = db.session.query(db.Books).filter(db.or_(db.Books.tags.any(db.Tags.name.like("%" + term + "%")), + db.Books.authors.any(db.Authors.name.like("%" + term + "%")), + db.Books.title.like("%" + term + "%"))).filter(filter).all() xml = render_template('feed.xml', searchterm=term, entries=entries, Last_Updated=Last_Updated) else: xml = render_template('feed.xml', searchterm="") - response= make_response(xml) + response = make_response(xml) response.headers["Content-Type"] = "application/xml" return response + @app.route("/feed/new") @requires_basic_auth_if_no_ano def feed_new(): @@ -408,12 +466,14 @@ def feed_new(): if current_user.filter_language() != "all": filter = db.Books.languages.any(db.Languages.lang_code == current_user.filter_language()) else: - filter= True + filter = True if not off: - off=0 - entries = db.session.query(db.Books).filter(filter).order_by(db.Books.last_modified.desc()).offset(off).limit(config.NEWEST_BOOKS) - xml = render_template('feed.xml', entries=entries, next_url="/feed/new?start_index=%d" % (int(config.NEWEST_BOOKS) + int(off))) - response= make_response(xml) + off = 0 + entries = db.session.query(db.Books).filter(filter).order_by(db.Books.timestamp.desc()).offset(off).limit( + config.NEWEST_BOOKS) + xml = render_template('feed.xml', entries=entries, + next_url="/feed/new?start_index=%d" % (int(config.NEWEST_BOOKS) + int(off))) + response = make_response(xml) response.headers["Content-Type"] = "application/xml" return response @@ -427,13 +487,15 @@ def feed_discover(): else: filter = True if not off: - off=0 + off = 0 entries = db.session.query(db.Books).filter(filter).order_by(func.random()).offset(off).limit(config.NEWEST_BOOKS) - xml = render_template('feed.xml', entries=entries, next_url="/feed/discover?start_index=%d" % (int(config.NEWEST_BOOKS) + int(off))) + xml = render_template('feed.xml', entries=entries, + next_url="/feed/discover?start_index=%d" % (int(config.NEWEST_BOOKS) + int(off))) response = make_response(xml) response.headers["Content-Type"] = "application/xml" return response + @app.route("/feed/hot") @requires_basic_auth_if_no_ano def feed_hot(): @@ -443,14 +505,17 @@ def feed_hot(): else: filter = True if not off: - off=0 - entries = db.session.query(db.Books).filter(filter).filter(db.Books.ratings.any(db.Ratings.rating > 9)).offset(off).limit(config.NEWEST_BOOKS) + off = 0 + entries = db.session.query(db.Books).filter(filter).filter(db.Books.ratings.any(db.Ratings.rating > 9)).offset( + off).limit(config.NEWEST_BOOKS) - xml = render_template('feed.xml', entries=entries, next_url="/feed/hot?start_index=%d" % (int(config.NEWEST_BOOKS) + int(off))) - response= make_response(xml) + xml = render_template('feed.xml', entries=entries, + next_url="/feed/hot?start_index=%d" % (int(config.NEWEST_BOOKS) + int(off))) + response = make_response(xml) response.headers["Content-Type"] = "application/xml" return response + @app.route("/feed/author") @requires_basic_auth_if_no_ano def feed_authorindex(): @@ -460,13 +525,15 @@ def feed_authorindex(): else: filter = True if not off: - off=0 + off = 0 authors = db.session.query(db.Authors).order_by(db.Authors.sort).offset(off).limit(config.NEWEST_BOOKS) - xml = render_template('feed.xml', authors=authors,next_url="/feed/author?start_index=%d" % (int(config.NEWEST_BOOKS) + int(off))) - response= make_response(xml) + xml = render_template('feed.xml', authors=authors, + next_url="/feed/author?start_index=%d" % (int(config.NEWEST_BOOKS) + int(off))) + response = make_response(xml) response.headers["Content-Type"] = "application/xml" return response + @app.route("/feed/author/") @requires_basic_auth_if_no_ano def feed_author(name): @@ -476,11 +543,12 @@ def feed_author(name): else: filter = True if not off: - off=0 + off = 0 entries = db.session.query(db.Books).filter(db.Books.authors.any(db.Authors.name.like("%" + name + "%"))).filter( - filter).offset(off).limit(config.NEWEST_BOOKS) - xml = render_template('feed.xml', entries=entries,next_url="/feed/author?start_index=%d" % (int(config.NEWEST_BOOKS) + int(off))) - response= make_response(xml) + filter).offset(off).limit(config.NEWEST_BOOKS) + xml = render_template('feed.xml', entries=entries, + next_url="/feed/author?start_index=%d" % (int(config.NEWEST_BOOKS) + int(off))) + response = make_response(xml) response.headers["Content-Type"] = "application/xml" return response @@ -490,10 +558,11 @@ def feed_author(name): def feed_categoryindex(): off = request.args.get("start_index") if not off: - off=0 + off = 0 entries = db.session.query(db.Tags).order_by(db.Tags.name).offset(off).limit(config.NEWEST_BOOKS) - xml = render_template('feed.xml', categorys=entries, next_url="/feed/category?start_index=%d" % (int(config.NEWEST_BOOKS) + int(off))) - response= make_response(xml) + xml = render_template('feed.xml', categorys=entries, + next_url="/feed/category?start_index=%d" % (int(config.NEWEST_BOOKS) + int(off))) + response = make_response(xml) response.headers["Content-Type"] = "application/xml" return response @@ -507,23 +576,26 @@ def feed_category(name): else: filter = True if not off: - off=0 + off = 0 entries = db.session.query(db.Books).filter(db.Books.tags.any(db.Tags.name.like("%" + name + "%"))).order_by( - db.Books.last_modified.desc()).filter(filter).offset(off).limit(config.NEWEST_BOOKS) - xml = render_template('feed.xml', entries=entries, next_url="/feed/category?start_index=%d" % (int(config.NEWEST_BOOKS) + int(off))) - response= make_response(xml) + db.Books.timestamp.desc()).filter(filter).offset(off).limit(config.NEWEST_BOOKS) + xml = render_template('feed.xml', entries=entries, + next_url="/feed/category?start_index=%d" % (int(config.NEWEST_BOOKS) + int(off))) + response = make_response(xml) response.headers["Content-Type"] = "application/xml" return response + @app.route("/feed/series") @requires_basic_auth_if_no_ano def feed_seriesindex(): off = request.args.get("start_index") if not off: - off=0 + off = 0 entries = db.session.query(db.Series).order_by(db.Series.name).offset(off).limit(config.NEWEST_BOOKS) - xml = render_template('feed.xml', series=entries, next_url="/feed/series?start_index=%d" % (int(config.NEWEST_BOOKS) + int(off))) - response= make_response(xml) + xml = render_template('feed.xml', series=entries, + next_url="/feed/series?start_index=%d" % (int(config.NEWEST_BOOKS) + int(off))) + response = make_response(xml) response.headers["Content-Type"] = "application/xml" return response @@ -537,11 +609,12 @@ def feed_series(name): else: filter = True if not off: - off=0 + off = 0 entries = db.session.query(db.Books).filter(db.Books.series.any(db.Series.name.like("%" + name + "%"))).order_by( - db.Books.last_modified.desc()).filter(filter).offset(off).limit(config.NEWEST_BOOKS) - xml = render_template('feed.xml', entries=entries, next_url="/feed/series?start_index=%d" % (int(config.NEWEST_BOOKS) + int(off))) - response= make_response(xml) + db.Books.timestamp.desc()).filter(filter).offset(off).limit(config.NEWEST_BOOKS) + xml = render_template('feed.xml', entries=entries, + next_url="/feed/series?start_index=%d" % (int(config.NEWEST_BOOKS) + int(off))) + response = make_response(xml) response.headers["Content-Type"] = "application/xml" return response @@ -557,31 +630,34 @@ def get_opds_download_link(book_id, format): author = helper.get_normalized_author(book.author_sort) file_name = book.title if len(author) > 0: - file_name = author+'-'+file_name + file_name = author + '-' + file_name file_name = helper.get_valid_filename(file_name) - response = make_response(send_from_directory(os.path.join(config.DB_ROOT, book.path), data.name + "." +format)) + response = make_response(send_from_directory(os.path.join(config.DB_ROOT, book.path), data.name + "." + format)) response.headers["Content-Disposition"] = "attachment; filename=%s.%s" % (data.name, format) return response - -@app.route("/get_authors_json", methods = ['GET', 'POST']) + + +@app.route("/get_authors_json", methods=['GET', 'POST']) @login_required_if_no_ano -def get_authors_json(): +def get_authors_json(): if request.method == "GET": query = request.args.get('q') entries = db.session.execute("select name from authors where name like '%" + query + "%'") json_dumps = json.dumps([dict(r) for r in entries]) return json_dumps -@app.route("/get_tags_json", methods = ['GET', 'POST']) + +@app.route("/get_tags_json", methods=['GET', 'POST']) @login_required_if_no_ano -def get_tags_json(): +def get_tags_json(): if request.method == "GET": query = request.args.get('q') entries = db.session.execute("select name from tags where name like '%" + query + "%'") json_dumps = json.dumps([dict(r) for r in entries]) return json_dumps -@app.route("/get_languages_json", methods = ['GET', 'POST']) + +@app.route("/get_languages_json", methods=['GET', 'POST']) @login_required_if_no_ano def get_languages_json(): if request.method == "GET": @@ -593,24 +669,26 @@ def get_languages_json(): cur_l = LC.parse(lang.lang_code) lang.name = cur_l.get_language_name(get_locale()) except: - lang.name=_(isoLanguages.get(part3=lang.lang_code).name) + lang.name = _(isoLanguages.get(part3=lang.lang_code).name) entries = [s for s in languages if query in s.name.lower()] json_dumps = json.dumps([dict(name=r.name) for r in entries]) return json_dumps -@app.route("/get_series_json", methods = ['GET', 'POST']) + +@app.route("/get_series_json", methods=['GET', 'POST']) @login_required_if_no_ano -def get_series_json(): +def get_series_json(): if request.method == "GET": query = request.args.get('q') entries = db.session.execute("select name from series where name like '%" + query + "%'") json_dumps = json.dumps([dict(r) for r in entries]) return json_dumps - -@app.route("/get_matching_tags", methods = ['GET', 'POST']) + + +@app.route("/get_matching_tags", methods=['GET', 'POST']) @login_required_if_no_ano -def get_matching_tags(): +def get_matching_tags(): tag_dict = {'tags': []} if request.method == "GET": q = db.session.query(db.Books) @@ -618,7 +696,8 @@ def get_matching_tags(): title_input = request.args.get('book_title') include_tag_inputs = request.args.getlist('include_tag') exclude_tag_inputs = request.args.getlist('exclude_tag') - q = q.filter(db.Books.authors.any(db.Authors.name.like("%" + author_input + "%")), db.Books.title.like("%"+title_input+"%")) + q = q.filter(db.Books.authors.any(db.Authors.name.like("%" + author_input + "%")), + db.Books.title.like("%" + title_input + "%")) if len(include_tag_inputs) > 0: for tag in include_tag_inputs: q = q.filter(db.Books.tags.any(db.Tags.id == tag)) @@ -632,12 +711,15 @@ def get_matching_tags(): json_dumps = json.dumps(tag_dict) return json_dumps + @app.route("/", defaults={'page': 1}) @app.route('/page/') @login_required_if_no_ano def index(page): - entries, random, pagination =fill_indexpage(page,db.Books,True, db.Books.last_modified.desc()) - return render_template('index.html', random=random, entries=entries, pagination=pagination, title=_(u"Latest Books")) + entries, random, pagination = fill_indexpage(page, db.Books, True, db.Books.timestamp.desc()) + return render_template('index.html', random=random, entries=entries, pagination=pagination, + title=_(u"Latest Books")) + @app.route("/hot", defaults={'page': 1}) @app.route('/hot/page/') @@ -649,25 +731,29 @@ def hot_books(page): filter = True if current_user.show_random_books(): random = db.session.query(db.Books).filter(filter).order_by(func.random()).limit(config.RANDOM_BOOKS) - else : + else: random = false off = int(int(config.NEWEST_BOOKS) * (page - 1)) - 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) + 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) hot_books = all_books.offset(off).limit(config.NEWEST_BOOKS) entries = list() for book in hot_books: entries.append(db.session.query(db.Books).filter(filter).filter(db.Books.id == book.Downloads.book_id).first()) numBooks = entries.__len__() pagination = Pagination(page, config.NEWEST_BOOKS, numBooks) - return render_template('index.html', random=random, entries=entries, pagination=pagination, title=_(u"Hot Books (most downloaded)")) + return render_template('index.html', random=random, entries=entries, pagination=pagination, + 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.last_modified.desc()) + entries, random, pagination = fill_indexpage(page, db.Books, func.randomblob(2), db.Books.timestamp.desc()) return render_template('discover.html', entries=entries, pagination=pagination, title=_(u"Random Books")) + @app.route("/author") @login_required_if_no_ano def author_list(): @@ -675,7 +761,8 @@ def author_list(): filter = db.Books.languages.any(db.Languages.lang_code == current_user.filter_language()) else: filter = True - entries= db.session.query(db.Authors,func.count('books_authors_link.book').label('count')).join(db.books_authors_link).join(db.Books).filter( + entries = db.session.query(db.Authors, func.count('books_authors_link.book').label('count')).join( + db.books_authors_link).join(db.Books).filter( filter).group_by('books_authors_link.author').order_by(db.Authors.sort).all() return render_template('list.html', entries=entries, folder='author', title=_(u"Author list")) @@ -692,8 +779,10 @@ def author(name): else: random = false - entries = db.session.query(db.Books).filter(db.Books.authors.any(db.Authors.name.like("%" + name + "%"))).filter(filter).all() - return render_template('index.html', random=random, entries=entries, title=_(u"Author: %(nam)s",nam=name)) + entries = db.session.query(db.Books).filter(db.Books.authors.any(db.Authors.name.like("%" + name + "%"))).filter( + filter).all() + return render_template('index.html', random=random, entries=entries, title=_(u"Author: %(nam)s", nam=name)) + @app.route("/series") @login_required_if_no_ano @@ -702,21 +791,26 @@ def series_list(): filter = db.Books.languages.any(db.Languages.lang_code == current_user.filter_language()) else: filter = True - entries= db.session.query(db.Series,func.count('books_series_link.book').label('count')).join(db.books_series_link).join(db.Books).filter( + entries = db.session.query(db.Series, func.count('books_series_link.book').label('count')).join( + db.books_series_link).join(db.Books).filter( filter).group_by('books_series_link.series').order_by(db.Series.sort).all() return render_template('list.html', entries=entries, folder='series', title=_(u"Series list")) + @app.route("/series//", defaults={'page': 1}) @app.route("/series//'") @login_required_if_no_ano -def series(name,page): - entries, random, pagination = fill_indexpage(page, db.Books, db.Books.series.any(db.Series.name == name ), db.Books.series_index) - if entries : - return render_template('index.html', random=random, pagination=pagination, entries=entries, title=_(u"Series: %(serie)s",serie=name)) - else : +def series(name, page): + entries, random, pagination = fill_indexpage(page, db.Books, db.Books.series.any(db.Series.name == name), + db.Books.series_index) + if entries: + return render_template('index.html', random=random, pagination=pagination, entries=entries, + 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('/' or url_for("index", _external=True)) + @app.route("/language") @login_required_if_no_ano def language_overview(): @@ -727,32 +821,40 @@ def language_overview(): cur_l = LC.parse(lang.lang_code) lang.name = cur_l.get_language_name(get_locale()) except: - lang.name=_(isoLanguages.get(part3=lang.lang_code).name) - else : + lang.name = _(isoLanguages.get(part3=lang.lang_code).name) + else: try: - langfound=1 + langfound = 1 cur_l = LC.parse(current_user.filter_language()) except: - langfound=0 - languages = db.session.query(db.Languages).filter(db.Languages.lang_code == current_user.filter_language()).all() + langfound = 0 + languages = db.session.query(db.Languages).filter( + db.Languages.lang_code == current_user.filter_language()).all() if langfound: languages[0].name = cur_l.get_language_name(get_locale()) - else : - languages[0].name=_(isoLanguages.get(part3=languages[0].lang_code).name) - lang_counter =db.session.query(db.books_languages_link,func.count('books_languages_link.book').label('bookcount')).group_by('books_languages_link.lang_code').all() - return render_template('languages.html', languages=languages, lang_counter=lang_counter, title=_(u"Available languages")) + else: + languages[0].name = _(isoLanguages.get(part3=languages[0].lang_code).name) + lang_counter = db.session.query(db.books_languages_link, + func.count('books_languages_link.book').label('bookcount')).group_by( + 'books_languages_link.lang_code').all() + return render_template('languages.html', languages=languages, lang_counter=lang_counter, + title=_(u"Available languages")) + -@app.route("/language/",defaults={'page': 1}) +@app.route("/language/", defaults={'page': 1}) @app.route('/language//page/') @login_required_if_no_ano -def language(name,page): - entries, random, pagination = fill_indexpage(page, db.Books, db.Books.languages.any(db.Languages.lang_code == name ), db.Books.last_modified.desc()) +def language(name, page): + entries, random, pagination = fill_indexpage(page, db.Books, db.Books.languages.any(db.Languages.lang_code == name), + db.Books.timestamp.desc()) try: cur_l = LC.parse(name) name = cur_l.get_language_name(get_locale()) except: name = _(isoLanguages.get(part3=name).name) - return render_template('index.html', random=random, entries=entries, pagination=pagination, title=_(u"Language: %(name)s", name=name)) + return render_template('index.html', random=random, entries=entries, pagination=pagination, + title=_(u"Language: %(name)s", name=name)) + @app.route("/category") @login_required_if_no_ano @@ -761,16 +863,21 @@ def category_list(): filter = db.Books.languages.any(db.Languages.lang_code == current_user.filter_language()) else: filter = True - entries= db.session.query(db.Tags,func.count('books_tags_link.book').label('count')).join(db.books_tags_link).join(db.Books).filter( + entries = db.session.query(db.Tags, func.count('books_tags_link.book').label('count')).join( + db.books_tags_link).join(db.Books).filter( filter).group_by('books_tags_link.tag').all() return render_template('list.html', entries=entries, folder='category', title=_(u"Category list")) -@app.route("/category/",defaults={'page': 1}) + +@app.route("/category/", defaults={'page': 1}) @app.route('/category//') @login_required_if_no_ano -def category(name,page): - entries, random, pagination = fill_indexpage(page, db.Books, db.Books.tags.any(db.Tags.name == name ), db.Books.last_modified.desc()) - return render_template('index.html', random=random, entries=entries, pagination=pagination, title=_(u"Category: %(name)s",name=name)) +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_template('index.html', random=random, entries=entries, pagination=pagination, + title=_(u"Category: %(name)s", name=name)) + @app.route("/book/") @login_required_if_no_ano @@ -780,35 +887,62 @@ def show_book(id): else: filter = True entries = db.session.query(db.Books).filter(db.Books.id == id).filter(filter).first() - if entries : - for index in range(0,len(entries.languages)) : + if entries: + for index in range(0, len(entries.languages)): try: - entries.languages[index].language_name=LC.parse(entries.languages[index].lang_code).get_language_name(get_locale()) + entries.languages[index].language_name = LC.parse(entries.languages[index].lang_code).get_language_name( + get_locale()) except: - entries.languages[index].language_name = _(isoLanguages.get(part3=entries.languages[index].lang_code).name) + entries.languages[index].language_name = _( + isoLanguages.get(part3=entries.languages[index].lang_code).name) cc = db.session.query(db.Custom_Columns).filter(db.Custom_Columns.datatype.notin_(db.cc_exceptions)).all() book_in_shelfs = [] shelfs = ub.session.query(ub.BookShelf).filter(ub.BookShelf.book_id == id).all() for entry in shelfs: book_in_shelfs.append(entry.shelf) - return render_template('detail.html', entry=entries, cc=cc, title=entries.title, books_shelfs=book_in_shelfs) - else : + return render_template('detail.html', entry=entries, cc=cc, title=entries.title, books_shelfs=book_in_shelfs) + else: flash(_(u"Error opening eBook. File does not exist or file is not accessible:"), category="error") return redirect('/' or url_for("index", _external=True)) + @app.route("/admin/") @login_required def admin(): - #return "Admin ONLY!" + # return "Admin ONLY!" abort(403) + @app.route("/stats") @login_required def stats(): counter = len(db.session.query(db.Books).all()) authors = len(db.session.query(db.Authors).all()) - return render_template('stats.html', bookcounter=counter, authorcounter=authors, title=_(u"Statistics")) + Versions=uploader.book_formats.get_versions() + if sys.platform == "win32": + kindlegen = os.path.join(config.MAIN_DIR, "vendor", u"kindlegen.exe") + else: + kindlegen = os.path.join(config.MAIN_DIR, "vendor", u"kindlegen") + kindlegen_version=_('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() + for lines in p.stdout.readlines(): + if re.search('Amazon kindlegen\(', lines): + Versions['KindlegenVersion'] = lines + Versions['PythonVersion']=sys.version + return render_template('stats.html', bookcounter=counter, authorcounter=authors, Versions=Versions, title=_(u"Statistics")) + + +@app.route("/shutdown") +def shutdown(): + logout_user() + # add restart command to queue + global_queue.put("something") + flash(_(u"Server restarts"), category="info") + return redirect('/' or url_for("index", _external=True)) + @app.route("/search", methods=["GET"]) @login_required_if_no_ano @@ -819,11 +953,15 @@ def search(): filter = db.Books.languages.any(db.Languages.lang_code == current_user.filter_language()) else: filter = True - entries = db.session.query(db.Books).filter(db.or_(db.Books.tags.any(db.Tags.name.like("%"+term+"%")),db.Books.series.any(db.Series.name.like("%"+term+"%")),db.Books.authors.any(db.Authors.name.like("%"+term+"%")),db.Books.title.like("%"+term+"%"))).filter(filter).all() + entries = db.session.query(db.Books).filter(db.or_(db.Books.tags.any(db.Tags.name.like("%" + term + "%")), + db.Books.series.any(db.Series.name.like("%" + term + "%")), + db.Books.authors.any(db.Authors.name.like("%" + term + "%")), + db.Books.title.like("%" + term + "%"))).filter(filter).all() return render_template('search.html', searchterm=term, entries=entries) else: return render_template('search.html', searchterm="") + @app.route("/advanced_search", methods=["GET"]) @login_required_if_no_ano def advanced_search(): @@ -833,15 +971,16 @@ def advanced_search(): exclude_tag_inputs = request.args.getlist('exclude_tag') author_name = request.args.get("author_name") book_title = request.args.get("book_title") - if author_name: author_name=author_name.strip() - if book_title : book_title=book_title.strip() + if author_name: author_name = author_name.strip() + if book_title: book_title = book_title.strip() if include_tag_inputs or exclude_tag_inputs or author_name or book_title: searchterm = [] searchterm.extend((author_name, book_title)) tag_names = db.session.query(db.Tags).filter(db.Tags.id.in_(include_tag_inputs)).all() searchterm.extend(tag.name for tag in tag_names) searchterm = " + ".join(filter(None, searchterm)) - q = q.filter(db.Books.authors.any(db.Authors.name.like("%" + author_name + "%")), db.Books.title.like("%"+book_title+"%")) + q = q.filter(db.Books.authors.any(db.Authors.name.like("%" + author_name + "%")), + db.Books.title.like("%" + book_title + "%")) # random = db.session.query(db.Books).order_by(func.random()).limit(config.RANDOM_BOOKS) for tag in include_tag_inputs: q = q.filter(db.Books.tags.any(db.Tags.id == tag)) @@ -854,28 +993,31 @@ def advanced_search(): tags = db.session.query(db.Tags).order_by(db.Tags.name).all() return render_template('search_form.html', tags=tags) + @app.route("/cover/") @login_required_if_no_ano def get_cover(cover_path): return send_from_directory(os.path.join(config.DB_ROOT, cover_path), "cover.jpg") + @app.route("/feed/cover/") @requires_basic_auth_if_no_ano def feed_get_cover(cover_path): return send_from_directory(os.path.join(config.DB_ROOT, cover_path), "cover.jpg") + @app.route("/read//") @login_required -def read_book(book_id,format): +def read_book(book_id, format): book = db.session.query(db.Books).filter(db.Books.id == book_id).first() - if book : - book_dir = os.path.join(config.MAIN_DIR, "cps","static", str(book_id)) + if book: + book_dir = os.path.join(config.MAIN_DIR, "cps", "static", str(book_id)) if not os.path.exists(book_dir): os.mkdir(book_dir) if format.lower() == "epub": - #check if mimetype file is exists - mime_file = str(book_id) +"/mimetype" - if os.path.exists(mime_file) == False: + # check if mimetype file is exists + mime_file = str(book_id) + "/mimetype" + if not os.path.exists(mime_file): epub_file = os.path.join(config.DB_ROOT, book.path, book.data[0].name) + ".epub" if not os.path.isfile(epub_file): raise ValueError('Error opening eBook. File does not exist: ', epub_file) @@ -898,23 +1040,32 @@ def read_book(book_id,format): zfile.close() return render_template('read.html', bookid=book_id, title=_(u"Read a Book")) elif format.lower() == "pdf": - all_name = str(book_id) +"/"+ urllib.quote(book.data[0].name) +".pdf" - tmp_file = os.path.join(book_dir,urllib.quote(book.data[0].name)) + ".pdf" - if os.path.exists(tmp_file) == False: - pdf_file = os.path.join(config.DB_ROOT, book.path, book.data[0].name) + ".pdf" - copyfile(pdf_file,tmp_file) + all_name = str(book_id) + "/" + urllib.quote(book.data[0].name) + ".pdf" + tmp_file = os.path.join(book_dir, urllib.quote(book.data[0].name)) + ".pdf" + if not os.path.exists(tmp_file): + pdf_file = os.path.join(config.DB_ROOT, book.path, book.data[0].name) + ".pdf" + copyfile(pdf_file, tmp_file) return render_template('readpdf.html', pdffile=all_name, title=_(u"Read a Book")) - elif format.lower() == "txt": - all_name = str(book_id) +"/"+ urllib.quote(book.data[0].name) +".txt" - tmp_file = os.path.join(book_dir,urllib.quote(book.data[0].name)) + ".txt" - if os.path.exists(all_name) == False: - txt_file = os.path.join(config.DB_ROOT, book.path, book.data[0].name) + ".txt" - copyfile(txt_file,tmp_file) + elif format.lower() == "txt": + all_name = str(book_id) + "/" + urllib.quote(book.data[0].name) + ".txt" + tmp_file = os.path.join(book_dir, urllib.quote(book.data[0].name)) + ".txt" + if not os.path.exists(all_name): + txt_file = os.path.join(config.DB_ROOT, book.path, book.data[0].name) + ".txt" + copyfile(txt_file, tmp_file) return render_template('readtxt.html', txtfile=all_name, title=_(u"Read a Book")) - else : + elif format.lower() == "cbr": + all_name = str(book_id) + "/" + urllib.quote(book.data[0].name) + ".cbr" + tmp_file = os.path.join(book_dir, urllib.quote(book.data[0].name)) + ".cbr" + if not os.path.exists(all_name): + cbr_file = os.path.join(config.DB_ROOT, book.path, book.data[0].name) + ".cbr" + copyfile(cbr_file, tmp_file) + return render_template('readcbr.html', comicfile=all_name, title=_(u"Read a Book")) + + else: flash(_(u"Error opening eBook. File does not exist or file is not accessible:"), category="error") return redirect('/' or url_for("index", _external=True)) + @app.route("/download//") @login_required @download_required @@ -926,19 +1077,20 @@ def get_download_link(book_id, format): author = helper.get_normalized_author(book.author_sort) file_name = book.title if len(author) > 0: - file_name = author+'-'+file_name + file_name = author + '-' + file_name file_name = helper.get_valid_filename(file_name) - response = make_response(send_from_directory(os.path.join(config.DB_ROOT, book.path), data.name + "." +format)) + response = make_response(send_from_directory(os.path.join(config.DB_ROOT, book.path), data.name + "." + format)) response.headers["Content-Disposition"] = \ "attachment; " \ "filename={utf_filename}.{suffix};" \ "filename*=UTF-8''{utf_filename}.{suffix}".format( - utf_filename=file_name.encode('utf-8'), - suffix=format - ) + utf_filename=file_name.encode('utf-8'), + suffix=format + ) return response -@app.route('/register', methods = ['GET', 'POST']) + +@app.route('/register', methods=['GET', 'POST']) def register(): error = None if not config.PUBLIC_REG: @@ -975,7 +1127,8 @@ def register(): return render_template('register.html', title=_(u"register")) -@app.route('/login', methods = ['GET', 'POST']) + +@app.route('/login', methods=['GET', 'POST']) def login(): error = None @@ -987,13 +1140,14 @@ def login(): user = ub.session.query(ub.User).filter(ub.User.nickname == form['username'].strip()).first() if user and check_password_hash(user.password, form['password']): - login_user(user, remember = True) + login_user(user, remember=True) flash(_(u"you are now logged in as: '%(nickname)s'", nickname=user.nickname), category="success") return redirect('/' or url_for("index", _external=True)) else: flash(_(u"Wrong Username or Password"), category="error") - return render_template('login.html',title=_(u"login")) + return render_template('login.html', title=_(u"login")) + @app.route('/logout') @login_required @@ -1013,14 +1167,16 @@ def send_to_kindle(book_id): elif current_user.kindle_mail: result = helper.send_mail(book_id, current_user.kindle_mail) if result is None: - flash(_(u"Book successfully send to %(kindlemail)s", kindlemail=current_user.kindle_mail), category="success") + flash(_(u"Book successfully send to %(kindlemail)s", kindlemail=current_user.kindle_mail), + category="success") helper.update_download(book_id, int(current_user.id)) else: - flash(_(u"There was an error sending this book: %(res)s",res=result), category="error") + flash(_(u"There was an error sending this book: %(res)s", res=result), category="error") else: flash(_(u"Please configure your kindle email address first..."), category="error") return redirect(request.environ["HTTP_REFERER"]) + @app.route("/shelf/add//") @login_required def add_to_shelf(shelf_id, book_id): @@ -1033,11 +1189,12 @@ def add_to_shelf(shelf_id, book_id): ub.session.add(ins) ub.session.commit() - flash(_(u"Book has been added to shelf: %(sname)s",sname=shelf.name), category="success") + flash(_(u"Book has been added to shelf: %(sname)s", sname=shelf.name), category="success") - #return redirect(url_for('show_book', id=book_id)) + # return redirect(url_for('show_book', id=book_id)) return redirect(request.environ["HTTP_REFERER"]) + @app.route("/shelf/remove//") @login_required def remove_from_shelf(shelf_id, book_id): @@ -1046,16 +1203,18 @@ def remove_from_shelf(shelf_id, book_id): flash("Sorry you are not allowed to remove a book from this shelf: %s" % shelf.name) return redirect(url_for('index', _external=True)) - book_shelf = ub.session.query(ub.BookShelf).filter(ub.BookShelf.shelf == shelf_id, ub.BookShelf.book_id == book_id).first() + book_shelf = ub.session.query(ub.BookShelf).filter(ub.BookShelf.shelf == shelf_id, + ub.BookShelf.book_id == book_id).first() - #rem = ub.BookShelf(shelf=shelf.id, book_id=book_id) + # rem = ub.BookShelf(shelf=shelf.id, book_id=book_id) ub.session.delete(book_shelf) ub.session.commit() - flash(_(u"Book has been removed from shelf: %(sname)s",sname=shelf.name), category="success") + flash(_(u"Book has been removed from shelf: %(sname)s", sname=shelf.name), category="success") return redirect(request.environ["HTTP_REFERER"]) + @app.route("/shelf/create", methods=["GET", "POST"]) @login_required def create_shelf(): @@ -1068,17 +1227,18 @@ def create_shelf(): shelf.user_id = int(current_user.id) existing_shelf = ub.session.query(ub.Shelf).filter(ub.Shelf.name == shelf.name).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) ub.session.commit() - flash(_(u"Shelf %(title)s created",title=to_save["title"]), category="success") + flash(_(u"Shelf %(title)s created", title=to_save["title"]), category="success") except: flash(_(u"There was an error"), category="error") - return render_template('shelf_edit.html',title=_(u"create a shelf")) + return render_template('shelf_edit.html', title=_(u"create a shelf")) else: - return render_template('shelf_edit.html',title=_(u"create a shelf")) + return render_template('shelf_edit.html', title=_(u"create a shelf")) + @app.route("/shelf/delete/") @login_required @@ -1089,21 +1249,28 @@ def delete_shelf(shelf_id): 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), ub.and_(ub.Shelf.is_public == 1, ub.Shelf.id == shelf_id))).delete() + deleted = ub.session.query(ub.Shelf).filter(ub.or_(ub.and_(ub.Shelf.user_id == int(current_user.id), + ub.Shelf.id == shelf_id), + ub.and_(ub.Shelf.is_public == 1, + ub.Shelf.id == shelf_id))).delete() if deleted: ub.session.query(ub.BookShelf).filter(ub.BookShelf.shelf == shelf_id).delete() ub.session.commit() - flash( _("successfully deleted shelf %(name)s", name=cur_shelf.name, category="success") ) + flash(_("successfully deleted shelf %(name)s", name=cur_shelf.name, category="success")) return redirect(url_for('index')) + @app.route("/shelf/") @login_required_if_no_ano def show_shelf(shelf_id): if current_user.is_anonymous(): shelf = ub.session.query(ub.Shelf).filter(ub.Shelf.is_public == 1, ub.Shelf.id == shelf_id).first() - else : - shelf = ub.session.query(ub.Shelf).filter(ub.or_(ub.and_(ub.Shelf.user_id == int(current_user.id), ub.Shelf.id == shelf_id), ub.and_(ub.Shelf.is_public == 1, ub.Shelf.id == shelf_id))).first() + else: + shelf = ub.session.query(ub.Shelf).filter(ub.or_(ub.and_(ub.Shelf.user_id == int(current_user.id), + ub.Shelf.id == shelf_id), + ub.and_(ub.Shelf.is_public == 1, + ub.Shelf.id == shelf_id))).first() result = list() if shelf: books_in_shelf = ub.session.query(ub.BookShelf).filter(ub.BookShelf.shelf == shelf_id).all() @@ -1111,9 +1278,10 @@ def show_shelf(shelf_id): cur_book = db.session.query(db.Books).filter(db.Books.id == book.book_id).first() result.append(cur_book) - return render_template('shelf.html', entries=result, title=_(u"Shelf: '%(name)s'" ,name=shelf.name), shelf=shelf) + return render_template('shelf.html', entries=result, title=_(u"Shelf: '%(name)s'", name=shelf.name), shelf=shelf) + -@app.route("/me", methods = ["GET", "POST"]) +@app.route("/me", methods=["GET", "POST"]) @login_required def profile(): content = ub.session.query(ub.User).filter(ub.User.id == int(current_user.id)).first() @@ -1125,7 +1293,7 @@ def profile(): lang.name = cur_l.get_language_name(get_locale()) except: lang.name = _(isoLanguages.get(part3=lang.lang_code).name) - translations=babel.list_translations()+[LC('en')] + translations = babel.list_translations() + [LC('en')] for book in content.downloads: downloads.append(db.session.query(db.Books).filter(db.Books.id == book.book_id).first()) if request.method == "POST": @@ -1144,14 +1312,34 @@ 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 + if "default_language" in to_save: + content.default_language = to_save["default_language"] try: ub.session.commit() except IntegrityError: ub.session.rollback() flash(_(u"Found an existing account for this email address."), category="error") - return render_template("user_edit.html", content=content, downloads=downloads, title=_(u"%(name)s's profile", name=current_user.nickname)) + return render_template("user_edit.html", content=content, downloads=downloads, + title=_(u"%(name)s's profile", name=current_user.nickname)) flash(_(u"Profile updated"), category="success") - return render_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_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/user") @login_required @@ -1161,7 +1349,8 @@ def user_list(): settings = ub.session.query(ub.Settings).first() return render_template("user_list.html", content=content, email=settings, title=_(u"User list")) -@app.route("/admin/user/new", methods = ["GET", "POST"]) + +@app.route("/admin/user/new", methods=["GET", "POST"]) @login_required @admin_required def new_user(): @@ -1173,7 +1362,7 @@ def new_user(): lang.name = cur_l.get_language_name(get_locale()) except: lang.name = _(isoLanguages.get(part3=lang.lang_code).name) - translations=babel.list_translations()+[LC('en')] + translations = babel.list_translations() + [LC('en')] if request.method == "POST": to_save = request.form.to_dict() if not to_save["nickname"] or not to_save["email"] or not to_save["password"]: @@ -1211,14 +1400,16 @@ def new_user(): try: ub.session.add(content) ub.session.commit() - flash(_("User '%(user)s' created" , user=content.nickname), category="success") + flash(_("User '%(user)s' created", user=content.nickname), category="success") return redirect(url_for('user_list', _external=True)) except IntegrityError: ub.session.rollback() 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, languages=languages, title="Add new user") + return render_template("user_edit.html", new_user=1, content=content, translations=translations, + languages=languages, title="Add new user") + -@app.route("/admin/user/mailsettings", methods = ["GET", "POST"]) +@app.route("/admin/user/mailsettings", methods=["GET", "POST"]) @login_required @admin_required def edit_mailsettings(): @@ -1237,11 +1428,21 @@ def edit_mailsettings(): try: ub.session.commit() flash(_(u"Mail settings updated"), category="success") - except (e): + except e: flash(e, category="error") + if to_save["test"]: + 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") + else: + flash(_(u"There was an error sending the Test E-Mail: %(res)s", res=result), category="error") + else: + flash(_(u"Mail settings updated"), category="success") return render_template("email_edit.html", content=content, title=_("Edit mail settings")) -@app.route("/admin/user/", methods = ["GET", "POST"]) + +@app.route("/admin/user/", methods=["GET", "POST"]) @login_required @admin_required def edit_user(user_id): @@ -1261,35 +1462,35 @@ def edit_user(user_id): to_save = request.form.to_dict() if "delete" in to_save: ub.session.delete(content) - flash(_(u"User '%(nick)s' deleted",nick=content.nickname), category="success") + flash(_(u"User '%(nick)s' deleted", nick=content.nickname), category="success") return redirect(url_for('user_list', _external=True)) else: if to_save["password"]: content.password = generate_password_hash(to_save["password"]) - + if "admin_role" in to_save and not content.role_admin(): content.role = content.role + ub.ROLE_ADMIN - elif not "admin_role" in to_save and content.role_admin(): + elif "admin_role" not in to_save and content.role_admin(): content.role = content.role - ub.ROLE_ADMIN - + if "download_role" in to_save and not content.role_download(): content.role = content.role + ub.ROLE_DOWNLOAD - elif not "download_role" in to_save and content.role_download(): + elif "download_role" not in to_save and content.role_download(): content.role = content.role - ub.ROLE_DOWNLOAD - + if "upload_role" in to_save and not content.role_upload(): content.role = content.role + ub.ROLE_UPLOAD - elif not "upload_role" in to_save and content.role_upload(): + elif "upload_role" not in to_save and content.role_upload(): content.role = content.role - ub.ROLE_UPLOAD - + if "edit_role" in to_save and not content.role_edit(): content.role = content.role + ub.ROLE_EDIT - elif not "edit_role" in to_save and content.role_edit(): + elif "edit_role" not in to_save and content.role_edit(): content.role = content.role - ub.ROLE_EDIT - + if "passwd_role" in to_save and not content.role_passwd(): content.role = content.role + ub.ROLE_PASSWD - elif not "passwd_role" in to_save and content.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 @@ -1316,18 +1517,20 @@ def edit_user(user_id): content.kindle_mail = to_save["kindle_mail"] try: ub.session.commit() - flash(_(u"User '%(nick)s' updated",nick= content.nickname), category="success") + flash(_(u"User '%(nick)s' updated", nick=content.nickname), category="success") except IntegrityError: ub.session.rollback() flash(_(u"An unknown error occured."), category="error") - return render_template("user_edit.html", translations=translations, languages=languages, new_user=0, content=content, downloads=downloads, title=_(u"Edit User %(nick)s",nick=content.nickname)) + return render_template("user_edit.html", translations=translations, languages=languages, new_user=0, + content=content, downloads=downloads, title=_(u"Edit User %(nick)s", nick=content.nickname)) + @app.route("/admin/book/", methods=['GET', 'POST']) @login_required @edit_required def edit_book(book_id): - ## create the function for sorting... - db.session.connection().connection.connection.create_function("title_sort",1,db.title_sort) + # create the function for sorting... + db.session.connection().connection.connection.create_function("title_sort", 1, db.title_sort) cc = db.session.query(db.Custom_Columns).filter(db.Custom_Columns.datatype.notin_(db.cc_exceptions)).all() if current_user.filter_language() != "all": filter = db.Books.languages.any(db.Languages.lang_code == current_user.filter_language()) @@ -1338,9 +1541,10 @@ def edit_book(book_id): if book: for index in range(0, len(book.languages)): try: - book.languages[index].language_name=LC.parse(book.languages[index].lang_code).get_language_name(get_locale()) + book.languages[index].language_name = LC.parse(book.languages[index].lang_code).get_language_name( + get_locale()) except: - book.languages[index].language_name=_(isoLanguages.get(part3=book.languages[index].lang_code).name) + book.languages[index].language_name = _(isoLanguages.get(part3=book.languages[index].lang_code).name) for author in book.authors: author_names.append(author.name) if request.method == 'POST': @@ -1356,7 +1560,7 @@ def edit_book(book_id): modify_database_object(input_authors, book.authors, db.Authors, db.session, 'author') if author0_before_edit != book.authors[0].name: edited_books_id.add(book.id) - + if to_save["cover_url"] and os.path.splitext(to_save["cover_url"])[1].lower() == ".jpg": img = requests.get(to_save["cover_url"]) f = open(os.path.join(config.DB_ROOT, book.path, "cover.jpg"), "wb") @@ -1384,7 +1588,7 @@ def edit_book(book_id): # retranslate displayed text to language codes languages = db.session.query(db.Languages).all() - input_l=[] + input_l = [] for lang in languages: try: lang.name = LC.parse(lang.lang_code).get_language_name(get_locale()).lower() @@ -1399,7 +1603,7 @@ def edit_book(book_id): old_rating = False if len(book.ratings) > 0: old_rating = book.ratings[0].rating - ratingx2 = int(float(to_save["rating"]) *2) + ratingx2 = int(float(to_save["rating"]) * 2) if ratingx2 != old_rating: is_rating = db.session.query(db.Ratings).filter(db.Ratings.rating == ratingx2).first() if is_rating: @@ -1423,43 +1627,45 @@ def edit_book(book_id): if to_save[cc_string].strip(): if c.datatype == 'bool': if to_save[cc_string] == 'None': - to_save[cc_string]= None + to_save[cc_string] = None else: to_save[cc_string] = 1 if to_save[cc_string] == 'True' else 0 if to_save[cc_string] != cc_db_value: if cc_db_value is not None: if to_save[cc_string] is not None: setattr(getattr(book, cc_string)[0], 'value', to_save[cc_string]) - else : + else: del_cc = getattr(book, cc_string)[0] getattr(book, cc_string).remove(del_cc) db.session.delete(del_cc) - else : + else: cc_class = db.cc_classes[c.id] - new_cc = cc_class(value=to_save[cc_string],book=book_id) + new_cc = cc_class(value=to_save[cc_string], book=book_id) db.session.add(new_cc) else: if c.datatype == 'rating': - to_save[cc_string] = str(int(float(to_save[cc_string]) *2)) + to_save[cc_string] = str(int(float(to_save[cc_string]) * 2)) if to_save[cc_string].strip() != cc_db_value: - if cc_db_value != None: - #remove old cc_val + if cc_db_value is not None: + # remove old cc_val del_cc = getattr(book, cc_string)[0] getattr(book, cc_string).remove(del_cc) if len(del_cc.books) == 0: db.session.delete(del_cc) cc_class = db.cc_classes[c.id] - new_cc = db.session.query(cc_class).filter(cc_class.value == to_save[cc_string].strip()).first() + new_cc = db.session.query(cc_class).filter( + cc_class.value == to_save[cc_string].strip()).first() # if no cc val is found add it - if new_cc == None: + if new_cc is None: new_cc = cc_class(value=to_save[cc_string].strip()) db.session.add(new_cc) - new_cc = db.session.query(cc_class).filter(cc_class.value == to_save[cc_string].strip()).first() + new_cc = db.session.query(cc_class).filter( + cc_class.value == to_save[cc_string].strip()).first() # add cc value to book getattr(book, cc_string).append(new_cc) else: - if cc_db_value != None: - #remove old cc_val + if cc_db_value is not None: + # remove old cc_val del_cc = getattr(book, cc_string)[0] getattr(book, cc_string).remove(del_cc) if len(del_cc.books) == 0: @@ -1476,7 +1682,7 @@ def edit_book(book_id): for inp_tag in input_tags: if inp_tag == c_tag.value: found = True - break; + break # if the tag was not found in the new list, add him to remove list if not found: del_tags.append(c_tag) @@ -1487,7 +1693,7 @@ def edit_book(book_id): for c_tag in getattr(book, cc_string): if inp_tag == c_tag.value: found = True - break; + break if not found: add_tags.append(inp_tag) # if there are tags to remove, we remove them now @@ -1500,12 +1706,14 @@ def edit_book(book_id): if len(add_tags) > 0: for add_tag in add_tags: # check if a tag with that name exists - new_tag = db.session.query(db.cc_classes[c.id]).filter(db.cc_classes[c.id].value == add_tag).first() + new_tag = db.session.query(db.cc_classes[c.id]).filter( + db.cc_classes[c.id].value == add_tag).first() # if no tag is found add it - if new_tag == None: + if new_tag is None: new_tag = db.cc_classes[c.id](value=add_tag) db.session.add(new_tag) - new_tag = db.session.query(db.cc_classes[c.id]).filter(db.cc_classes[c.id].value == add_tag).first() + new_tag = db.session.query(db.cc_classes[c.id]).filter( + db.cc_classes[c.id].value == add_tag).first() # add tag to book getattr(book, cc_string).append(new_tag) @@ -1525,18 +1733,16 @@ def edit_book(book_id): flash(_(u"Error opening eBook. File does not exist or file is not accessible:"), category="error") return redirect('/' or url_for("index", _external=True)) -import uploader -from shutil import move -@app.route("/upload", methods = ["GET", "POST"]) +@app.route("/upload", methods=["GET", "POST"]) @login_required @upload_required def upload(): if not config.UPLOADING: abort(404) - ## create the function for sorting... - db.session.connection().connection.connection.create_function("title_sort",1,db.title_sort) - db.session.connection().connection.connection.create_function('uuid4', 0, lambda : str(uuid4())) + # create the function for sorting... + db.session.connection().connection.connection.create_function("title_sort", 1, db.title_sort) + db.session.connection().connection.connection.create_function('uuid4', 0, lambda: str(uuid4())) if request.method == 'POST' and 'btn-upload' in request.files: file = request.files['btn-upload'] meta = uploader.upload(file) @@ -1582,11 +1788,12 @@ def upload(): db_author = db.Authors(author, "", "") db.session.add(db_author) path = os.path.join(author_dir, title_dir) - db_book = db.Books(title, "", "", datetime.datetime.now(), datetime.datetime(101, 01,01), 1, datetime.datetime.now(), path, has_cover, db_author, []) + db_book = db.Books(title, "", "", datetime.datetime.now(), datetime.datetime(101, 01, 01), 1, + datetime.datetime.now(), path, has_cover, db_author, []) db_book.authors.append(db_author) db_data = db.Data(db_book, meta.extension.upper()[1:], file_size, data_name) db_book.data.append(db_data) - + db.session.add(db_book) db.session.commit() author_names = [] @@ -1596,4 +1803,4 @@ def upload(): if current_user.role_edit() or current_user.role_admin(): return render_template('edit_book.html', book=db_book, authors=author_names, cc=cc) book_in_shelfs = [] - return render_template('detail.html', entry=db_book, cc=cc, title=db_book.title, books_shelfs=book_in_shelfs) \ No newline at end of file + return render_template('detail.html', entry=db_book, cc=cc, title=db_book.title, books_shelfs=book_in_shelfs) diff --git a/messages.pot b/messages.pot index 78671f22..69854d44 100644 --- a/messages.pot +++ b/messages.pot @@ -8,7 +8,7 @@ msgid "" msgstr "" "Project-Id-Version: PROJECT VERSION\n" "Report-Msgid-Bugs-To: EMAIL@ADDRESS\n" -"POT-Creation-Date: 2016-11-12 09:44+0100\n" +"POT-Creation-Date: 2016-12-23 08:39+0100\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: FULL NAME \n" "Language-Team: LANGUAGE \n" @@ -17,244 +17,266 @@ msgstr "" "Content-Transfer-Encoding: 8bit\n" "Generated-By: Babel 2.3.4\n" -#: cps/helper.py:80 cps/templates/detail.html:113 -msgid "Send to Kindle" +#: cps/book_formats.py:108 cps/book_formats.py:112 cps/web.py:923 +msgid "not installed" +msgstr "" + +#: cps/helper.py:93 +msgid "Calibre-web test email" msgstr "" -#: cps/helper.py:81 +#: cps/helper.py:94 cps/helper.py:147 msgid "This email has been sent via calibre web." msgstr "" -#: cps/helper.py:103 cps/helper.py:118 -msgid "Could not find any formats suitable for sending by email" +#: cps/helper.py:128 cps/helper.py:216 +#, python-format +msgid "Failed to send mail: %s" msgstr "" -#: cps/helper.py:112 -msgid "Could not convert epub to mobi" +#: cps/helper.py:146 cps/templates/detail.html:113 +msgid "Send to Kindle" msgstr "" -#: cps/helper.py:142 -#, python-format -msgid "Failed to send mail: %s" +#: cps/helper.py:169 cps/helper.py:184 +msgid "Could not find any formats suitable for sending by email" msgstr "" -#: cps/helper.py:162 +#: cps/helper.py:178 +msgid "Could not convert epub to mobi" +msgstr "" + +#: cps/helper.py:236 msgid "The requested file could not be read. Maybe wrong permissions?" msgstr "" -#: cps/web.py:639 +#: cps/web.py:717 msgid "Latest Books" msgstr "" -#: cps/web.py:661 +#: cps/web.py:742 msgid "Hot Books (most downloaded)" msgstr "" -#: cps/templates/index.xml:41 cps/web.py:668 +#: cps/templates/index.xml:41 cps/web.py:750 msgid "Random Books" msgstr "" -#: cps/web.py:679 +#: cps/web.py:763 msgid "Author list" msgstr "" -#: cps/web.py:695 +#: cps/web.py:780 #, python-format msgid "Author: %(nam)s" msgstr "" -#: cps/templates/index.xml:65 cps/web.py:706 +#: cps/templates/index.xml:65 cps/web.py:793 msgid "Series list" msgstr "" -#: cps/web.py:714 +#: cps/web.py:804 #, python-format msgid "Series: %(serie)s" msgstr "" -#: cps/web.py:716 cps/web.py:796 cps/web.py:914 cps/web.py:1524 +#: cps/web.py:806 cps/web.py:902 cps/web.py:1061 cps/web.py:1729 msgid "Error opening eBook. File does not exist or file is not accessible:" msgstr "" -#: cps/web.py:742 +#: cps/web.py:837 msgid "Available languages" msgstr "" -#: cps/web.py:754 +#: cps/web.py:852 #, python-format msgid "Language: %(name)s" msgstr "" -#: cps/templates/index.xml:57 cps/web.py:765 +#: cps/templates/index.xml:57 cps/web.py:865 msgid "Category list" msgstr "" -#: cps/web.py:772 +#: cps/web.py:875 #, python-format msgid "Category: %(name)s" msgstr "" -#: cps/web.py:810 +#: cps/web.py:931 msgid "Statistics" msgstr "" -#: cps/web.py:898 cps/web.py:905 cps/web.py:912 +#: cps/web.py:939 +msgid "Server restarts" +msgstr "" + +#: cps/web.py:1037 cps/web.py:1044 cps/web.py:1051 cps/web.py:1058 msgid "Read a Book" msgstr "" -#: cps/web.py:951 cps/web.py:1179 +#: cps/web.py:1100 cps/web.py:1365 msgid "Please fill out all fields!" msgstr "" -#: cps/web.py:967 +#: cps/web.py:1116 msgid "An unknown error occured. Please try again later." msgstr "" -#: cps/web.py:972 +#: cps/web.py:1121 msgid "This username or email address is already in use." msgstr "" -#: cps/web.py:975 +#: cps/web.py:1124 msgid "register" msgstr "" -#: cps/web.py:990 +#: cps/web.py:1140 #, python-format msgid "you are now logged in as: '%(nickname)s'" msgstr "" -#: cps/web.py:993 +#: cps/web.py:1143 msgid "Wrong Username or Password" msgstr "" -#: cps/web.py:995 +#: cps/web.py:1145 msgid "login" msgstr "" -#: cps/web.py:1011 +#: cps/web.py:1162 msgid "Please configure the SMTP mail settings first..." msgstr "" -#: cps/web.py:1015 +#: cps/web.py:1166 #, python-format msgid "Book successfully send to %(kindlemail)s" msgstr "" -#: cps/web.py:1018 +#: cps/web.py:1170 #, python-format msgid "There was an error sending this book: %(res)s" msgstr "" -#: cps/web.py:1020 +#: cps/web.py:1172 msgid "Please configure your kindle email address first..." msgstr "" -#: cps/web.py:1035 +#: cps/web.py:1188 #, python-format msgid "Book has been added to shelf: %(sname)s" msgstr "" -#: cps/web.py:1054 +#: cps/web.py:1209 #, python-format msgid "Book has been removed from shelf: %(sname)s" msgstr "" -#: cps/web.py:1070 +#: cps/web.py:1226 #, python-format msgid "A shelf with the name '%(title)s' already exists." msgstr "" -#: cps/web.py:1075 +#: cps/web.py:1231 #, python-format msgid "Shelf %(title)s created" msgstr "" -#: cps/web.py:1077 +#: cps/web.py:1233 msgid "There was an error" msgstr "" -#: cps/web.py:1078 cps/web.py:1080 +#: cps/web.py:1234 cps/web.py:1236 msgid "create a shelf" msgstr "" -#: cps/web.py:1096 +#: cps/web.py:1256 #, python-format msgid "successfully deleted shelf %(name)s" msgstr "" -#: cps/web.py:1113 +#: cps/web.py:1277 #, python-format msgid "Shelf: '%(name)s'" msgstr "" -#: cps/web.py:1150 +#: cps/web.py:1332 msgid "Found an existing account for this email address." msgstr "" -#: cps/web.py:1151 cps/web.py:1153 +#: cps/web.py:1334 cps/web.py:1337 #, python-format msgid "%(name)s's profile" msgstr "" -#: cps/web.py:1152 +#: cps/web.py:1335 msgid "Profile updated" msgstr "" -#: cps/web.py:1161 +#: cps/web.py:1346 msgid "User list" msgstr "" -#: cps/templates/user_list.html:32 cps/web.py:1180 +#: cps/templates/user_list.html:32 cps/web.py:1366 msgid "Add new user" msgstr "" -#: cps/web.py:1213 +#: cps/web.py:1399 #, python-format msgid "User '%(user)s' created" msgstr "" -#: cps/web.py:1217 +#: cps/web.py:1403 msgid "Found an existing account for this email address or nickname." msgstr "" -#: cps/web.py:1238 +#: cps/web.py:1426 cps/web.py:1437 msgid "Mail settings updated" msgstr "" -#: cps/web.py:1241 +#: cps/web.py:1432 +#, python-format +msgid "Test E-Mail successfully send to %(kindlemail)s" +msgstr "" + +#: cps/web.py:1435 +#, python-format +msgid "There was an error sending the Test E-Mail: %(res)s" +msgstr "" + +#: cps/web.py:1438 msgid "Edit mail settings" msgstr "" -#: cps/web.py:1263 +#: cps/web.py:1461 #, python-format msgid "User '%(nick)s' deleted" msgstr "" -#: cps/web.py:1318 +#: cps/web.py:1516 #, python-format msgid "User '%(nick)s' updated" msgstr "" -#: cps/web.py:1321 +#: cps/web.py:1519 msgid "An unknown error occured." msgstr "" -#: cps/web.py:1322 +#: cps/web.py:1521 #, python-format msgid "Edit User %(nick)s" msgstr "" -#: cps/web.py:1556 +#: cps/web.py:1759 #, python-format msgid "Failed to create path %s (Permission denied)." msgstr "" -#: cps/web.py:1561 +#: cps/web.py:1764 #, python-format msgid "Failed to store file %s (Permission denied)." msgstr "" -#: cps/web.py:1566 +#: cps/web.py:1769 #, python-format msgid "Failed to delete file %s (Permission denied)." msgstr "" @@ -339,14 +361,14 @@ msgstr "" msgid "view book after edit" msgstr "" -#: cps/templates/edit_book.html:105 cps/templates/email_edit.html:30 -#: cps/templates/login.html:19 cps/templates/search_form.html:33 -#: cps/templates/shelf_edit.html:15 cps/templates/user_edit.html:93 +#: cps/templates/edit_book.html:105 cps/templates/login.html:19 +#: cps/templates/search_form.html:33 cps/templates/shelf_edit.html:15 +#: cps/templates/user_edit.html:94 msgid "Submit" msgstr "" -#: cps/templates/edit_book.html:106 cps/templates/email_edit.html:31 -#: cps/templates/user_edit.html:95 +#: cps/templates/edit_book.html:106 cps/templates/email_edit.html:32 +#: cps/templates/user_edit.html:96 msgid "Back" msgstr "" @@ -374,6 +396,14 @@ msgstr "" msgid "From e-mail" msgstr "" +#: cps/templates/email_edit.html:30 +msgid "Save settings" +msgstr "" + +#: cps/templates/email_edit.html:31 +msgid "Save settings and send Test E-Mail" +msgstr "" + #: cps/templates/feed.xml:14 msgid "Next" msgstr "" @@ -504,6 +534,14 @@ msgstr "" msgid "Remember me" msgstr "" +#: cps/templates/read.html:136 +msgid "Reflow text when sidebars are open." +msgstr "" + +#: cps/templates/readpdf.html:29 +msgid "PDF.js viewer" +msgstr "" + #: cps/templates/register.html:4 msgid "Register a new account" msgstr "" @@ -544,6 +582,10 @@ msgstr "" msgid "Delete this Shelf" msgstr "" +#: cps/templates/shelf.html:7 +msgid "Edit Shelf name" +msgstr "" + #: cps/templates/shelf_edit.html:7 msgid "Title" msgstr "" @@ -552,11 +594,27 @@ msgstr "" msgid "should the shelf be public?" msgstr "" -#: cps/templates/stats.html:4 +#: cps/templates/stats.html:3 +msgid "Linked libraries" +msgstr "" + +#: cps/templates/stats.html:8 +msgid "Program library" +msgstr "" + +#: cps/templates/stats.html:9 +msgid "Installed Version" +msgstr "" + +#: cps/templates/stats.html:32 +msgid "Calibre library statistics" +msgstr "" + +#: cps/templates/stats.html:37 msgid "Books in this Library" msgstr "" -#: cps/templates/stats.html:5 +#: cps/templates/stats.html:41 msgid "Authors in this Library" msgstr "" @@ -572,51 +630,51 @@ msgstr "" msgid "Show all" msgstr "" -#: cps/templates/user_edit.html:46 +#: cps/templates/user_edit.html:45 msgid "Show random books" msgstr "" -#: cps/templates/user_edit.html:50 +#: cps/templates/user_edit.html:49 msgid "Show hot books" msgstr "" -#: cps/templates/user_edit.html:54 +#: cps/templates/user_edit.html:53 msgid "Show language selection" msgstr "" -#: cps/templates/user_edit.html:58 +#: cps/templates/user_edit.html:57 msgid "Show series selection" msgstr "" -#: cps/templates/user_edit.html:62 +#: cps/templates/user_edit.html:61 msgid "Show category selection" msgstr "" -#: cps/templates/user_edit.html:67 +#: cps/templates/user_edit.html:68 msgid "Admin user" msgstr "" -#: cps/templates/user_edit.html:71 +#: cps/templates/user_edit.html:72 msgid "Allow Downloads" msgstr "" -#: cps/templates/user_edit.html:75 +#: cps/templates/user_edit.html:76 msgid "Allow Uploads" msgstr "" -#: cps/templates/user_edit.html:79 +#: cps/templates/user_edit.html:80 msgid "Allow Edit" msgstr "" -#: cps/templates/user_edit.html:83 +#: cps/templates/user_edit.html:84 msgid "Allow Changing Password" msgstr "" -#: cps/templates/user_edit.html:89 +#: cps/templates/user_edit.html:90 msgid "Delete this user" msgstr "" -#: cps/templates/user_edit.html:100 +#: cps/templates/user_edit.html:101 msgid "Recent Downloads" msgstr "" @@ -664,5 +722,3 @@ msgstr "" msgid "Change SMTP settings" msgstr "" -msgid "Latin" -msgstr ""