Commit ba44a158 authored by OzzieIsaacs's avatar OzzieIsaacs

changes for #77

Code cosmetics
#75:
- More debug infos for kindlegen and sending e-mail.
- Button for sending test e-mail.
- timeout of 5min for sending e-mail
parent c582ccf7
...@@ -17,6 +17,7 @@ eggs/ ...@@ -17,6 +17,7 @@ eggs/
*.db *.db
*.log *.log
config.ini config.ini
cps/static/[0-9]*
.idea/ .idea/
*.bak *.bak
......
...@@ -2,10 +2,14 @@ ...@@ -2,10 +2,14 @@
import os import os
import sys import sys
from threading import Thread
from multiprocessing import Queue
import time
base_path = os.path.dirname(os.path.abspath(__file__)) base_path = os.path.dirname(os.path.abspath(__file__))
# Insert local directories into path # 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 web
from cps import config from cps import config
...@@ -15,11 +19,47 @@ from tornado.ioloop import IOLoop ...@@ -15,11 +19,47 @@ from tornado.ioloop import IOLoop
global title_sort global title_sort
def title_sort(title): def title_sort(title):
return title return title
if config.DEVELOPMENT:
web.app.run(host="0.0.0.0",port=config.PORT, debug=True)
else: def start_calibreweb(messagequeue):
http_server = HTTPServer(WSGIContainer(web.app)) web.global_queue = messagequeue
http_server.listen(config.PORT) if config.DEVELOPMENT:
IOLoop.instance().start() web.app.run(host="0.0.0.0", port=config.PORT, debug=True)
\ No newline at end of file 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()
import logging
import uploader
import os
from flask_babel import gettext as _
__author__ = 'lemmsh' __author__ = 'lemmsh'
import logging
logger = logging.getLogger("book_formats") logger = logging.getLogger("book_formats")
import uploader
import os
try: try:
from wand.image import Image from wand.image import Image
from wand import version as ImageVersion
use_generic_pdf_cover = False use_generic_pdf_cover = False
except ImportError, e: except ImportError, e:
logger.warning('cannot import Image, generating pdf covers for pdf uploads will not work: %s', e) logger.warning('cannot import Image, generating pdf covers for pdf uploads will not work: %s', e)
use_generic_pdf_cover = True use_generic_pdf_cover = True
try: try:
from PyPDF2 import PdfFileReader from PyPDF2 import PdfFileReader
from PyPDF2 import __version__ as PyPdfVersion
use_pdf_meta = True use_pdf_meta = True
except ImportError, e: except ImportError, e:
logger.warning('cannot import PyPDF2, extracting pdf metadata will not work: %s', 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): ...@@ -37,9 +41,9 @@ def process(tmp_file_path, original_file_name, original_file_extension):
try: try:
if ".PDF" == original_file_extension.upper(): if ".PDF" == original_file_extension.upper():
return pdf_meta(tmp_file_path, original_file_name, original_file_extension) 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) 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) return fb2.get_fb2_info(tmp_file_path, original_file_name, original_file_extension)
except Exception, e: except Exception, e:
logger.warning('cannot parse metadata, using default: %s', 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): ...@@ -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) return default_meta(tmp_file_path, original_file_name, original_file_extension)
def 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( return uploader.BookMeta(
file_path = tmp_file_path, file_path=tmp_file_path,
extension = original_file_extension, extension=original_file_extension,
title = original_file_name, title=original_file_name,
author = "Unknown", author="Unknown",
cover = None, cover=None,
description = "", description="",
tags = "", tags="",
series = "", series="",
series_id="") series_id="")
def pdf_meta(tmp_file_path, original_file_name, original_file_extension): 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')) pdf = PdfFileReader(open(tmp_file_path, 'rb'))
doc_info = pdf.getDocumentInfo() doc_info = pdf.getDocumentInfo()
else: else:
doc_info = None 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" 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 title = doc_info.title if doc_info.title is not None else original_file_name
subject = doc_info.subject subject = doc_info.subject
...@@ -78,16 +81,17 @@ def pdf_meta(tmp_file_path, original_file_name, original_file_extension): ...@@ -78,16 +81,17 @@ def pdf_meta(tmp_file_path, original_file_name, original_file_extension):
title = original_file_name title = original_file_name
subject = "" subject = ""
return uploader.BookMeta( return uploader.BookMeta(
file_path = tmp_file_path, file_path=tmp_file_path,
extension = original_file_extension, extension=original_file_extension,
title = title, title=title,
author = author, author=author,
cover = pdf_preview(tmp_file_path, original_file_name), cover=pdf_preview(tmp_file_path, original_file_name),
description = subject, description=subject,
tags = "", tags="",
series = "", series="",
series_id="") series_id="")
def pdf_preview(tmp_file_path, tmp_dir): def pdf_preview(tmp_file_path, tmp_dir):
if use_generic_pdf_cover: if use_generic_pdf_cover:
return None return None
...@@ -97,3 +101,14 @@ def pdf_preview(tmp_file_path, tmp_dir): ...@@ -97,3 +101,14 @@ def pdf_preview(tmp_file_path, tmp_dir):
img.compression_quality = 88 img.compression_quality = 88
img.save(filename=os.path.join(tmp_dir, cover_file_name)) img.save(filename=os.path.join(tmp_dir, cover_file_name))
return cover_file_name return cover_file_name
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
...@@ -5,9 +5,10 @@ import os ...@@ -5,9 +5,10 @@ import os
import sys import sys
from configobj import ConfigObj 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 = ConfigObj(CONFIG_FILE)
CFG.encoding='UTF-8' CFG.encoding = 'UTF-8'
def CheckSection(sec): def CheckSection(sec):
""" Check if INI section exists, if not create it """ """ Check if INI section exists, if not create it """
...@@ -18,6 +19,7 @@ def CheckSection(sec): ...@@ -18,6 +19,7 @@ def CheckSection(sec):
CFG[sec] = {} CFG[sec] = {}
return False return False
def check_setting_str(config, cfg_name, item_name, def_val, log=True): def check_setting_str(config, cfg_name, item_name, def_val, log=True):
try: try:
my_val = config[cfg_name][item_name].decode('UTF-8') 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)) ...@@ -62,24 +64,16 @@ PUBLIC_REG = bool(check_setting_int(CFG, 'Advanced', 'PUBLIC_REG', 0))
UPLOADING = bool(check_setting_int(CFG, 'Advanced', 'UPLOADING', 0)) UPLOADING = bool(check_setting_int(CFG, 'Advanced', 'UPLOADING', 0))
ANON_BROWSE = bool(check_setting_int(CFG, 'Advanced', 'ANON_BROWSE', 0)) ANON_BROWSE = bool(check_setting_int(CFG, 'Advanced', 'ANON_BROWSE', 0))
SYS_ENCODING="UTF-8" SYS_ENCODING = "UTF-8"
if DB_ROOT == "": if DB_ROOT == "":
print "Calibre database directory (DB_ROOT) is not configured" print "Calibre database directory (DB_ROOT) is not configured"
sys.exit(1) sys.exit(1)
configval={} configval = {"DB_ROOT": DB_ROOT, "APP_DB_ROOT": APP_DB_ROOT, "MAIN_DIR": MAIN_DIR, "LOG_DIR": LOG_DIR, "PORT": PORT,
configval["DB_ROOT"] = DB_ROOT "NEWEST_BOOKS": NEWEST_BOOKS, "DEVELOPMENT": DEVELOPMENT, "TITLE_REGEX": TITLE_REGEX,
configval["APP_DB_ROOT"] = APP_DB_ROOT "PUBLIC_REG": PUBLIC_REG, "UPLOADING": UPLOADING, "ANON_BROWSE": ANON_BROWSE}
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
def save_config(configval): def save_config(configval):
new_config = ConfigObj(encoding='UTF-8') new_config = ConfigObj(encoding='UTF-8')
......
...@@ -9,8 +9,10 @@ import config ...@@ -9,8 +9,10 @@ import config
import re import re
import ast import ast
#calibre sort stuff # calibre sort stuff
title_pat = re.compile(config.TITLE_REGEX, re.IGNORECASE) title_pat = re.compile(config.TITLE_REGEX, re.IGNORECASE)
def title_sort(title): def title_sort(title):
match = title_pat.search(title) match = title_pat.search(title)
if match: if match:
...@@ -52,7 +54,7 @@ books_languages_link = Table('books_languages_link', Base.metadata, ...@@ -52,7 +54,7 @@ books_languages_link = Table('books_languages_link', Base.metadata,
cc = conn.execute("SELECT id, datatype FROM custom_columns") cc = conn.execute("SELECT id, datatype FROM custom_columns")
cc_ids = [] cc_ids = []
cc_exceptions = [ 'datetime', 'int', 'comments', 'float', 'composite','series' ] cc_exceptions = ['datetime', 'int', 'comments', 'float', 'composite', 'series']
books_custom_column_links = {} books_custom_column_links = {}
cc_classes = {} cc_classes = {}
for row in cc: for row in cc:
...@@ -61,18 +63,19 @@ for row in cc: ...@@ -61,18 +63,19 @@ for row in cc:
Column('book', Integer, ForeignKey('books.id'), primary_key=True), Column('book', Integer, ForeignKey('books.id'), primary_key=True),
Column('value', Integer, ForeignKey('custom_column_' + str(row.id) + '.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': if row.datatype == 'bool':
ccdict = {'__tablename__': 'custom_column_' + str(row.id), ccdict = {'__tablename__': 'custom_column_' + str(row.id),
'id': Column(Integer, primary_key=True), 'id': Column(Integer, primary_key=True),
'book': Column(Integer,ForeignKey('books.id')), 'book': Column(Integer, ForeignKey('books.id')),
'value': Column(Boolean)} 'value': Column(Boolean)}
else: else:
ccdict={'__tablename__':'custom_column_' + str(row.id), ccdict = {'__tablename__': 'custom_column_' + str(row.id),
'id':Column(Integer, primary_key=True), 'id': Column(Integer, primary_key=True),
'value':Column(String)} 'value': Column(String)}
cc_classes[row.id] = type('Custom_Column_' + str(row.id), (Base,), ccdict) cc_classes[row.id] = type('Custom_Column_' + str(row.id), (Base,), ccdict)
class Comments(Base): class Comments(Base):
__tablename__ = 'comments' __tablename__ = 'comments'
...@@ -100,6 +103,7 @@ class Tags(Base): ...@@ -100,6 +103,7 @@ class Tags(Base):
def __repr__(self): def __repr__(self):
return u"<Tags('{0})>".format(self.name) return u"<Tags('{0})>".format(self.name)
class Authors(Base): class Authors(Base):
__tablename__ = 'authors' __tablename__ = 'authors'
...@@ -116,6 +120,7 @@ class Authors(Base): ...@@ -116,6 +120,7 @@ class Authors(Base):
def __repr__(self): def __repr__(self):
return u"<Authors('{0},{1}{2}')>".format(self.name, self.sort, self.link) return u"<Authors('{0},{1}{2}')>".format(self.name, self.sort, self.link)
class Series(Base): class Series(Base):
__tablename__ = 'series' __tablename__ = 'series'
...@@ -130,30 +135,33 @@ class Series(Base): ...@@ -130,30 +135,33 @@ class Series(Base):
def __repr__(self): def __repr__(self):
return u"<Series('{0},{1}')>".format(self.name, self.sort) return u"<Series('{0},{1}')>".format(self.name, self.sort)
class Ratings(Base): class Ratings(Base):
__tablename__ = 'ratings' __tablename__ = 'ratings'
id = Column(Integer, primary_key=True) id = Column(Integer, primary_key=True)
rating = Column(Integer) rating = Column(Integer)
def __init__(self,rating): def __init__(self, rating):
self.rating = rating self.rating = rating
def __repr__(self): def __repr__(self):
return u"<Ratings('{0}')>".format(self.rating) return u"<Ratings('{0}')>".format(self.rating)
class Languages(Base): class Languages(Base):
__tablename__ = 'languages' __tablename__ = 'languages'
id = Column(Integer, primary_key=True) id = Column(Integer, primary_key=True)
lang_code = Column(String) lang_code = Column(String)
def __init__(self,lang_code): def __init__(self, lang_code):
self.lang_code = lang_code self.lang_code = lang_code
def __repr__(self): def __repr__(self):
return u"<Languages('{0}')>".format(self.lang_code) return u"<Languages('{0}')>".format(self.lang_code)
class Data(Base): class Data(Base):
__tablename__ = 'data' __tablename__ = 'data'
...@@ -172,6 +180,7 @@ class Data(Base): ...@@ -172,6 +180,7 @@ class Data(Base):
def __repr__(self): def __repr__(self):
return u"<Data('{0},{1}{2}{3}')>".format(self.book, self.format, self.uncompressed_size, self.name) return u"<Data('{0},{1}{2}{3}')>".format(self.book, self.format, self.uncompressed_size, self.name)
class Books(Base): class Books(Base):
__tablename__ = 'books' __tablename__ = 'books'
...@@ -207,17 +216,24 @@ class Books(Base): ...@@ -207,17 +216,24 @@ class Books(Base):
self.has_cover = has_cover self.has_cover = has_cover
def __repr__(self): def __repr__(self):
return u"<Books('{0},{1}{2}{3}{4}{5}{6}{7}{8}')>".format(self.title, self.sort, self.author_sort, self.timestamp, self.pubdate, self.series_index, self.last_modified ,self.path, self.has_cover) return u"<Books('{0},{1}{2}{3}{4}{5}{6}{7}{8}')>".format(self.title, self.sort, self.author_sort,
self.timestamp, self.pubdate, self.series_index,
self.last_modified, self.path, self.has_cover)
for id in cc_ids: for id in cc_ids:
if id[1] == 'bool': 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: 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): class Custom_Columns(Base):
__tablename__ = 'custom_columns' __tablename__ = 'custom_columns'
id = Column(Integer,primary_key=True) id = Column(Integer, primary_key=True)
label = Column(String) label = Column(String)
name = Column(String) name = Column(String)
datatype = Column(String) datatype = Column(String)
...@@ -231,9 +247,7 @@ class Custom_Columns(Base): ...@@ -231,9 +247,7 @@ class Custom_Columns(Base):
display_dict = ast.literal_eval(self.display) display_dict = ast.literal_eval(self.display)
return display_dict return display_dict
#Base.metadata.create_all(engine) # Base.metadata.create_all(engine)
Session = sessionmaker() Session = sessionmaker()
Session.configure(bind=engine) Session.configure(bind=engine)
session = Session() session = Session()
...@@ -3,8 +3,9 @@ from lxml import etree ...@@ -3,8 +3,9 @@ from lxml import etree
import os import os
import uploader import uploader
def extractCover(zip, coverFile, tmp_file_name): def extractCover(zip, coverFile, tmp_file_name):
if (coverFile is None): if coverFile is None:
return None return None
else: else:
cf = zip.read("OPS/" + coverFile) cf = zip.read("OPS/" + coverFile)
...@@ -16,35 +17,34 @@ def extractCover(zip, coverFile, tmp_file_name): ...@@ -16,35 +17,34 @@ def extractCover(zip, coverFile, tmp_file_name):
return tmp_cover_name return tmp_cover_name
def get_epub_info(tmp_file_path, original_file_name, original_file_extension): def get_epub_info(tmp_file_path, original_file_name, original_file_extension):
ns = { ns = {
'n':'urn:oasis:names:tc:opendocument:xmlns:container', 'n': 'urn:oasis:names:tc:opendocument:xmlns:container',
'pkg':'http://www.idpf.org/2007/opf', 'pkg': 'http://www.idpf.org/2007/opf',
'dc':'http://purl.org/dc/elements/1.1/' 'dc': 'http://purl.org/dc/elements/1.1/'
} }
zip = zipfile.ZipFile(tmp_file_path) zip = zipfile.ZipFile(tmp_file_path)
txt = zip.read('META-INF/container.xml') txt = zip.read('META-INF/container.xml')
tree = etree.fromstring(txt) 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) cf = zip.read(cfname)
tree = etree.fromstring(cf) 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 = {} epub_metadata = {}
for s in ['title', 'description', 'creator']: for s in ['title', 'description', 'creator']:
tmp = p.xpath('dc:%s/text()'%(s),namespaces=ns) tmp = p.xpath('dc:%s/text()' % s, namespaces=ns)
if (len(tmp) > 0): if len(tmp) > 0:
epub_metadata[s] = p.xpath('dc:%s/text()'%(s),namespaces=ns)[0] epub_metadata[s] = p.xpath('dc:%s/text()' % s, namespaces=ns)[0]
else: else:
epub_metadata[s] = "Unknown" epub_metadata[s] = "Unknown"
coversection = tree.xpath("/pkg:package/pkg:manifest/pkg:item[@id='cover']/@href",namespaces=ns) coversection = tree.xpath("/pkg:package/pkg:manifest/pkg:item[@id='cover']/@href", namespaces=ns)
if (len(coversection) > 0): if len(coversection) > 0:
coverfile = extractCover(zip, coversection[0], tmp_file_path) coverfile = extractCover(zip, coversection[0], tmp_file_path)
else: else:
coverfile = None coverfile = None
...@@ -53,15 +53,13 @@ def get_epub_info(tmp_file_path, original_file_name, original_file_extension): ...@@ -53,15 +53,13 @@ def get_epub_info(tmp_file_path, original_file_name, original_file_extension):
else: else:
title = epub_metadata['title'] title = epub_metadata['title']
return uploader.BookMeta( return uploader.BookMeta(
file_path = tmp_file_path, file_path=tmp_file_path,
extension = original_file_extension, extension=original_file_extension,
title = title, title=title,
author = epub_metadata['creator'], author=epub_metadata['creator'],
cover = coverfile, cover=coverfile,
description = epub_metadata['description'], description=epub_metadata['description'],
tags = "", tags="",
series = "", series="",
series_id="") series_id="")
...@@ -7,29 +7,31 @@ import uploader ...@@ -7,29 +7,31 @@ import uploader
def get_fb2_info(tmp_file_path, original_file_name, original_file_extension): def get_fb2_info(tmp_file_path, original_file_name, original_file_extension):
ns = { ns = {
'fb':'http://www.gribuser.ru/xml/fictionbook/2.0', 'fb': 'http://www.gribuser.ru/xml/fictionbook/2.0',
'l':'http://www.w3.org/1999/xlink', 'l': 'http://www.w3.org/1999/xlink',
} }
fb2_file = open(tmp_file_path) fb2_file = open(tmp_file_path)
tree = etree.fromstring(fb2_file.read()) tree = etree.fromstring(fb2_file.read())
authors = tree.xpath('/fb:FictionBook/fb:description/fb:title-info/fb:author', namespaces=ns) authors = tree.xpath('/fb:FictionBook/fb:description/fb:title-info/fb:author', namespaces=ns)
def get_author(element): 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)) author = ", ".join(map(get_author, authors))
title = unicode(tree.xpath('/fb:FictionBook/fb:description/fb:title-info/fb:book-title/text()', namespaces=ns)[0]) 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( return uploader.BookMeta(
file_path = tmp_file_path, file_path=tmp_file_path,
extension = original_file_extension, extension=original_file_extension,
title = title, title=title,
author = author, author=author,
cover = None, cover=None,
description = description, description=description,
tags = "", tags="",
series = "", series="",
series_id="") series_id="")
...@@ -4,8 +4,10 @@ ...@@ -4,8 +4,10 @@
import db, ub import db, ub
import config import config
from flask import current_app as app from flask import current_app as app
import logging
import smtplib import smtplib
import tempfile
import socket import socket
import sys import sys
import os import os
...@@ -21,16 +23,19 @@ from email.generator import Generator ...@@ -21,16 +23,19 @@ from email.generator import Generator
from flask_babel import gettext as _ from flask_babel import gettext as _
import subprocess import subprocess
def update_download(book_id, user_id): 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: if not check:
new_download = ub.Downloads(user_id=user_id, book_id=book_id) new_download = ub.Downloads(user_id=user_id, book_id=book_id)
ub.session.add(new_download) ub.session.add(new_download)
ub.session.commit() ub.session.commit()
def make_mobi(book_id): def make_mobi(book_id):
if sys.platform =="win32": if sys.platform == "win32":
kindlegen = os.path.join(config.MAIN_DIR, "vendor", u"kindlegen.exe") kindlegen = os.path.join(config.MAIN_DIR, "vendor", u"kindlegen.exe")
else: else:
kindlegen = os.path.join(config.MAIN_DIR, "vendor", u"kindlegen") kindlegen = os.path.join(config.MAIN_DIR, "vendor", u"kindlegen")
...@@ -45,9 +50,17 @@ def make_mobi(book_id): ...@@ -45,9 +50,17 @@ def make_mobi(book_id):
file_path = os.path.join(config.DB_ROOT, book.path, data.name) file_path = os.path.join(config.DB_ROOT, book.path, data.name)
if os.path.exists(file_path + u".epub"): if os.path.exists(file_path + u".epub"):
p = subprocess.Popen((kindlegen + " \"" + file_path + u".epub\" ").encode(sys.getfilesystemencoding()), shell=True, stdout=subprocess.PIPE, p = subprocess.Popen((kindlegen + " \"" + file_path + u".epub\" ").encode(sys.getfilesystemencoding()),
stderr=subprocess.PIPE, stdin=subprocess.PIPE) shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE, stdin=subprocess.PIPE)
check = p.wait() # 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: if not check or check < 2:
book.data.append(db.Data( book.data.append(db.Data(
name=book.data[0].name, name=book.data[0].name,
...@@ -64,8 +77,67 @@ def make_mobi(book_id): ...@@ -64,8 +77,67 @@ def make_mobi(book_id):
app.logger.error("make_mobie: epub not found: %s.epub" % file_path) app.logger.error("make_mobie: epub not found: %s.epub" % file_path)
return None 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): def send_mail(book_id, kindle_mail):
'''Send email with attachments''' """Send email with attachments"""
is_mobi = False is_mobi = False
is_azw = False is_azw = False
is_azw3 = False is_azw3 = False
...@@ -84,7 +156,7 @@ def send_mail(book_id, kindle_mail): ...@@ -84,7 +156,7 @@ def send_mail(book_id, kindle_mail):
use_ssl = settings.get('mail_use_ssl', 0) use_ssl = settings.get('mail_use_ssl', 0)
# attach files # 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() book = db.session.query(db.Books).filter(db.Books.id == book_id).first()
data = db.session.query(db.Data).filter(db.Data.book == book.id) data = db.session.query(db.Data).filter(db.Data.book == book.id)
...@@ -125,8 +197,13 @@ def send_mail(book_id, kindle_mail): ...@@ -125,8 +197,13 @@ def send_mail(book_id, kindle_mail):
# send email # send email
try: try:
mailserver = smtplib.SMTP(settings["mail_server"],settings["mail_port"]) timeout=600 # set timeout to 5mins
mailserver.set_debuglevel(0)
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: if int(use_ssl) == 1:
mailserver.ehlo() mailserver.ehlo()
...@@ -137,6 +214,9 @@ def send_mail(book_id, kindle_mail): ...@@ -137,6 +214,9 @@ def send_mail(book_id, kindle_mail):
mailserver.login(settings["mail_login"], settings["mail_password"]) mailserver.login(settings["mail_login"], settings["mail_password"])
mailserver.sendmail(settings["mail_login"], kindle_mail, msg) mailserver.sendmail(settings["mail_login"], kindle_mail, msg)
mailserver.quit() mailserver.quit()
smtplib.stderr = org_stderr
except (socket.error, smtplib.SMTPRecipientsRefused, smtplib.SMTPException), e: except (socket.error, smtplib.SMTPRecipientsRefused, smtplib.SMTPException), e:
app.logger.error(traceback.print_exc()) app.logger.error(traceback.print_exc())
return _("Failed to send mail: %s" % str(e)) return _("Failed to send mail: %s" % str(e))
...@@ -145,7 +225,7 @@ def send_mail(book_id, kindle_mail): ...@@ -145,7 +225,7 @@ def send_mail(book_id, kindle_mail):
def get_attachment(file_path): def get_attachment(file_path):
'''Get file as MIMEBase message''' """Get file as MIMEBase message"""
try: try:
file_ = open(file_path, 'rb') file_ = open(file_path, 'rb')
...@@ -163,6 +243,7 @@ def get_attachment(file_path): ...@@ -163,6 +243,7 @@ def get_attachment(file_path):
'permissions?')) 'permissions?'))
return None return None
def get_valid_filename(value, replace_whitespace=True): def get_valid_filename(value, replace_whitespace=True):
""" """
Returns the given string converted to a string that can be used for a clean 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): ...@@ -178,6 +259,7 @@ def get_valid_filename(value, replace_whitespace=True):
value = value.replace(u"\u00DF", "ss") value = value.replace(u"\u00DF", "ss")
return value return value
def get_normalized_author(value): def get_normalized_author(value):
""" """
Normalizes sorted author name Normalizes sorted author name
...@@ -187,13 +269,14 @@ def get_normalized_author(value): ...@@ -187,13 +269,14 @@ def get_normalized_author(value):
value = " ".join(value.split(", ")[::-1]) value = " ".join(value.split(", ")[::-1])
return value return value
def update_dir_stucture(book_id): 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() book = db.session.query(db.Books).filter(db.Books.id == book_id).first()
path = os.path.join(config.DB_ROOT, book.path) path = os.path.join(config.DB_ROOT, book.path)
authordir = book.path.split(os.sep)[0] authordir = book.path.split(os.sep)[0]
new_authordir=get_valid_filename(book.authors[0].name, False) new_authordir = get_valid_filename(book.authors[0].name, False)
titledir = book.path.split(os.sep)[1] titledir = book.path.split(os.sep)[1]
new_titledir = get_valid_filename(book.title, False) + " (" + str(book_id) + ")" new_titledir = get_valid_filename(book.title, False) + " (" + str(book_id) + ")"
...@@ -208,4 +291,3 @@ def update_dir_stucture(book_id): ...@@ -208,4 +291,3 @@ def update_dir_stucture(book_id):
os.renames(path, new_author_path) os.renames(path, new_author_path)
book.path = new_authordir + os.sep + book.path.split(os.sep)[1] book.path = new_authordir + os.sep + book.path.split(os.sep)[1]
db.session.commit() db.session.commit()
...@@ -27,7 +27,8 @@ ...@@ -27,7 +27,8 @@
<label for="mail_from">{{_('From e-mail')}}</label> <label for="mail_from">{{_('From e-mail')}}</label>
<input type="text" class="form-control" name="mail_from" id="mail_from" value="{{content.mail_from}}"> <input type="text" class="form-control" name="mail_from" id="mail_from" value="{{content.mail_from}}">
</div> </div>
<button type="submit" class="btn btn-default">{{_('Submit')}}</button> <button type="submit" name="submit" value="submit" class="btn btn-default">{{_('Save settings')}}</button>
<button type="submit" name="test" value="test" class="btn btn-default">{{_('Save settings and send Test E-Mail')}}</button>
<a href="{{ url_for('user_list') }}" class="btn btn-default">{{_('Back')}}</a> <a href="{{ url_for('user_list') }}" class="btn btn-default">{{_('Back')}}</a>
</form> </form>
......
{% extends "layout.html" %} {% extends "layout.html" %}
{% block body %} {% block body %}
<div class="discover"> <h3>{{_('Linked libraries')}}</h3>
<h2>{{bookcounter}} {{_('Books in this Library')}}</h2>
<h2>{{authorcounter}} {{_('Authors in this Library')}}</h2> <table class="table">
</div> <thead>
<tr>
<th>{{_('Program library')}}</th>
<th>{{_('Installed Version')}}</th>
</tr>
</thead>
<tbody>
<tr>
<th>Python</th>
<td>{{Versions['PythonVersion']}}</td>
</tr>
<tr>
<th>Kindlegen</th>
<td>{{Versions['KindlegenVersion']}}</td>
</tr>
<tr>
<th>ImageMagick</th>
<td>{{Versions['ImageVersion']}}</td>
</tr>
<tr>
<th>PyPDF2</th>
<td>{{Versions['PyPdfVersion']}}</td>
</tr>
</tbody>
</table>
<h3>{{_('Calibre library statistics')}}</h3>
<table class="table">
<tbody>
<tr>
<th>{{bookcounter}}</th>
<td>{{_('Books in this Library')}}</td>
</tr>
<tr>
<th>{{authorcounter}}</th>
<td>{{_('Authors in this Library')}}</td>
</tr>
</tbody>
</table>
{% endblock %} {% endblock %}
...@@ -40,7 +40,6 @@ ...@@ -40,7 +40,6 @@
{% endfor %} {% endfor %}
</select> </select>
</div> </div>
{% if g.user and g.user.role_admin() and not profile %}
<div class="form-group"> <div class="form-group">
<input type="checkbox" name="show_random" {% if content.random_books %}checked{% endif %}> <input type="checkbox" name="show_random" {% if content.random_books %}checked{% endif %}>
<label for="show_random">{{_('Show random books')}}</label> <label for="show_random">{{_('Show random books')}}</label>
...@@ -62,6 +61,8 @@ ...@@ -62,6 +61,8 @@
<label for="show_category">{{_('Show category selection')}}</label> <label for="show_category">{{_('Show category selection')}}</label>
</div> </div>
{% if g.user and g.user.role_admin() and not profile %}
<div class="form-group"> <div class="form-group">
<input type="checkbox" name="admin_role" id="admin_role" {% if content.role_admin() %}checked{% endif %}> <input type="checkbox" name="admin_role" id="admin_role" {% if content.role_admin() %}checked{% endif %}>
<label for="admin_role">{{_('Admin user')}}</label> <label for="admin_role">{{_('Admin user')}}</label>
......
...@@ -7,7 +7,7 @@ msgid "" ...@@ -7,7 +7,7 @@ msgid ""
msgstr "" msgstr ""
"Project-Id-Version: PROJECT VERSION\n" "Project-Id-Version: PROJECT VERSION\n"
"Report-Msgid-Bugs-To: EMAIL@ADDRESS\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" "PO-Revision-Date: 2016-07-12 19:54+0200\n"
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n" "Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
"Language: de\n" "Language: de\n"
...@@ -18,250 +18,272 @@ msgstr "" ...@@ -18,250 +18,272 @@ msgstr ""
"Content-Transfer-Encoding: 8bit\n" "Content-Transfer-Encoding: 8bit\n"
"Generated-By: Babel 2.3.4\n" "Generated-By: Babel 2.3.4\n"
#: cps/helper.py:80 cps/templates/detail.html:113 #: cps/book_formats.py:108 cps/book_formats.py:112 cps/web.py:923
msgid "Send to Kindle" msgid "not installed"
msgstr "An Kindle senden" 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." msgid "This email has been sent via calibre web."
msgstr "Die E-Mail wurde via calibre-web versendet" 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" msgid "Could not find any formats suitable for sending by email"
msgstr "" msgstr ""
"Konnte keine Formate finden welche für das versenden per E-Mail geeignet " "Konnte keine Formate finden welche für das versenden per E-Mail geeignet "
"sind" "sind"
#: cps/helper.py:112 #: cps/helper.py:178
msgid "Could not convert epub to mobi" msgid "Could not convert epub to mobi"
msgstr "Konnte .epub nicht nach .mobi convertieren" msgstr "Konnte .epub nicht nach .mobi konvertieren"
#: cps/helper.py:142
#, python-format
msgid "Failed to send mail: %s"
msgstr "E-Mail: %s konnte nicht gesendet werden"
#: cps/helper.py:162 #: cps/helper.py:236
msgid "The requested file could not be read. Maybe wrong permissions?" msgid "The requested file could not be read. Maybe wrong permissions?"
msgstr "Die angeforderte Datei konnte nicht gelesen werden. Falsche Dateirechte?" msgstr "Die angeforderte Datei konnte nicht gelesen werden. Falsche Dateirechte?"
#: cps/web.py:639 #: cps/web.py:717
msgid "Latest Books" msgid "Latest Books"
msgstr "Letzte Bücher" msgstr "Letzte Bücher"
#: cps/web.py:661 #: cps/web.py:742
msgid "Hot Books (most downloaded)" msgid "Hot Books (most downloaded)"
msgstr "Beliebte Bücher (die meisten Downloads)" 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" msgid "Random Books"
msgstr "Zufällige Bücher" msgstr "Zufällige Bücher"
#: cps/web.py:679 #: cps/web.py:763
msgid "Author list" msgid "Author list"
msgstr "Autorenliste" msgstr "Autorenliste"
#: cps/web.py:695 #: cps/web.py:780
#, python-format #, python-format
msgid "Author: %(nam)s" msgid "Author: %(nam)s"
msgstr "Autor: %(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" msgid "Series list"
msgstr "Liste Serien" msgstr "Liste Serien"
#: cps/web.py:714 #: cps/web.py:804
#, python-format #, python-format
msgid "Series: %(serie)s" msgid "Series: %(serie)s"
msgstr "Serie: %(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:" msgid "Error opening eBook. File does not exist or file is not accessible:"
msgstr "" msgstr ""
"Buch öffnen fehlgeschlagen. Datei existiert nicht, oder ist nicht " "Buch öffnen fehlgeschlagen. Datei existiert nicht, oder ist nicht "
"zugänglich." "zugänglich."
#: cps/web.py:742 #: cps/web.py:837
msgid "Available languages" msgid "Available languages"
msgstr "Verfügbare Sprachen" msgstr "Verfügbare Sprachen"
#: cps/web.py:754 #: cps/web.py:852
#, python-format #, python-format
msgid "Language: %(name)s" msgid "Language: %(name)s"
msgstr "Sprache: %(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" msgid "Category list"
msgstr "Kategorieliste" msgstr "Kategorieliste"
#: cps/web.py:772 #: cps/web.py:875
#, python-format #, python-format
msgid "Category: %(name)s" msgid "Category: %(name)s"
msgstr "Kategorie: %(name)s" msgstr "Kategorie: %(name)s"
#: cps/web.py:810 #: cps/web.py:931
msgid "Statistics" msgid "Statistics"
msgstr "Statistiken" 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" msgid "Read a Book"
msgstr "Lese ein Buch" 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!" msgid "Please fill out all fields!"
msgstr "Bitte alle Felder ausfüllen!" msgstr "Bitte alle Felder ausfüllen!"
#: cps/web.py:967 #: cps/web.py:1116
msgid "An unknown error occured. Please try again later." msgid "An unknown error occured. Please try again later."
msgstr "Es ist ein unbekannter Fehler aufgetreten. Bitte später erneut versuchen." 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." msgid "This username or email address is already in use."
msgstr "Der Benutzername oder die E-Mailadresse ist in bereits in Benutzung." msgstr "Der Benutzername oder die E-Mailadresse ist in bereits in Benutzung."
#: cps/web.py:975 #: cps/web.py:1124
msgid "register" msgid "register"
msgstr "Registieren" msgstr "Registieren"
#: cps/web.py:990 #: cps/web.py:1140
#, python-format #, python-format
msgid "you are now logged in as: '%(nickname)s'" msgid "you are now logged in as: '%(nickname)s'"
msgstr "Du bist nun eingeloggt als '%(nickname)s'" msgstr "Du bist nun eingeloggt als '%(nickname)s'"
#: cps/web.py:993 #: cps/web.py:1143
msgid "Wrong Username or Password" msgid "Wrong Username or Password"
msgstr "Flascher Benutzername oder Passwort" msgstr "Flascher Benutzername oder Passwort"
#: cps/web.py:995 #: cps/web.py:1145
msgid "login" msgid "login"
msgstr "Login" msgstr "Login"
#: cps/web.py:1011 #: cps/web.py:1162
msgid "Please configure the SMTP mail settings first..." msgid "Please configure the SMTP mail settings first..."
msgstr "Bitte zuerst die SMTP Mail Einstellung konfigurieren ..." msgstr "Bitte zuerst die SMTP Mail Einstellung konfigurieren ..."
#: cps/web.py:1015 #: cps/web.py:1166
#, python-format #, python-format
msgid "Book successfully send to %(kindlemail)s" msgid "Book successfully send to %(kindlemail)s"
msgstr "Buch erfolgreich versandt an %(kindlemail)s" msgstr "Buch erfolgreich versandt an %(kindlemail)s"
#: cps/web.py:1018 #: cps/web.py:1170
#, python-format #, python-format
msgid "There was an error sending this book: %(res)s" msgid "There was an error sending this book: %(res)s"
msgstr "Beim Senden des Buchs trat ein Fehler auf: %(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..." msgid "Please configure your kindle email address first..."
msgstr "Bitte die Kindle E-Mail Adresse zuuerst konfigurieren..." msgstr "Bitte die Kindle E-Mail Adresse zuuerst konfigurieren..."
#: cps/web.py:1035 #: cps/web.py:1188
#, python-format #, python-format
msgid "Book has been added to shelf: %(sname)s" msgid "Book has been added to shelf: %(sname)s"
msgstr "Das Buch wurde dem Bücherregal: %(sname)s hinzugefügt" msgstr "Das Buch wurde dem Bücherregal: %(sname)s hinzugefügt"
#: cps/web.py:1054 #: cps/web.py:1209
#, python-format #, python-format
msgid "Book has been removed from shelf: %(sname)s" msgid "Book has been removed from shelf: %(sname)s"
msgstr "Das Buch wurde aus dem Bücherregal: %(sname)s entfernt" msgstr "Das Buch wurde aus dem Bücherregal: %(sname)s entfernt"
#: cps/web.py:1070 #: cps/web.py:1226
#, python-format #, python-format
msgid "A shelf with the name '%(title)s' already exists." msgid "A shelf with the name '%(title)s' already exists."
msgstr "Es existiert bereits ein Bücheregal mit dem Titel '%(title)s'" msgstr "Es existiert bereits ein Bücheregal mit dem Titel '%(title)s'"
#: cps/web.py:1075 #: cps/web.py:1231
#, python-format #, python-format
msgid "Shelf %(title)s created" msgid "Shelf %(title)s created"
msgstr "Bücherregal %(title)s erzeugt" msgstr "Bücherregal %(title)s erzeugt"
#: cps/web.py:1077 #: cps/web.py:1233
msgid "There was an error" msgid "There was an error"
msgstr "Es trat ein Fehler auf" 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" msgid "create a shelf"
msgstr "Bücherregal erzeugen" msgstr "Bücherregal erzeugen"
#: cps/web.py:1096 #: cps/web.py:1256
#, python-format #, python-format
msgid "successfully deleted shelf %(name)s" msgid "successfully deleted shelf %(name)s"
msgstr "Bücherregal %(name)s erfolgreich gelöscht" msgstr "Bücherregal %(name)s erfolgreich gelöscht"
#: cps/web.py:1113 #: cps/web.py:1277
#, python-format #, python-format
msgid "Shelf: '%(name)s'" msgid "Shelf: '%(name)s'"
msgstr "Bücherregal: '%(name)s'" msgstr "Bücherregal: '%(name)s'"
#: cps/web.py:1150 #: cps/web.py:1332
msgid "Found an existing account for this email address." msgid "Found an existing account for this email address."
msgstr "Es existiert ein Benutzerkonto für diese E-Mailadresse" 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 #, python-format
msgid "%(name)s's profile" msgid "%(name)s's profile"
msgstr "%(name)s's Profil" msgstr "%(name)s's Profil"
#: cps/web.py:1152 #: cps/web.py:1335
msgid "Profile updated" msgid "Profile updated"
msgstr "Profil aktualisiert" msgstr "Profil aktualisiert"
#: cps/web.py:1161 #: cps/web.py:1346
msgid "User list" msgid "User list"
msgstr "Benutzerliste" 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" msgid "Add new user"
msgstr "Neuen Benutzer hinzufügen" msgstr "Neuen Benutzer hinzufügen"
#: cps/web.py:1213 #: cps/web.py:1399
#, python-format #, python-format
msgid "User '%(user)s' created" msgid "User '%(user)s' created"
msgstr "Benutzer '%(user)s' angelegt" msgstr "Benutzer '%(user)s' angelegt"
#: cps/web.py:1217 #: cps/web.py:1403
msgid "Found an existing account for this email address or nickname." msgid "Found an existing account for this email address or nickname."
msgstr "" msgstr ""
"Es existiert ein Benutzerkonto für diese Emailadresse oder den " "Es existiert ein Benutzerkonto für diese Emailadresse oder den "
"Benutzernamen" "Benutzernamen"
#: cps/web.py:1238 #: cps/web.py:1426 cps/web.py:1437
msgid "Mail settings updated" msgid "Mail settings updated"
msgstr "E-Mail Einstellungen aktualisiert" 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" msgid "Edit mail settings"
msgstr "E-Mail Einstellungen editieren" msgstr "E-Mail Einstellungen editieren"
#: cps/web.py:1263 #: cps/web.py:1461
#, python-format #, python-format
msgid "User '%(nick)s' deleted" msgid "User '%(nick)s' deleted"
msgstr "Benutzer '%(nick)s' gelöscht" msgstr "Benutzer '%(nick)s' gelöscht"
#: cps/web.py:1318 #: cps/web.py:1516
#, python-format #, python-format
msgid "User '%(nick)s' updated" msgid "User '%(nick)s' updated"
msgstr "Benutzer '%(nick)s' aktualisiert" msgstr "Benutzer '%(nick)s' aktualisiert"
#: cps/web.py:1321 #: cps/web.py:1519
msgid "An unknown error occured." msgid "An unknown error occured."
msgstr "Es ist ein unbekanter Fehler aufgetreten" msgstr "Es ist ein unbekanter Fehler aufgetreten"
#: cps/web.py:1322 #: cps/web.py:1521
#, python-format #, python-format
msgid "Edit User %(nick)s" msgid "Edit User %(nick)s"
msgstr "Benutzer %(nick)s bearbeiten" msgstr "Benutzer %(nick)s bearbeiten"
#: cps/web.py:1556 #: cps/web.py:1759
#, python-format #, python-format
msgid "Failed to create path %s (Permission denied)." msgid "Failed to create path %s (Permission denied)."
msgstr "Fehler beim Erzeugen des Pfads %s (Zugriff verweigert)" msgstr "Fehler beim Erzeugen des Pfads %s (Zugriff verweigert)"
#: cps/web.py:1561 #: cps/web.py:1764
#, python-format #, python-format
msgid "Failed to store file %s (Permission denied)." msgid "Failed to store file %s (Permission denied)."
msgstr "Fehler beim speichern der Datei %s (Zugriff verweigert)" msgstr "Fehler beim speichern der Datei %s (Zugriff verweigert)"
#: cps/web.py:1566 #: cps/web.py:1769
#, python-format #, python-format
msgid "Failed to delete file %s (Permission denied)." msgid "Failed to delete file %s (Permission denied)."
msgstr "Fehler beim Löschen von Datei %s (Zugriff verweigert)" msgstr "Fehler beim Löschen von Datei %s (Zugriff verweigert)"
...@@ -346,14 +368,14 @@ msgstr "Nein" ...@@ -346,14 +368,14 @@ msgstr "Nein"
msgid "view book after edit" msgid "view book after edit"
msgstr "Buch nach Bearbeitung ansehen" msgstr "Buch nach Bearbeitung ansehen"
#: cps/templates/edit_book.html:105 cps/templates/email_edit.html:30 #: cps/templates/edit_book.html:105 cps/templates/login.html:19
#: cps/templates/login.html:19 cps/templates/search_form.html:33 #: cps/templates/search_form.html:33 cps/templates/shelf_edit.html:15
#: cps/templates/shelf_edit.html:15 cps/templates/user_edit.html:93 #: cps/templates/user_edit.html:94
msgid "Submit" msgid "Submit"
msgstr "Abschicken" msgstr "Abschicken"
#: cps/templates/edit_book.html:106 cps/templates/email_edit.html:31 #: cps/templates/edit_book.html:106 cps/templates/email_edit.html:32
#: cps/templates/user_edit.html:95 #: cps/templates/user_edit.html:96
msgid "Back" msgid "Back"
msgstr "Zurück" msgstr "Zurück"
...@@ -381,6 +403,14 @@ msgstr "SMTP Passwort" ...@@ -381,6 +403,14 @@ msgstr "SMTP Passwort"
msgid "From e-mail" msgid "From e-mail"
msgstr "Absenderadresse" 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 #: cps/templates/feed.xml:14
msgid "Next" msgid "Next"
msgstr "Nächste" msgstr "Nächste"
...@@ -511,6 +541,14 @@ msgstr "Passwort" ...@@ -511,6 +541,14 @@ msgstr "Passwort"
msgid "Remember me" msgid "Remember me"
msgstr "Merken" 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 #: cps/templates/register.html:4
msgid "Register a new account" msgid "Register a new account"
msgstr "Neues Benutzerkonto erzeugen" msgstr "Neues Benutzerkonto erzeugen"
...@@ -551,6 +589,10 @@ msgstr "Tags ausschließen" ...@@ -551,6 +589,10 @@ msgstr "Tags ausschließen"
msgid "Delete this Shelf" msgid "Delete this Shelf"
msgstr "Lösche dieses Bücherregal" 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 #: cps/templates/shelf_edit.html:7
msgid "Title" msgid "Title"
msgstr "Titel" msgstr "Titel"
...@@ -559,11 +601,27 @@ msgstr "Titel" ...@@ -559,11 +601,27 @@ msgstr "Titel"
msgid "should the shelf be public?" msgid "should the shelf be public?"
msgstr "Soll das Bücherregal öffentlich sein?" 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" msgid "Books in this Library"
msgstr "Bücher in dieser Bibliothek" msgstr "Bücher in dieser Bibliothek"
#: cps/templates/stats.html:5 #: cps/templates/stats.html:41
msgid "Authors in this Library" msgid "Authors in this Library"
msgstr "Autoren in dieser Bibliothek" msgstr "Autoren in dieser Bibliothek"
...@@ -579,51 +637,51 @@ msgstr "Zeige nur Bücher mit dieser Sprache" ...@@ -579,51 +637,51 @@ msgstr "Zeige nur Bücher mit dieser Sprache"
msgid "Show all" msgid "Show all"
msgstr "Zeige alle" msgstr "Zeige alle"
#: cps/templates/user_edit.html:46 #: cps/templates/user_edit.html:45
msgid "Show random books" msgid "Show random books"
msgstr "Zeige Zufällige Bücher" msgstr "Zeige Zufällige Bücher"
#: cps/templates/user_edit.html:50 #: cps/templates/user_edit.html:49
msgid "Show hot books" msgid "Show hot books"
msgstr "Zeige Auswahl Beliebte Bücher" msgstr "Zeige Auswahl Beliebte Bücher"
#: cps/templates/user_edit.html:54 #: cps/templates/user_edit.html:53
msgid "Show language selection" msgid "Show language selection"
msgstr "Zeige Sprachauswahl" msgstr "Zeige Sprachauswahl"
#: cps/templates/user_edit.html:58 #: cps/templates/user_edit.html:57
msgid "Show series selection" msgid "Show series selection"
msgstr "Zeige Auswahl Serien" msgstr "Zeige Auswahl Serien"
#: cps/templates/user_edit.html:62 #: cps/templates/user_edit.html:61
msgid "Show category selection" msgid "Show category selection"
msgstr "Zeige Kategorie Auswahl" msgstr "Zeige Kategorie Auswahl"
#: cps/templates/user_edit.html:67 #: cps/templates/user_edit.html:68
msgid "Admin user" msgid "Admin user"
msgstr "Admin Benutzer" msgstr "Admin Benutzer"
#: cps/templates/user_edit.html:71 #: cps/templates/user_edit.html:72
msgid "Allow Downloads" msgid "Allow Downloads"
msgstr "Downloads erlauben" msgstr "Downloads erlauben"
#: cps/templates/user_edit.html:75 #: cps/templates/user_edit.html:76
msgid "Allow Uploads" msgid "Allow Uploads"
msgstr "Uploads erlauben" msgstr "Uploads erlauben"
#: cps/templates/user_edit.html:79 #: cps/templates/user_edit.html:80
msgid "Allow Edit" msgid "Allow Edit"
msgstr "Bearbeiten erlauben" msgstr "Bearbeiten erlauben"
#: cps/templates/user_edit.html:83 #: cps/templates/user_edit.html:84
msgid "Allow Changing Password" msgid "Allow Changing Password"
msgstr "Passwort ändern erlauben" msgstr "Passwort ändern erlauben"
#: cps/templates/user_edit.html:89 #: cps/templates/user_edit.html:90
msgid "Delete this user" msgid "Delete this user"
msgstr "Benutzer löschen" msgstr "Benutzer löschen"
#: cps/templates/user_edit.html:100 #: cps/templates/user_edit.html:101
msgid "Recent Downloads" msgid "Recent Downloads"
msgstr "Letzte Downloads" msgstr "Letzte Downloads"
...@@ -674,6 +732,3 @@ msgstr "SMTP Einstellungen ändern" ...@@ -674,6 +732,3 @@ msgstr "SMTP Einstellungen ändern"
msgid "Latin" msgid "Latin"
msgstr "Latein" msgstr "Latein"
#~ msgid "Version"
#~ msgstr "Version"
# Translations template for PROJECT. # French translations for PROJECT.
# Copyright (C) 2016 ORGANIZATION # Copyright (C) 2016 ORGANIZATION
# This file is distributed under the same license as the PROJECT project. # This file is distributed under the same license as the PROJECT project.
# FIRST AUTHOR <EMAIL@ADDRESS>, 2016. # FIRST AUTHOR <EMAIL@ADDRESS>, 2016.
...@@ -7,256 +7,281 @@ msgid "" ...@@ -7,256 +7,281 @@ msgid ""
msgstr "" msgstr ""
"Project-Id-Version: PROJECT VERSION\n" "Project-Id-Version: PROJECT VERSION\n"
"Report-Msgid-Bugs-To: EMAIL@ADDRESS\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" "PO-Revision-Date: 2016-11-13 18:35+0100\n"
"Last-Translator: Nicolas Roudninski <nicoroud@gmail.com>\n"
"Language: fr\n"
"Language-Team: \n" "Language-Team: \n"
"Plural-Forms: nplurals=2; plural=(n > 1)\n"
"MIME-Version: 1.0\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" "Content-Transfer-Encoding: 8bit\n"
"Generated-By: Babel 2.3.4\n" "Generated-By: Babel 2.3.4\n"
"X-Generator: Poedit 1.8.11\n"
"Last-Translator: Nicolas Roudninski <nicoroud@gmail.com>\n"
"Plural-Forms: nplurals=2; plural=(n > 1);\n"
"Language: fr_FR\n"
#: cps/helper.py:80 cps/templates/detail.html:113 #: cps/book_formats.py:108 cps/book_formats.py:112 cps/web.py:923
msgid "Send to Kindle" msgid "not installed"
msgstr "Envoyer ver Kindle" 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." msgid "This email has been sent via calibre web."
msgstr "Ce message a été envoyé depuis 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" msgid "Could not find any formats suitable for sending by email"
msgstr "Impossible de trouver un format adapté à envoyer par courriel" 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" msgid "Could not convert epub to mobi"
msgstr "Impossible de convertir epub vers mobi" msgstr "Impossible de convertir epub vers mobi"
#: cps/helper.py:142 #: cps/helper.py:236
#, python-format
msgid "Failed to send mail: %s"
msgstr "Impossible d'envoyer le courriel : %s"
#: cps/helper.py:162
msgid "The requested file could not be read. Maybe wrong permissions?" 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" msgid "Latest Books"
msgstr "Derniers livres" msgstr "Derniers livres"
#: cps/web.py:661 #: cps/web.py:742
msgid "Hot Books (most downloaded)" msgid "Hot Books (most downloaded)"
msgstr "Livres populaires (les plus téléchargés)" 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" msgid "Random Books"
msgstr "Livres au hasard" msgstr "Livres au hasard"
#: cps/web.py:679 #: cps/web.py:763
msgid "Author list" msgid "Author list"
msgstr "Liste des auteurs" msgstr "Liste des auteurs"
#: cps/web.py:695 #: cps/web.py:780
#, python-format #, python-format
msgid "Author: %(nam)s" msgid "Author: %(nam)s"
msgstr "Auteur : %(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" msgid "Series list"
msgstr "Liste des séries" msgstr "Liste des séries"
#: cps/web.py:714 #: cps/web.py:804
#, python-format #, python-format
msgid "Series: %(serie)s" msgid "Series: %(serie)s"
msgstr "Séries : %(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:" 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" msgid "Available languages"
msgstr "Langues disponibles" msgstr "Langues disponibles"
#: cps/web.py:754 #: cps/web.py:852
#, python-format #, python-format
msgid "Language: %(name)s" msgid "Language: %(name)s"
msgstr "Langue : %(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" msgid "Category list"
msgstr "Liste des catégories" msgstr "Liste des catégories"
#: cps/web.py:772 #: cps/web.py:875
#, python-format #, python-format
msgid "Category: %(name)s" msgid "Category: %(name)s"
msgstr "Catégorie : %(name)s" msgstr "Catégorie : %(name)s"
#: cps/web.py:810 #: cps/web.py:931
msgid "Statistics" msgid "Statistics"
msgstr "Statistiques" 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" msgid "Read a Book"
msgstr "Lire un livre" 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!" msgid "Please fill out all fields!"
msgstr "SVP, complétez tous les champs !" msgstr "SVP, complétez tous les champs !"
#: cps/web.py:967 #: cps/web.py:1116
msgid "An unknown error occured. Please try again later." msgid "An unknown error occured. Please try again later."
msgstr "Une erreur a eu lieu. Merci de réessayez plus tard." 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." 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." 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" msgid "register"
msgstr "S'enregistrer" msgstr "S'enregistrer"
#: cps/web.py:990 #: cps/web.py:1140
#, python-format #, python-format
msgid "you are now logged in as: '%(nickname)s'" msgid "you are now logged in as: '%(nickname)s'"
msgstr "Vous êtes maintenant connecté sous : '%(nickname)s'" msgstr "Vous êtes maintenant connecté sous : '%(nickname)s'"
#: cps/web.py:993 #: cps/web.py:1143
msgid "Wrong Username or Password" msgid "Wrong Username or Password"
msgstr "Mauvais nom d'utilisateur ou mot de passe" msgstr "Mauvais nom d'utilisateur ou mot de passe"
#: cps/web.py:995 #: cps/web.py:1145
msgid "login" msgid "login"
msgstr "Connexion" msgstr "Connexion"
#: cps/web.py:1011 #: cps/web.py:1162
msgid "Please configure the SMTP mail settings first..." msgid "Please configure the SMTP mail settings first..."
msgstr "Veillez configurer les paramètres smtp d'abord..." msgstr "Veillez configurer les paramètres smtp d'abord..."
#: cps/web.py:1015 #: cps/web.py:1166
#, python-format #, python-format
msgid "Book successfully send to %(kindlemail)s" msgid "Book successfully send to %(kindlemail)s"
msgstr "Livres envoyés à %(kindlemail)s avec succès" msgstr "Livres envoyés à %(kindlemail)s avec succès"
#: cps/web.py:1018 #: cps/web.py:1170
#, python-format #, python-format
msgid "There was an error sending this book: %(res)s" msgid "There was an error sending this book: %(res)s"
msgstr "Il y a eu une erreur en envoyant ce livre : %(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..." msgid "Please configure your kindle email address first..."
msgstr "Veuillez configurer votre adresse kindle d'abord..." msgstr "Veuillez configurer votre adresse kindle d'abord..."
#: cps/web.py:1035 #: cps/web.py:1188
#, python-format #, python-format
msgid "Book has been added to shelf: %(sname)s" msgid "Book has been added to shelf: %(sname)s"
msgstr "Le livre a bien été ajouté à l'étagère : %(sname)s" msgstr "Le livre a bien été ajouté à l'étagère : %(sname)s"
#: cps/web.py:1054 #: cps/web.py:1209
#, python-format #, python-format
msgid "Book has been removed from shelf: %(sname)s" msgid "Book has been removed from shelf: %(sname)s"
msgstr "Le livre a été supprimé de l'étagère %(sname)s" msgstr "Le livre a été supprimé de l'étagère %(sname)s"
#: cps/web.py:1070 #: cps/web.py:1226
#, python-format #, python-format
msgid "A shelf with the name '%(title)s' already exists." msgid "A shelf with the name '%(title)s' already exists."
msgstr "Une étagère de ce nom '%(title)s' existe déjà." msgstr "Une étagère de ce nom '%(title)s' existe déjà."
#: cps/web.py:1075 #: cps/web.py:1231
#, python-format #, python-format
msgid "Shelf %(title)s created" msgid "Shelf %(title)s created"
msgstr "Étagère %(title)s créée" msgstr "Étagère %(title)s créée"
#: cps/web.py:1077 #: cps/web.py:1233
msgid "There was an error" msgid "There was an error"
msgstr "Il y a eu une erreur" 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" msgid "create a shelf"
msgstr "Créer une étagère" msgstr "Créer une étagère"
#: cps/web.py:1096 #: cps/web.py:1256
#, python-format #, python-format
msgid "successfully deleted shelf %(name)s" msgid "successfully deleted shelf %(name)s"
msgstr "L'étagère %(name)s a été supprimé avec succès" msgstr "L'étagère %(name)s a été supprimé avec succès"
#: cps/web.py:1113 #: cps/web.py:1277
#, python-format #, python-format
msgid "Shelf: '%(name)s'" msgid "Shelf: '%(name)s'"
msgstr "Étagère : '%(name)s'" msgstr "Étagère : '%(name)s'"
#: cps/web.py:1150 #: cps/web.py:1332
msgid "Found an existing account for this email address." msgid "Found an existing account for this email address."
msgstr "Un compte avec cette adresse de courriel existe déjà." 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 #, python-format
msgid "%(name)s's profile" msgid "%(name)s's profile"
msgstr "Profil de %(name)s" msgstr "Profil de %(name)s"
#: cps/web.py:1152 #: cps/web.py:1335
msgid "Profile updated" msgid "Profile updated"
msgstr "Profil mis à jour" msgstr "Profil mis à jour"
#: cps/web.py:1161 #: cps/web.py:1346
msgid "User list" msgid "User list"
msgstr "Liste des ustilisateurs" 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" msgid "Add new user"
msgstr "Ajouter un nouvel utilisateur" msgstr "Ajouter un nouvel utilisateur"
#: cps/web.py:1213 #: cps/web.py:1399
#, python-format #, python-format
msgid "User '%(user)s' created" msgid "User '%(user)s' created"
msgstr "Utilisateur '%(user)s' créé" msgstr "Utilisateur '%(user)s' créé"
#: cps/web.py:1217 #: cps/web.py:1403
msgid "Found an existing account for this email address or nickname." 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à." 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" msgid "Mail settings updated"
msgstr "Paramètres de courriel mis à jour" 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" msgid "Edit mail settings"
msgstr "Éditer les paramètres de courriel" msgstr "Éditer les paramètres de courriel"
#: cps/web.py:1263 #: cps/web.py:1461
#, python-format #, python-format
msgid "User '%(nick)s' deleted" msgid "User '%(nick)s' deleted"
msgstr "Utilisateur '%(nick)s' supprimé" msgstr "Utilisateur '%(nick)s' supprimé"
#: cps/web.py:1318 #: cps/web.py:1516
#, python-format #, python-format
msgid "User '%(nick)s' updated" msgid "User '%(nick)s' updated"
msgstr "Utilisateur '%(nick)s' mis à jour" msgstr "Utilisateur '%(nick)s' mis à jour"
#: cps/web.py:1321 #: cps/web.py:1519
msgid "An unknown error occured." msgid "An unknown error occured."
msgstr "Oups ! Une erreur inconnue a eu lieu." msgstr "Oups ! Une erreur inconnue a eu lieu."
#: cps/web.py:1322 #: cps/web.py:1521
#, python-format #, python-format
msgid "Edit User %(nick)s" msgid "Edit User %(nick)s"
msgstr "Éditer l'utilisateur %(nick)s" msgstr "Éditer l'utilisateur %(nick)s"
#: cps/web.py:1556 #: cps/web.py:1759
#, python-format #, python-format
msgid "Failed to create path %s (Permission denied)." msgid "Failed to create path %s (Permission denied)."
msgstr "Impossible de créer le chemin %s (permission refusée)" msgstr "Impossible de créer le chemin %s (permission refusée)"
#: cps/web.py:1561 #: cps/web.py:1764
#, python-format #, python-format
msgid "Failed to store file %s (Permission denied)." msgid "Failed to store file %s (Permission denied)."
msgstr "Impossible d'enregistrer le fichier %s (permission refusée)" msgstr "Impossible d'enregistrer le fichier %s (permission refusée)"
#: cps/web.py:1566 #: cps/web.py:1769
#, python-format #, python-format
msgid "Failed to delete file %s (Permission denied)." msgid "Failed to delete file %s (Permission denied)."
msgstr "Impossible de supprimer le fichier %s (permission refusée)" msgstr "Impossible de supprimer le fichier %s (permission refusée)"
...@@ -341,14 +366,14 @@ msgstr "Non" ...@@ -341,14 +366,14 @@ msgstr "Non"
msgid "view book after edit" msgid "view book after edit"
msgstr "Voir le livre après l'édition" msgstr "Voir le livre après l'édition"
#: cps/templates/edit_book.html:105 cps/templates/email_edit.html:30 #: cps/templates/edit_book.html:105 cps/templates/login.html:19
#: cps/templates/login.html:19 cps/templates/search_form.html:33 #: cps/templates/search_form.html:33 cps/templates/shelf_edit.html:15
#: cps/templates/shelf_edit.html:15 cps/templates/user_edit.html:93 #: cps/templates/user_edit.html:94
msgid "Submit" msgid "Submit"
msgstr "Soumettre" msgstr "Soumettre"
#: cps/templates/edit_book.html:106 cps/templates/email_edit.html:31 #: cps/templates/edit_book.html:106 cps/templates/email_edit.html:32
#: cps/templates/user_edit.html:95 #: cps/templates/user_edit.html:96
msgid "Back" msgid "Back"
msgstr "Retour" msgstr "Retour"
...@@ -376,6 +401,14 @@ msgstr "Mot de passe smtp" ...@@ -376,6 +401,14 @@ msgstr "Mot de passe smtp"
msgid "From e-mail" msgid "From e-mail"
msgstr "Expéditeur des courriels" 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 #: cps/templates/feed.xml:14
msgid "Next" msgid "Next"
msgstr "Suivant" msgstr "Suivant"
...@@ -506,6 +539,14 @@ msgstr "Mot de passe" ...@@ -506,6 +539,14 @@ msgstr "Mot de passe"
msgid "Remember me" msgid "Remember me"
msgstr "Se rappeler de moi" 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 #: cps/templates/register.html:4
msgid "Register a new account" msgid "Register a new account"
msgstr "Enregistrer un nouveau compte" msgstr "Enregistrer un nouveau compte"
...@@ -546,6 +587,10 @@ msgstr "Exclure des étiquettes" ...@@ -546,6 +587,10 @@ msgstr "Exclure des étiquettes"
msgid "Delete this Shelf" msgid "Delete this Shelf"
msgstr "Effacer cette étagère" msgstr "Effacer cette étagère"
#: cps/templates/shelf.html:7
msgid "Edit Shelf name"
msgstr ""
#: cps/templates/shelf_edit.html:7 #: cps/templates/shelf_edit.html:7
msgid "Title" msgid "Title"
msgstr "Titre" msgstr "Titre"
...@@ -554,11 +599,27 @@ msgstr "Titre" ...@@ -554,11 +599,27 @@ msgstr "Titre"
msgid "should the shelf be public?" msgid "should the shelf be public?"
msgstr "Cette étagère doit-elle être publique ?" 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" msgid "Books in this Library"
msgstr "Livres dans la bibiothèque" msgstr "Livres dans la bibiothèque"
#: cps/templates/stats.html:5 #: cps/templates/stats.html:41
msgid "Authors in this Library" msgid "Authors in this Library"
msgstr "Auteurs dans la bibliothèque" msgstr "Auteurs dans la bibliothèque"
...@@ -574,51 +635,51 @@ msgstr "Montrer les livres dans la langue" ...@@ -574,51 +635,51 @@ msgstr "Montrer les livres dans la langue"
msgid "Show all" msgid "Show all"
msgstr "Montrer tout" msgstr "Montrer tout"
#: cps/templates/user_edit.html:46 #: cps/templates/user_edit.html:45
msgid "Show random books" msgid "Show random books"
msgstr "Montrer des livres au hasard" msgstr "Montrer des livres au hasard"
#: cps/templates/user_edit.html:50 #: cps/templates/user_edit.html:49
msgid "Show hot books" msgid "Show hot books"
msgstr "Montrer les livres populaires" msgstr "Montrer les livres populaires"
#: cps/templates/user_edit.html:54 #: cps/templates/user_edit.html:53
msgid "Show language selection" msgid "Show language selection"
msgstr "Montrer la sélection de la langue" msgstr "Montrer la sélection de la langue"
#: cps/templates/user_edit.html:58 #: cps/templates/user_edit.html:57
msgid "Show series selection" msgid "Show series selection"
msgstr "Montrer la sélection des séries" msgstr "Montrer la sélection des séries"
#: cps/templates/user_edit.html:62 #: cps/templates/user_edit.html:61
msgid "Show category selection" msgid "Show category selection"
msgstr "Montrer la sélection des catégories" msgstr "Montrer la sélection des catégories"
#: cps/templates/user_edit.html:67 #: cps/templates/user_edit.html:68
msgid "Admin user" msgid "Admin user"
msgstr "Utilisateur admin" msgstr "Utilisateur admin"
#: cps/templates/user_edit.html:71 #: cps/templates/user_edit.html:72
msgid "Allow Downloads" msgid "Allow Downloads"
msgstr "Permettre les téléchargements" msgstr "Permettre les téléchargements"
#: cps/templates/user_edit.html:75 #: cps/templates/user_edit.html:76
msgid "Allow Uploads" msgid "Allow Uploads"
msgstr "Permettre les téléversements" msgstr "Permettre les téléversements"
#: cps/templates/user_edit.html:79 #: cps/templates/user_edit.html:80
msgid "Allow Edit" msgid "Allow Edit"
msgstr "Permettre l'édition" msgstr "Permettre l'édition"
#: cps/templates/user_edit.html:83 #: cps/templates/user_edit.html:84
msgid "Allow Changing Password" msgid "Allow Changing Password"
msgstr "Permettre le changement de mot de passe" msgstr "Permettre le changement de mot de passe"
#: cps/templates/user_edit.html:89 #: cps/templates/user_edit.html:90
msgid "Delete this user" msgid "Delete this user"
msgstr "Supprimer cet utilisateur" msgstr "Supprimer cet utilisateur"
#: cps/templates/user_edit.html:100 #: cps/templates/user_edit.html:101
msgid "Recent Downloads" msgid "Recent Downloads"
msgstr "Téléchargements récents" msgstr "Téléchargements récents"
...@@ -668,3 +729,4 @@ msgstr "Changer les paramètre smtp" ...@@ -668,3 +729,4 @@ msgstr "Changer les paramètre smtp"
msgid "Latin" msgid "Latin"
msgstr "Latin" msgstr "Latin"
...@@ -21,18 +21,19 @@ ROLE_EDIT = 8 ...@@ -21,18 +21,19 @@ ROLE_EDIT = 8
ROLE_PASSWD = 16 ROLE_PASSWD = 16
DEFAULT_PASS = "admin123" DEFAULT_PASS = "admin123"
class User(Base): class User(Base):
__tablename__ = 'user' __tablename__ = 'user'
id = Column(Integer, primary_key = True) id = Column(Integer, primary_key=True)
nickname = Column(String(64), unique = True) nickname = Column(String(64), unique=True)
email = Column(String(120), unique = True, default = "") email = Column(String(120), unique=True, default="")
role = Column(SmallInteger, default = ROLE_USER) role = Column(SmallInteger, default=ROLE_USER)
password = Column(String) password = Column(String)
kindle_mail = Column(String(120), default="") kindle_mail = Column(String(120), default="")
shelf = relationship('Shelf', backref = 'user', lazy = 'dynamic') shelf = relationship('Shelf', backref='user', lazy='dynamic')
whislist = relationship('Whislist', backref = 'user', lazy = 'dynamic') whislist = relationship('Whislist', backref='user', lazy='dynamic')
downloads = relationship('Downloads', backref= 'user', lazy = 'dynamic') downloads = relationship('Downloads', backref='user', lazy='dynamic')
locale = Column(String(2), default="en") locale = Column(String(2), default="en")
random_books = Column(Integer, default=1) random_books = Column(Integer, default=1)
language_books = Column(Integer, default=1) language_books = Column(Integer, default=1)
...@@ -43,26 +44,31 @@ class User(Base): ...@@ -43,26 +44,31 @@ class User(Base):
def is_authenticated(self): def is_authenticated(self):
return True return True
def role_admin(self): def role_admin(self):
if self.role is not None: if self.role is not None:
return True if self.role & ROLE_ADMIN == ROLE_ADMIN else False return True if self.role & ROLE_ADMIN == ROLE_ADMIN else False
else: else:
return False return False
def role_download(self): def role_download(self):
if self.role is not None: if self.role is not None:
return True if self.role & ROLE_DOWNLOAD == ROLE_DOWNLOAD else False return True if self.role & ROLE_DOWNLOAD == ROLE_DOWNLOAD else False
else: else:
return False return False
def role_upload(self): def role_upload(self):
if self.role is not None: if self.role is not None:
return True if self.role & ROLE_UPLOAD == ROLE_UPLOAD else False return True if self.role & ROLE_UPLOAD == ROLE_UPLOAD else False
else: else:
return False return False
def role_edit(self): def role_edit(self):
if self.role is not None: if self.role is not None:
return True if self.role & ROLE_EDIT == ROLE_EDIT else False return True if self.role & ROLE_EDIT == ROLE_EDIT else False
else: else:
return False return False
def role_passwd(self): def role_passwd(self):
if self.role is not None: if self.role is not None:
return True if self.role & ROLE_PASSWD == ROLE_PASSWD else False return True if self.role & ROLE_PASSWD == ROLE_PASSWD else False
...@@ -96,20 +102,20 @@ class User(Base): ...@@ -96,20 +102,20 @@ class User(Base):
def show_category(self): def show_category(self):
return self.category_books return self.category_books
def __repr__(self): def __repr__(self):
return '<User %r>' % (self.nickname) return '<User %r>' % self.nickname
class Shelf(Base): class Shelf(Base):
__tablename__ = 'shelf' __tablename__ = 'shelf'
id = Column(Integer, primary_key = True) id = Column(Integer, primary_key=True)
name = Column(String) name = Column(String)
is_public = Column(Integer, default=0) is_public = Column(Integer, default=0)
user_id = Column(Integer, ForeignKey('user.id')) user_id = Column(Integer, ForeignKey('user.id'))
def __repr__(self): def __repr__(self):
return '<Shelf %r>' % (self.name) return '<Shelf %r>' % self.name
class Whislist(Base): class Whislist(Base):
...@@ -124,7 +130,7 @@ class Whislist(Base): ...@@ -124,7 +130,7 @@ class Whislist(Base):
pass pass
def __repr__(self): def __repr__(self):
return '<Whislist %r>' % (self.name) return '<Whislist %r>' % self.name
class BookShelf(Base): class BookShelf(Base):
...@@ -135,7 +141,7 @@ class BookShelf(Base): ...@@ -135,7 +141,7 @@ class BookShelf(Base):
shelf = Column(Integer, ForeignKey('shelf.id')) shelf = Column(Integer, ForeignKey('shelf.id'))
def __repr__(self): def __repr__(self):
return '<Book %r>' % (self.id) return '<Book %r>' % self.id
class Downloads(Base): class Downloads(Base):
...@@ -146,7 +152,8 @@ class Downloads(Base): ...@@ -146,7 +152,8 @@ class Downloads(Base):
user_id = Column(Integer, ForeignKey('user.id')) user_id = Column(Integer, ForeignKey('user.id'))
def __repr__(self): def __repr__(self):
return '<Download %r' % (self.book_id) return '<Download %r' % self.book_id
class Whish(Base): class Whish(Base):
__tablename__ = 'whish' __tablename__ = 'whish'
...@@ -157,7 +164,8 @@ class Whish(Base): ...@@ -157,7 +164,8 @@ class Whish(Base):
wishlist = Column(Integer, ForeignKey('wishlist.id')) wishlist = Column(Integer, ForeignKey('wishlist.id'))
def __repr__(self): def __repr__(self):
return '<Whish %r>' % (self.title) return '<Whish %r>' % self.title
class Settings(Base): class Settings(Base):
__tablename__ = 'settings' __tablename__ = 'settings'
...@@ -174,12 +182,13 @@ class Settings(Base): ...@@ -174,12 +182,13 @@ class Settings(Base):
#return '<Smtp %r>' % (self.mail_server) #return '<Smtp %r>' % (self.mail_server)
pass pass
def migrate_Database(): def migrate_Database():
try: try:
session.query(exists().where(User.random_books)).scalar() session.query(exists().where(User.random_books)).scalar()
session.commit() session.commit()
except exc.OperationalError: # Database is not compatible, some rows are missing except exc.OperationalError: # Database is not compatible, some rows are missing
conn=engine.connect() conn = engine.connect()
conn.execute("ALTER TABLE user ADD column random_books INTEGER DEFAULT 1") 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 locale String(2) DEFAULT 'en'")
conn.execute("ALTER TABLE user ADD column default_language String(3) DEFAULT 'all'") conn.execute("ALTER TABLE user ADD column default_language String(3) DEFAULT 'all'")
...@@ -208,23 +217,25 @@ def create_default_config(): ...@@ -208,23 +217,25 @@ def create_default_config():
session.add(settings) session.add(settings)
session.commit() session.commit()
def get_mail_settings(): def get_mail_settings():
settings = session.query(Settings).first() settings = session.query(Settings).first()
if not settings: if not settings:
return {} return {}
data = { data = {
'mail_server': settings.mail_server, 'mail_server': settings.mail_server,
'mail_port': settings.mail_port, 'mail_port': settings.mail_port,
'mail_use_ssl': settings.mail_use_ssl, 'mail_use_ssl': settings.mail_use_ssl,
'mail_login': settings.mail_login, 'mail_login': settings.mail_login,
'mail_password': settings.mail_password, 'mail_password': settings.mail_password,
'mail_from': settings.mail_from 'mail_from': settings.mail_from
} }
return data return data
def create_admin_user(): def create_admin_user():
user = User() user = User()
user.nickname = "admin" user.nickname = "admin"
...@@ -251,4 +262,3 @@ if not os.path.exists(dbpath): ...@@ -251,4 +262,3 @@ if not os.path.exists(dbpath):
pass pass
else: else:
migrate_Database() migrate_Database()
...@@ -9,6 +9,8 @@ BookMeta = namedtuple('BookMeta', 'file_path, extension, title, author, cover, d ...@@ -9,6 +9,8 @@ BookMeta = namedtuple('BookMeta', 'file_path, extension, title, author, cover, d
""" """
:rtype: BookMeta :rtype: BookMeta
""" """
def upload(file): def upload(file):
tmp_dir = os.path.join(gettempdir(), 'calibre_web') tmp_dir = os.path.join(gettempdir(), 'calibre_web')
...@@ -23,7 +25,3 @@ def upload(file): ...@@ -23,7 +25,3 @@ def upload(file):
file.save(tmp_file_path) file.save(tmp_file_path)
meta = book_formats.process(tmp_file_path, filename_root, file_extension) meta = book_formats.process(tmp_file_path, filename_root, file_extension)
return meta return meta
...@@ -5,8 +5,8 @@ import mimetypes ...@@ -5,8 +5,8 @@ import mimetypes
import logging import logging
from logging.handlers import RotatingFileHandler from logging.handlers import RotatingFileHandler
import textwrap import textwrap
mimetypes.add_type('application/xhtml+xml','.xhtml') from flask import Flask, render_template, session, request, Response, redirect, url_for, send_from_directory, \
from flask import Flask, render_template, session, request, Response, redirect, url_for, send_from_directory, make_response, g, flash, abort make_response, g, flash, abort
import db, config, ub, helper import db, config, ub, helper
import os import os
import errno import errno
...@@ -31,10 +31,15 @@ import datetime ...@@ -31,10 +31,15 @@ import datetime
from iso639 import languages as isoLanguages from iso639 import languages as isoLanguages
from uuid import uuid4 from uuid import uuid4
import os.path import os.path
import sys
import subprocess
import shutil import shutil
import re import re
from shutil import move
try: try:
from wand.image import Image from wand.image import Image
use_generic_pdf_cover = False use_generic_pdf_cover = False
except ImportError, e: except ImportError, e:
use_generic_pdf_cover = True use_generic_pdf_cover = True
...@@ -42,8 +47,11 @@ except ImportError, e: ...@@ -42,8 +47,11 @@ except ImportError, e:
from shutil import copyfile from shutil import copyfile
from cgi import escape from cgi import escape
mimetypes.add_type('application/xhtml+xml', '.xhtml')
class ReverseProxied(object): class ReverseProxied(object):
'''Wrap the application in this middleware and configure the """Wrap the application in this middleware and configure the
front-end server to add these headers, to let you quietly bind front-end server to add these headers, to let you quietly bind
this to a URL other than / and to an HTTP scheme that is this to a URL other than / and to an HTTP scheme that is
different than what is used locally. different than what is used locally.
...@@ -58,7 +66,8 @@ class ReverseProxied(object): ...@@ -58,7 +66,8 @@ class ReverseProxied(object):
proxy_set_header X-Scheme $scheme; proxy_set_header X-Scheme $scheme;
proxy_set_header X-Script-Name /myprefix; proxy_set_header X-Script-Name /myprefix;
} }
''' """
def __init__(self, app): def __init__(self, app):
self.app = app self.app = app
...@@ -78,49 +87,69 @@ class ReverseProxied(object): ...@@ -78,49 +87,69 @@ class ReverseProxied(object):
environ['HTTP_HOST'] = server environ['HTTP_HOST'] = server
return self.app(environ, start_response) return self.app(environ, start_response)
app = (Flask(__name__)) app = (Flask(__name__))
app.wsgi_app = ReverseProxied(app.wsgi_app) app.wsgi_app = ReverseProxied(app.wsgi_app)
formatter = logging.Formatter( formatter = logging.Formatter(
"[%(asctime)s] {%(pathname)s:%(lineno)d} %(levelname)s - %(message)s") "[%(asctime)s] {%(pathname)s:%(lineno)d} %(levelname)s - %(message)s")
file_handler = RotatingFileHandler(os.path.join(config.LOG_DIR, "calibre-web.log"), maxBytes=10000, backupCount=1) file_handler = RotatingFileHandler(os.path.join(config.LOG_DIR, "calibre-web.log"), maxBytes=50000, backupCount=1)
file_handler.setLevel(logging.INFO)
file_handler.setFormatter(formatter) file_handler.setFormatter(formatter)
app.logger.addHandler(file_handler) 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...') app.logger.info('Starting Calibre Web...')
logging.getLogger("book_formats").addHandler(file_handler) logging.getLogger("book_formats").addHandler(file_handler)
logging.getLogger("book_formats").setLevel(logging.INFO) logging.getLogger("book_formats").setLevel(logging.INFO)
Principal(app) Principal(app)
babel = Babel(app) babel = Babel(app)
import uploader
global global_queue
global_queue = None
class Anonymous(AnonymousUserMixin): class Anonymous(AnonymousUserMixin):
def __init__(self): def __init__(self):
self.nickname = 'Guest' self.nickname = 'Guest'
self.role = -1 self.role = -1
def role_admin(self): def role_admin(self):
return False return False
def role_download(self): def role_download(self):
return False return False
def role_upload(self): def role_upload(self):
return False return False
def role_edit(self): def role_edit(self):
return False return False
def filter_language(self): def filter_language(self):
return 'all' return 'all'
def show_random_books(self): def show_random_books(self):
return True return True
def show_hot_books(self): def show_hot_books(self):
return True return True
def show_series(self): def show_series(self):
return True return True
def show_category(self): def show_category(self):
return True return True
def show_language(self): def show_language(self):
return True return True
def is_anonymous(self): def is_anonymous(self):
return config.ANON_BROWSE return config.ANON_BROWSE
...@@ -138,21 +167,24 @@ LANGUAGES = { ...@@ -138,21 +167,24 @@ LANGUAGES = {
'fr': 'Français' 'fr': 'Français'
} }
@babel.localeselector @babel.localeselector
def get_locale(): def get_locale():
# if a user is logged in, use the locale from the user settings # if a user is logged in, use the locale from the user settings
user = getattr(g, 'user', None) user = getattr(g, 'user', None)
if user is not None and hasattr(user, "locale"): 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()] preferred = [x.replace('-', '_') for x in request.accept_languages.values()]
return negotiate_locale(preferred, LANGUAGES.keys()) return negotiate_locale(preferred, LANGUAGES.keys())
@babel.timezoneselector @babel.timezoneselector
def get_timezone(): def get_timezone():
user = getattr(g, 'user', None) user = getattr(g, 'user', None)
if user is not None: if user is not None:
return user.timezone return user.timezone
@lm.user_loader @lm.user_loader
def load_user(id): def load_user(id):
return ub.session.query(ub.User).filter(ub.User.id == int(id)).first() return ub.session.query(ub.User).filter(ub.User.id == int(id)).first()
...@@ -173,6 +205,7 @@ def load_user_from_header(header_val): ...@@ -173,6 +205,7 @@ def load_user_from_header(header_val):
return user return user
return return
def check_auth(username, password): def check_auth(username, password):
user = ub.session.query(ub.User).filter(ub.User.nickname == username).first() user = ub.session.query(ub.User).filter(ub.User.nickname == username).first()
if user and check_password_hash(user.password, password): if user and check_password_hash(user.password, password):
...@@ -180,11 +213,13 @@ def check_auth(username, password): ...@@ -180,11 +213,13 @@ def check_auth(username, password):
else: else:
return False return False
def authenticate(): def authenticate():
return Response( return Response(
'Could not verify your access level for that URL.\n' 'Could not verify your access level for that URL.\n'
'You have to login with proper credentials', 401, 'You have to login with proper credentials', 401,
{'WWW-Authenticate': 'Basic realm="Login Required"'}) {'WWW-Authenticate': 'Basic realm="Login Required"'})
def requires_basic_auth_if_no_ano(f): def requires_basic_auth_if_no_ano(f):
@wraps(f) @wraps(f)
...@@ -194,11 +229,12 @@ def requires_basic_auth_if_no_ano(f): ...@@ -194,11 +229,12 @@ def requires_basic_auth_if_no_ano(f):
if not auth or not check_auth(auth.username, auth.password): if not auth or not check_auth(auth.username, auth.password):
return authenticate() return authenticate()
return f(*args, **kwargs) return f(*args, **kwargs)
return decorated 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): def __init__(self, page, per_page, total_count):
self.page = page self.page = page
self.per_page = per_page self.per_page = per_page
...@@ -221,88 +257,104 @@ class Pagination(object): ...@@ -221,88 +257,104 @@ class Pagination(object):
last = 0 last = 0
for num in xrange(1, self.pages + 1): for num in xrange(1, self.pages + 1):
if num <= left_edge or \ if num <= left_edge or \
(num > self.page - left_current - 1 and \ (num > self.page - left_current - 1 and \
num < self.page + right_current) or \ num < self.page + right_current) or \
num > self.pages - right_edge: num > self.pages - right_edge:
if last + 1 != num: if last + 1 != num:
yield None yield None
yield num yield num
last = num last = num
##pagination links in jinja
# pagination links in jinja
def url_for_other_page(page): def url_for_other_page(page):
args = request.view_args.copy() args = request.view_args.copy()
args['page'] = page args['page'] = page
return url_for(request.endpoint, **args) return url_for(request.endpoint, **args)
app.jinja_env.globals['url_for_other_page'] = url_for_other_page app.jinja_env.globals['url_for_other_page'] = url_for_other_page
def login_required_if_no_ano(func): def login_required_if_no_ano(func):
if config.ANON_BROWSE == 1: if config.ANON_BROWSE == 1:
return func return func
return login_required(func) return login_required(func)
## custom jinja filters
# custom jinja filters
@app.template_filter('shortentitle') @app.template_filter('shortentitle')
def shortentitle_filter(s): def shortentitle_filter(s):
if len(s) > 60: if len(s) > 60:
s = s.split(':', 1)[0] s = s.split(':', 1)[0]
if len(s) > 60: 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 return s
def admin_required(f): def admin_required(f):
""" """
Checks if current_user.role == 1 Checks if current_user.role == 1
""" """
@wraps(f) @wraps(f)
def inner(*args, **kwargs): def inner(*args, **kwargs):
if current_user.role_admin(): if current_user.role_admin():
return f(*args, **kwargs) return f(*args, **kwargs)
abort(403) abort(403)
return inner return inner
def download_required(f): def download_required(f):
@wraps(f) @wraps(f)
def inner(*args, **kwargs): def inner(*args, **kwargs):
if current_user.role_download() or current_user.role_admin(): if current_user.role_download() or current_user.role_admin():
return f(*args, **kwargs) return f(*args, **kwargs)
abort(403) abort(403)
return inner return inner
def upload_required(f): def upload_required(f):
@wraps(f) @wraps(f)
def inner(*args, **kwargs): def inner(*args, **kwargs):
if current_user.role_upload() or current_user.role_admin(): if current_user.role_upload() or current_user.role_admin():
return f(*args, **kwargs) return f(*args, **kwargs)
abort(403) abort(403)
return inner return inner
def edit_required(f): def edit_required(f):
@wraps(f) @wraps(f)
def inner(*args, **kwargs): def inner(*args, **kwargs):
if current_user.role_edit() or current_user.role_admin(): if current_user.role_edit() or current_user.role_admin():
return f(*args, **kwargs) return f(*args, **kwargs)
abort(403) abort(403)
return inner return inner
# Fill indexpage with all requested data from database # 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": if current_user.filter_language() != "all":
filter = db.Books.languages.any(db.Languages.lang_code == current_user.filter_language()) filter = db.Books.languages.any(db.Languages.lang_code == current_user.filter_language())
else: else:
filter= True filter = True
if current_user.show_random_books(): if current_user.show_random_books():
random = db.session.query(db.Books).filter(filter).order_by(func.random()).limit(config.RANDOM_BOOKS) random = db.session.query(db.Books).filter(filter).order_by(func.random()).limit(config.RANDOM_BOOKS)
else : else:
random = false random = false
off = int(int(config.NEWEST_BOOKS) * (page-1)) 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())) pagination = Pagination(page, config.NEWEST_BOOKS,
entries = db.session.query(database).filter(db_filter).filter(filter).order_by(order).offset(off).limit(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 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 != ''] input_elements = [x for x in input_elements if x != '']
# we have all input element (authors, series, tags) names now # we have all input element (authors, series, tags) names now
# 1. search for elements to remove # 1. search for elements to remove
...@@ -312,7 +364,7 @@ def modify_database_object(input_elements, db_book_object, db_object, db_session ...@@ -312,7 +364,7 @@ def modify_database_object(input_elements, db_book_object, db_object, db_session
for inp_element in input_elements: for inp_element in input_elements:
if inp_element == c_elements.name: if inp_element == c_elements.name:
found = True found = True
break; break
# if the element was not found in the new list, add it to remove list # if the element was not found in the new list, add it to remove list
if not found: if not found:
del_elements.append(c_elements) del_elements.append(c_elements)
...@@ -323,7 +375,7 @@ def modify_database_object(input_elements, db_book_object, db_object, db_session ...@@ -323,7 +375,7 @@ def modify_database_object(input_elements, db_book_object, db_object, db_session
for c_elements in db_book_object: for c_elements in db_book_object:
if inp_element == c_elements.name: if inp_element == c_elements.name:
found = True found = True
break; break
if not found: if not found:
add_elements.append(inp_element) add_elements.append(inp_element)
# if there are elements to remove, we remove them now # 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 ...@@ -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 there are elements to add, we add them now!
if len(add_elements) > 0: if len(add_elements) > 0:
if type == 'languages': if type == 'languages':
db_filter=db_object.lang_code db_filter = db_object.lang_code
else: else:
db_filter=db_object.name db_filter = db_object.name
for add_element in add_elements: for add_element in add_elements:
# check if a element with that name exists # check if a element with that name exists
new_element = db_session.query(db_object).filter(db_filter == add_element).first() new_element = db_session.query(db_object).filter(db_filter == add_element).first()
# if no element is found add it # if no element is found add it
if new_element == None: if new_element is None:
if type=='author': if type == 'author':
new_element = db_object(add_element, add_element, "") new_element = db_object(add_element, add_element, "")
else: else:
if type=='series': if type == 'series':
new_element = db_object(add_element, add_element) 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) new_element = db_object(add_element)
db_session.add(new_element) db_session.add(new_element)
new_element = db.session.query(db_object).filter(db_filter == add_element).first() new_element = db.session.query(db_object).filter(db_filter == add_element).first()
...@@ -363,6 +415,7 @@ def before_request(): ...@@ -363,6 +415,7 @@ def before_request():
g.allow_registration = config.PUBLIC_REG g.allow_registration = config.PUBLIC_REG
g.allow_upload = config.UPLOADING g.allow_upload = config.UPLOADING
@app.route("/feed") @app.route("/feed")
@requires_basic_auth_if_no_ano @requires_basic_auth_if_no_ano
def feed_index(): def feed_index():
...@@ -371,18 +424,20 @@ def feed_index(): ...@@ -371,18 +424,20 @@ def feed_index():
else: else:
filter = True filter = True
xml = render_template('index.xml') xml = render_template('index.xml')
response= make_response(xml) response = make_response(xml)
response.headers["Content-Type"] = "application/xml" response.headers["Content-Type"] = "application/xml"
return response return response
@app.route("/feed/osd") @app.route("/feed/osd")
@requires_basic_auth_if_no_ano @requires_basic_auth_if_no_ano
def feed_osd(): def feed_osd():
xml = render_template('osd.xml') xml = render_template('osd.xml')
response= make_response(xml) response = make_response(xml)
response.headers["Content-Type"] = "application/xml" response.headers["Content-Type"] = "application/xml"
return response return response
@app.route("/feed/search", methods=["GET"]) @app.route("/feed/search", methods=["GET"])
@requires_basic_auth_if_no_ano @requires_basic_auth_if_no_ano
def feed_search(): def feed_search():
...@@ -392,15 +447,18 @@ def feed_search(): ...@@ -392,15 +447,18 @@ def feed_search():
else: else:
filter = True filter = True
if term: 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) xml = render_template('feed.xml', searchterm=term, entries=entries, Last_Updated=Last_Updated)
else: else:
xml = render_template('feed.xml', searchterm="") xml = render_template('feed.xml', searchterm="")
response= make_response(xml) response = make_response(xml)
response.headers["Content-Type"] = "application/xml" response.headers["Content-Type"] = "application/xml"
return response return response
@app.route("/feed/new") @app.route("/feed/new")
@requires_basic_auth_if_no_ano @requires_basic_auth_if_no_ano
def feed_new(): def feed_new():
...@@ -408,12 +466,14 @@ def feed_new(): ...@@ -408,12 +466,14 @@ def feed_new():
if current_user.filter_language() != "all": if current_user.filter_language() != "all":
filter = db.Books.languages.any(db.Languages.lang_code == current_user.filter_language()) filter = db.Books.languages.any(db.Languages.lang_code == current_user.filter_language())
else: else:
filter= True filter = True
if not off: if not off:
off=0 off = 0
entries = db.session.query(db.Books).filter(filter).order_by(db.Books.last_modified.desc()).offset(off).limit(config.NEWEST_BOOKS) entries = db.session.query(db.Books).filter(filter).order_by(db.Books.timestamp.desc()).offset(off).limit(
xml = render_template('feed.xml', entries=entries, next_url="/feed/new?start_index=%d" % (int(config.NEWEST_BOOKS) + int(off))) config.NEWEST_BOOKS)
response= make_response(xml) 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" response.headers["Content-Type"] = "application/xml"
return response return response
...@@ -427,13 +487,15 @@ def feed_discover(): ...@@ -427,13 +487,15 @@ def feed_discover():
else: else:
filter = True filter = True
if not off: if not off:
off=0 off = 0
entries = db.session.query(db.Books).filter(filter).order_by(func.random()).offset(off).limit(config.NEWEST_BOOKS) 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 = make_response(xml)
response.headers["Content-Type"] = "application/xml" response.headers["Content-Type"] = "application/xml"
return response return response
@app.route("/feed/hot") @app.route("/feed/hot")
@requires_basic_auth_if_no_ano @requires_basic_auth_if_no_ano
def feed_hot(): def feed_hot():
...@@ -443,14 +505,17 @@ def feed_hot(): ...@@ -443,14 +505,17 @@ def feed_hot():
else: else:
filter = True filter = True
if not off: if not off:
off=0 off = 0
entries = db.session.query(db.Books).filter(filter).filter(db.Books.ratings.any(db.Ratings.rating > 9)).offset(off).limit(config.NEWEST_BOOKS) 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))) xml = render_template('feed.xml', entries=entries,
response= make_response(xml) next_url="/feed/hot?start_index=%d" % (int(config.NEWEST_BOOKS) + int(off)))
response = make_response(xml)
response.headers["Content-Type"] = "application/xml" response.headers["Content-Type"] = "application/xml"
return response return response
@app.route("/feed/author") @app.route("/feed/author")
@requires_basic_auth_if_no_ano @requires_basic_auth_if_no_ano
def feed_authorindex(): def feed_authorindex():
...@@ -460,13 +525,15 @@ def feed_authorindex(): ...@@ -460,13 +525,15 @@ def feed_authorindex():
else: else:
filter = True filter = True
if not off: if not off:
off=0 off = 0
authors = db.session.query(db.Authors).order_by(db.Authors.sort).offset(off).limit(config.NEWEST_BOOKS) authors = db.session.query(db.Authors).order_by(db.Authors.sort).offset(off).limit(config.NEWEST_BOOKS)
xml = render_template('feed.xml', authors=authors,next_url="/feed/author?start_index=%d" % (int(config.NEWEST_BOOKS) + int(off))) xml = render_template('feed.xml', authors=authors,
response= make_response(xml) next_url="/feed/author?start_index=%d" % (int(config.NEWEST_BOOKS) + int(off)))
response = make_response(xml)
response.headers["Content-Type"] = "application/xml" response.headers["Content-Type"] = "application/xml"
return response return response
@app.route("/feed/author/<name>") @app.route("/feed/author/<name>")
@requires_basic_auth_if_no_ano @requires_basic_auth_if_no_ano
def feed_author(name): def feed_author(name):
...@@ -476,11 +543,12 @@ def feed_author(name): ...@@ -476,11 +543,12 @@ def feed_author(name):
else: else:
filter = True filter = True
if not off: if not off:
off=0 off = 0
entries = db.session.query(db.Books).filter(db.Books.authors.any(db.Authors.name.like("%" + name + "%"))).filter( entries = db.session.query(db.Books).filter(db.Books.authors.any(db.Authors.name.like("%" + name + "%"))).filter(
filter).offset(off).limit(config.NEWEST_BOOKS) 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))) xml = render_template('feed.xml', entries=entries,
response= make_response(xml) next_url="/feed/author?start_index=%d" % (int(config.NEWEST_BOOKS) + int(off)))
response = make_response(xml)
response.headers["Content-Type"] = "application/xml" response.headers["Content-Type"] = "application/xml"
return response return response
...@@ -490,10 +558,11 @@ def feed_author(name): ...@@ -490,10 +558,11 @@ def feed_author(name):
def feed_categoryindex(): def feed_categoryindex():
off = request.args.get("start_index") off = request.args.get("start_index")
if not off: if not off:
off=0 off = 0
entries = db.session.query(db.Tags).order_by(db.Tags.name).offset(off).limit(config.NEWEST_BOOKS) entries = db.session.query(db.Tags).order_by(db.Tags.name).offset(off).limit(config.NEWEST_BOOKS)
xml = render_template('feed.xml', categorys=entries, next_url="/feed/category?start_index=%d" % (int(config.NEWEST_BOOKS) + int(off))) xml = render_template('feed.xml', categorys=entries,
response= make_response(xml) next_url="/feed/category?start_index=%d" % (int(config.NEWEST_BOOKS) + int(off)))
response = make_response(xml)
response.headers["Content-Type"] = "application/xml" response.headers["Content-Type"] = "application/xml"
return response return response
...@@ -507,23 +576,26 @@ def feed_category(name): ...@@ -507,23 +576,26 @@ def feed_category(name):
else: else:
filter = True filter = True
if not off: 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( 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) 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))) xml = render_template('feed.xml', entries=entries,
response= make_response(xml) next_url="/feed/category?start_index=%d" % (int(config.NEWEST_BOOKS) + int(off)))
response = make_response(xml)
response.headers["Content-Type"] = "application/xml" response.headers["Content-Type"] = "application/xml"
return response return response
@app.route("/feed/series") @app.route("/feed/series")
@requires_basic_auth_if_no_ano @requires_basic_auth_if_no_ano
def feed_seriesindex(): def feed_seriesindex():
off = request.args.get("start_index") off = request.args.get("start_index")
if not off: if not off:
off=0 off = 0
entries = db.session.query(db.Series).order_by(db.Series.name).offset(off).limit(config.NEWEST_BOOKS) entries = db.session.query(db.Series).order_by(db.Series.name).offset(off).limit(config.NEWEST_BOOKS)
xml = render_template('feed.xml', series=entries, next_url="/feed/series?start_index=%d" % (int(config.NEWEST_BOOKS) + int(off))) xml = render_template('feed.xml', series=entries,
response= make_response(xml) next_url="/feed/series?start_index=%d" % (int(config.NEWEST_BOOKS) + int(off)))
response = make_response(xml)
response.headers["Content-Type"] = "application/xml" response.headers["Content-Type"] = "application/xml"
return response return response
...@@ -537,11 +609,12 @@ def feed_series(name): ...@@ -537,11 +609,12 @@ def feed_series(name):
else: else:
filter = True filter = True
if not off: 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( 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) 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))) xml = render_template('feed.xml', entries=entries,
response= make_response(xml) next_url="/feed/series?start_index=%d" % (int(config.NEWEST_BOOKS) + int(off)))
response = make_response(xml)
response.headers["Content-Type"] = "application/xml" response.headers["Content-Type"] = "application/xml"
return response return response
...@@ -557,31 +630,34 @@ def get_opds_download_link(book_id, format): ...@@ -557,31 +630,34 @@ def get_opds_download_link(book_id, format):
author = helper.get_normalized_author(book.author_sort) author = helper.get_normalized_author(book.author_sort)
file_name = book.title file_name = book.title
if len(author) > 0: if len(author) > 0:
file_name = author+'-'+file_name file_name = author + '-' + file_name
file_name = helper.get_valid_filename(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) response.headers["Content-Disposition"] = "attachment; filename=%s.%s" % (data.name, format)
return response return response
@app.route("/get_authors_json", methods = ['GET', 'POST'])
@app.route("/get_authors_json", methods=['GET', 'POST'])
@login_required_if_no_ano @login_required_if_no_ano
def get_authors_json(): def get_authors_json():
if request.method == "GET": if request.method == "GET":
query = request.args.get('q') query = request.args.get('q')
entries = db.session.execute("select name from authors where name like '%" + query + "%'") entries = db.session.execute("select name from authors where name like '%" + query + "%'")
json_dumps = json.dumps([dict(r) for r in entries]) json_dumps = json.dumps([dict(r) for r in entries])
return json_dumps return json_dumps
@app.route("/get_tags_json", methods = ['GET', 'POST'])
@app.route("/get_tags_json", methods=['GET', 'POST'])
@login_required_if_no_ano @login_required_if_no_ano
def get_tags_json(): def get_tags_json():
if request.method == "GET": if request.method == "GET":
query = request.args.get('q') query = request.args.get('q')
entries = db.session.execute("select name from tags where name like '%" + query + "%'") entries = db.session.execute("select name from tags where name like '%" + query + "%'")
json_dumps = json.dumps([dict(r) for r in entries]) json_dumps = json.dumps([dict(r) for r in entries])
return json_dumps return json_dumps
@app.route("/get_languages_json", methods = ['GET', 'POST'])
@app.route("/get_languages_json", methods=['GET', 'POST'])
@login_required_if_no_ano @login_required_if_no_ano
def get_languages_json(): def get_languages_json():
if request.method == "GET": if request.method == "GET":
...@@ -593,24 +669,26 @@ def get_languages_json(): ...@@ -593,24 +669,26 @@ def get_languages_json():
cur_l = LC.parse(lang.lang_code) cur_l = LC.parse(lang.lang_code)
lang.name = cur_l.get_language_name(get_locale()) lang.name = cur_l.get_language_name(get_locale())
except: 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()] entries = [s for s in languages if query in s.name.lower()]
json_dumps = json.dumps([dict(name=r.name) for r in entries]) json_dumps = json.dumps([dict(name=r.name) for r in entries])
return json_dumps return json_dumps
@app.route("/get_series_json", methods = ['GET', 'POST'])
@app.route("/get_series_json", methods=['GET', 'POST'])
@login_required_if_no_ano @login_required_if_no_ano
def get_series_json(): def get_series_json():
if request.method == "GET": if request.method == "GET":
query = request.args.get('q') query = request.args.get('q')
entries = db.session.execute("select name from series where name like '%" + query + "%'") entries = db.session.execute("select name from series where name like '%" + query + "%'")
json_dumps = json.dumps([dict(r) for r in entries]) json_dumps = json.dumps([dict(r) for r in entries])
return json_dumps return json_dumps
@app.route("/get_matching_tags", methods = ['GET', 'POST'])
@app.route("/get_matching_tags", methods=['GET', 'POST'])
@login_required_if_no_ano @login_required_if_no_ano
def get_matching_tags(): def get_matching_tags():
tag_dict = {'tags': []} tag_dict = {'tags': []}
if request.method == "GET": if request.method == "GET":
q = db.session.query(db.Books) q = db.session.query(db.Books)
...@@ -618,7 +696,8 @@ def get_matching_tags(): ...@@ -618,7 +696,8 @@ def get_matching_tags():
title_input = request.args.get('book_title') title_input = request.args.get('book_title')
include_tag_inputs = request.args.getlist('include_tag') include_tag_inputs = request.args.getlist('include_tag')
exclude_tag_inputs = request.args.getlist('exclude_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: if len(include_tag_inputs) > 0:
for tag in include_tag_inputs: for tag in include_tag_inputs:
q = q.filter(db.Books.tags.any(db.Tags.id == tag)) q = q.filter(db.Books.tags.any(db.Tags.id == tag))
...@@ -632,12 +711,15 @@ def get_matching_tags(): ...@@ -632,12 +711,15 @@ def get_matching_tags():
json_dumps = json.dumps(tag_dict) json_dumps = json.dumps(tag_dict)
return json_dumps return json_dumps
@app.route("/", defaults={'page': 1}) @app.route("/", defaults={'page': 1})
@app.route('/page/<int:page>') @app.route('/page/<int:page>')
@login_required_if_no_ano @login_required_if_no_ano
def index(page): def index(page):
entries, random, pagination =fill_indexpage(page,db.Books,True, db.Books.last_modified.desc()) 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")) return render_template('index.html', random=random, entries=entries, pagination=pagination,
title=_(u"Latest Books"))
@app.route("/hot", defaults={'page': 1}) @app.route("/hot", defaults={'page': 1})
@app.route('/hot/page/<int:page>') @app.route('/hot/page/<int:page>')
...@@ -649,25 +731,29 @@ def hot_books(page): ...@@ -649,25 +731,29 @@ def hot_books(page):
filter = True filter = True
if current_user.show_random_books(): if current_user.show_random_books():
random = db.session.query(db.Books).filter(filter).order_by(func.random()).limit(config.RANDOM_BOOKS) random = db.session.query(db.Books).filter(filter).order_by(func.random()).limit(config.RANDOM_BOOKS)
else : else:
random = false random = false
off = int(int(config.NEWEST_BOOKS) * (page - 1)) 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) hot_books = all_books.offset(off).limit(config.NEWEST_BOOKS)
entries = list() entries = list()
for book in hot_books: for book in hot_books:
entries.append(db.session.query(db.Books).filter(filter).filter(db.Books.id == book.Downloads.book_id).first()) entries.append(db.session.query(db.Books).filter(filter).filter(db.Books.id == book.Downloads.book_id).first())
numBooks = entries.__len__() numBooks = entries.__len__()
pagination = Pagination(page, config.NEWEST_BOOKS, numBooks) pagination = Pagination(page, config.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", defaults={'page': 1})
@app.route('/discover/page/<int:page>') @app.route('/discover/page/<int:page>')
@login_required_if_no_ano @login_required_if_no_ano
def discover(page): 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")) return render_template('discover.html', entries=entries, pagination=pagination, title=_(u"Random Books"))
@app.route("/author") @app.route("/author")
@login_required_if_no_ano @login_required_if_no_ano
def author_list(): def author_list():
...@@ -675,7 +761,8 @@ def author_list(): ...@@ -675,7 +761,8 @@ def author_list():
filter = db.Books.languages.any(db.Languages.lang_code == current_user.filter_language()) filter = db.Books.languages.any(db.Languages.lang_code == current_user.filter_language())
else: else:
filter = True 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() 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")) return render_template('list.html', entries=entries, folder='author', title=_(u"Author list"))
...@@ -692,8 +779,10 @@ def author(name): ...@@ -692,8 +779,10 @@ def author(name):
else: else:
random = false random = false
entries = db.session.query(db.Books).filter(db.Books.authors.any(db.Authors.name.like("%" + name + "%"))).filter(filter).all() entries = db.session.query(db.Books).filter(db.Books.authors.any(db.Authors.name.like("%" + name + "%"))).filter(
return render_template('index.html', random=random, entries=entries, title=_(u"Author: %(nam)s",nam=name)) filter).all()
return render_template('index.html', random=random, entries=entries, title=_(u"Author: %(nam)s", nam=name))
@app.route("/series") @app.route("/series")
@login_required_if_no_ano @login_required_if_no_ano
...@@ -702,21 +791,26 @@ def series_list(): ...@@ -702,21 +791,26 @@ def series_list():
filter = db.Books.languages.any(db.Languages.lang_code == current_user.filter_language()) filter = db.Books.languages.any(db.Languages.lang_code == current_user.filter_language())
else: else:
filter = True 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() 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")) return render_template('list.html', entries=entries, folder='series', title=_(u"Series list"))
@app.route("/series/<name>/", defaults={'page': 1}) @app.route("/series/<name>/", defaults={'page': 1})
@app.route("/series/<name>/<int:page>'") @app.route("/series/<name>/<int:page>'")
@login_required_if_no_ano @login_required_if_no_ano
def series(name,page): def series(name, page):
entries, random, pagination = fill_indexpage(page, db.Books, db.Books.series.any(db.Series.name == name ), db.Books.series_index) entries, random, pagination = fill_indexpage(page, db.Books, db.Books.series.any(db.Series.name == name),
if entries : db.Books.series_index)
return render_template('index.html', random=random, pagination=pagination, entries=entries, title=_(u"Series: %(serie)s",serie=name)) if entries:
else : 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") flash(_(u"Error opening eBook. File does not exist or file is not accessible:"), category="error")
return redirect('/' or url_for("index", _external=True)) return redirect('/' or url_for("index", _external=True))
@app.route("/language") @app.route("/language")
@login_required_if_no_ano @login_required_if_no_ano
def language_overview(): def language_overview():
...@@ -727,32 +821,40 @@ def language_overview(): ...@@ -727,32 +821,40 @@ def language_overview():
cur_l = LC.parse(lang.lang_code) cur_l = LC.parse(lang.lang_code)
lang.name = cur_l.get_language_name(get_locale()) lang.name = cur_l.get_language_name(get_locale())
except: except:
lang.name=_(isoLanguages.get(part3=lang.lang_code).name) lang.name = _(isoLanguages.get(part3=lang.lang_code).name)
else : else:
try: try:
langfound=1 langfound = 1
cur_l = LC.parse(current_user.filter_language()) cur_l = LC.parse(current_user.filter_language())
except: except:
langfound=0 langfound = 0
languages = db.session.query(db.Languages).filter(db.Languages.lang_code == current_user.filter_language()).all() languages = db.session.query(db.Languages).filter(
db.Languages.lang_code == current_user.filter_language()).all()
if langfound: if langfound:
languages[0].name = cur_l.get_language_name(get_locale()) languages[0].name = cur_l.get_language_name(get_locale())
else : else:
languages[0].name=_(isoLanguages.get(part3=languages[0].lang_code).name) 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() lang_counter = db.session.query(db.books_languages_link,
return render_template('languages.html', languages=languages, lang_counter=lang_counter, title=_(u"Available languages")) 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/<name>",defaults={'page': 1}) @app.route("/language/<name>", defaults={'page': 1})
@app.route('/language/<name>/page/<int:page>') @app.route('/language/<name>/page/<int:page>')
@login_required_if_no_ano @login_required_if_no_ano
def language(name,page): 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()) entries, random, pagination = fill_indexpage(page, db.Books, db.Books.languages.any(db.Languages.lang_code == name),
db.Books.timestamp.desc())
try: try:
cur_l = LC.parse(name) cur_l = LC.parse(name)
name = cur_l.get_language_name(get_locale()) name = cur_l.get_language_name(get_locale())
except: except:
name = _(isoLanguages.get(part3=name).name) 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") @app.route("/category")
@login_required_if_no_ano @login_required_if_no_ano
...@@ -761,16 +863,21 @@ def category_list(): ...@@ -761,16 +863,21 @@ def category_list():
filter = db.Books.languages.any(db.Languages.lang_code == current_user.filter_language()) filter = db.Books.languages.any(db.Languages.lang_code == current_user.filter_language())
else: else:
filter = True 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() filter).group_by('books_tags_link.tag').all()
return render_template('list.html', entries=entries, folder='category', title=_(u"Category list")) return render_template('list.html', entries=entries, folder='category', title=_(u"Category list"))
@app.route("/category/<name>",defaults={'page': 1})
@app.route("/category/<name>", defaults={'page': 1})
@app.route('/category/<name>/<int:page>') @app.route('/category/<name>/<int:page>')
@login_required_if_no_ano @login_required_if_no_ano
def category(name,page): 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()) entries, random, pagination = fill_indexpage(page, db.Books, db.Books.tags.any(db.Tags.name == name),
return render_template('index.html', random=random, entries=entries, pagination=pagination, title=_(u"Category: %(name)s",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/<int:id>") @app.route("/book/<int:id>")
@login_required_if_no_ano @login_required_if_no_ano
...@@ -780,35 +887,62 @@ def show_book(id): ...@@ -780,35 +887,62 @@ def show_book(id):
else: else:
filter = True filter = True
entries = db.session.query(db.Books).filter(db.Books.id == id).filter(filter).first() entries = db.session.query(db.Books).filter(db.Books.id == id).filter(filter).first()
if entries : if entries:
for index in range(0,len(entries.languages)) : for index in range(0, len(entries.languages)):
try: 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: 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() cc = db.session.query(db.Custom_Columns).filter(db.Custom_Columns.datatype.notin_(db.cc_exceptions)).all()
book_in_shelfs = [] book_in_shelfs = []
shelfs = ub.session.query(ub.BookShelf).filter(ub.BookShelf.book_id == id).all() shelfs = ub.session.query(ub.BookShelf).filter(ub.BookShelf.book_id == id).all()
for entry in shelfs: for entry in shelfs:
book_in_shelfs.append(entry.shelf) book_in_shelfs.append(entry.shelf)
return render_template('detail.html', entry=entries, cc=cc, title=entries.title, books_shelfs=book_in_shelfs) return render_template('detail.html', entry=entries, cc=cc, title=entries.title, books_shelfs=book_in_shelfs)
else : else:
flash(_(u"Error opening eBook. File does not exist or file is not accessible:"), category="error") flash(_(u"Error opening eBook. File does not exist or file is not accessible:"), category="error")
return redirect('/' or url_for("index", _external=True)) return redirect('/' or url_for("index", _external=True))
@app.route("/admin/") @app.route("/admin/")
@login_required @login_required
def admin(): def admin():
#return "Admin ONLY!" # return "Admin ONLY!"
abort(403) abort(403)
@app.route("/stats") @app.route("/stats")
@login_required @login_required
def stats(): def stats():
counter = len(db.session.query(db.Books).all()) counter = len(db.session.query(db.Books).all())
authors = len(db.session.query(db.Authors).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"]) @app.route("/search", methods=["GET"])
@login_required_if_no_ano @login_required_if_no_ano
...@@ -819,11 +953,15 @@ def search(): ...@@ -819,11 +953,15 @@ def search():
filter = db.Books.languages.any(db.Languages.lang_code == current_user.filter_language()) filter = db.Books.languages.any(db.Languages.lang_code == current_user.filter_language())
else: else:
filter = True 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) return render_template('search.html', searchterm=term, entries=entries)
else: else:
return render_template('search.html', searchterm="") return render_template('search.html', searchterm="")
@app.route("/advanced_search", methods=["GET"]) @app.route("/advanced_search", methods=["GET"])
@login_required_if_no_ano @login_required_if_no_ano
def advanced_search(): def advanced_search():
...@@ -833,15 +971,16 @@ def advanced_search(): ...@@ -833,15 +971,16 @@ def advanced_search():
exclude_tag_inputs = request.args.getlist('exclude_tag') exclude_tag_inputs = request.args.getlist('exclude_tag')
author_name = request.args.get("author_name") author_name = request.args.get("author_name")
book_title = request.args.get("book_title") book_title = request.args.get("book_title")
if author_name: author_name=author_name.strip() if author_name: author_name = author_name.strip()
if book_title : book_title=book_title.strip() if book_title: book_title = book_title.strip()
if include_tag_inputs or exclude_tag_inputs or author_name or book_title: if include_tag_inputs or exclude_tag_inputs or author_name or book_title:
searchterm = [] searchterm = []
searchterm.extend((author_name, book_title)) searchterm.extend((author_name, book_title))
tag_names = db.session.query(db.Tags).filter(db.Tags.id.in_(include_tag_inputs)).all() 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.extend(tag.name for tag in tag_names)
searchterm = " + ".join(filter(None, searchterm)) 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) # random = db.session.query(db.Books).order_by(func.random()).limit(config.RANDOM_BOOKS)
for tag in include_tag_inputs: for tag in include_tag_inputs:
q = q.filter(db.Books.tags.any(db.Tags.id == tag)) q = q.filter(db.Books.tags.any(db.Tags.id == tag))
...@@ -854,28 +993,31 @@ def advanced_search(): ...@@ -854,28 +993,31 @@ def advanced_search():
tags = db.session.query(db.Tags).order_by(db.Tags.name).all() tags = db.session.query(db.Tags).order_by(db.Tags.name).all()
return render_template('search_form.html', tags=tags) return render_template('search_form.html', tags=tags)
@app.route("/cover/<path:cover_path>") @app.route("/cover/<path:cover_path>")
@login_required_if_no_ano @login_required_if_no_ano
def get_cover(cover_path): def get_cover(cover_path):
return send_from_directory(os.path.join(config.DB_ROOT, cover_path), "cover.jpg") return send_from_directory(os.path.join(config.DB_ROOT, cover_path), "cover.jpg")
@app.route("/feed/cover/<path:cover_path>") @app.route("/feed/cover/<path:cover_path>")
@requires_basic_auth_if_no_ano @requires_basic_auth_if_no_ano
def feed_get_cover(cover_path): def feed_get_cover(cover_path):
return send_from_directory(os.path.join(config.DB_ROOT, cover_path), "cover.jpg") return send_from_directory(os.path.join(config.DB_ROOT, cover_path), "cover.jpg")
@app.route("/read/<int:book_id>/<format>") @app.route("/read/<int:book_id>/<format>")
@login_required @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() book = db.session.query(db.Books).filter(db.Books.id == book_id).first()
if book : if book:
book_dir = os.path.join(config.MAIN_DIR, "cps","static", str(book_id)) book_dir = os.path.join(config.MAIN_DIR, "cps", "static", str(book_id))
if not os.path.exists(book_dir): if not os.path.exists(book_dir):
os.mkdir(book_dir) os.mkdir(book_dir)
if format.lower() == "epub": if format.lower() == "epub":
#check if mimetype file is exists # check if mimetype file is exists
mime_file = str(book_id) +"/mimetype" mime_file = str(book_id) + "/mimetype"
if os.path.exists(mime_file) == False: if not os.path.exists(mime_file):
epub_file = os.path.join(config.DB_ROOT, book.path, book.data[0].name) + ".epub" epub_file = os.path.join(config.DB_ROOT, book.path, book.data[0].name) + ".epub"
if not os.path.isfile(epub_file): if not os.path.isfile(epub_file):
raise ValueError('Error opening eBook. File does not exist: ', epub_file) raise ValueError('Error opening eBook. File does not exist: ', epub_file)
...@@ -898,23 +1040,32 @@ def read_book(book_id,format): ...@@ -898,23 +1040,32 @@ def read_book(book_id,format):
zfile.close() zfile.close()
return render_template('read.html', bookid=book_id, title=_(u"Read a Book")) return render_template('read.html', bookid=book_id, title=_(u"Read a Book"))
elif format.lower() == "pdf": elif format.lower() == "pdf":
all_name = str(book_id) +"/"+ urllib.quote(book.data[0].name) +".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" tmp_file = os.path.join(book_dir, urllib.quote(book.data[0].name)) + ".pdf"
if os.path.exists(tmp_file) == False: if not os.path.exists(tmp_file):
pdf_file = os.path.join(config.DB_ROOT, book.path, book.data[0].name) + ".pdf" pdf_file = os.path.join(config.DB_ROOT, book.path, book.data[0].name) + ".pdf"
copyfile(pdf_file,tmp_file) copyfile(pdf_file, tmp_file)
return render_template('readpdf.html', pdffile=all_name, title=_(u"Read a Book")) return render_template('readpdf.html', pdffile=all_name, title=_(u"Read a Book"))
elif format.lower() == "txt": elif format.lower() == "txt":
all_name = str(book_id) +"/"+ urllib.quote(book.data[0].name) +".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" tmp_file = os.path.join(book_dir, urllib.quote(book.data[0].name)) + ".txt"
if os.path.exists(all_name) == False: if not os.path.exists(all_name):
txt_file = os.path.join(config.DB_ROOT, book.path, book.data[0].name) + ".txt" txt_file = os.path.join(config.DB_ROOT, book.path, book.data[0].name) + ".txt"
copyfile(txt_file,tmp_file) copyfile(txt_file, tmp_file)
return render_template('readtxt.html', txtfile=all_name, title=_(u"Read a Book")) 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") flash(_(u"Error opening eBook. File does not exist or file is not accessible:"), category="error")
return redirect('/' or url_for("index", _external=True)) return redirect('/' or url_for("index", _external=True))
@app.route("/download/<int:book_id>/<format>") @app.route("/download/<int:book_id>/<format>")
@login_required @login_required
@download_required @download_required
...@@ -926,19 +1077,20 @@ def get_download_link(book_id, format): ...@@ -926,19 +1077,20 @@ def get_download_link(book_id, format):
author = helper.get_normalized_author(book.author_sort) author = helper.get_normalized_author(book.author_sort)
file_name = book.title file_name = book.title
if len(author) > 0: if len(author) > 0:
file_name = author+'-'+file_name file_name = author + '-' + file_name
file_name = helper.get_valid_filename(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"] = \ response.headers["Content-Disposition"] = \
"attachment; " \ "attachment; " \
"filename={utf_filename}.{suffix};" \ "filename={utf_filename}.{suffix};" \
"filename*=UTF-8''{utf_filename}.{suffix}".format( "filename*=UTF-8''{utf_filename}.{suffix}".format(
utf_filename=file_name.encode('utf-8'), utf_filename=file_name.encode('utf-8'),
suffix=format suffix=format
) )
return response return response
@app.route('/register', methods = ['GET', 'POST'])
@app.route('/register', methods=['GET', 'POST'])
def register(): def register():
error = None error = None
if not config.PUBLIC_REG: if not config.PUBLIC_REG:
...@@ -975,7 +1127,8 @@ def register(): ...@@ -975,7 +1127,8 @@ def register():
return render_template('register.html', title=_(u"register")) return render_template('register.html', title=_(u"register"))
@app.route('/login', methods = ['GET', 'POST'])
@app.route('/login', methods=['GET', 'POST'])
def login(): def login():
error = None error = None
...@@ -987,13 +1140,14 @@ def login(): ...@@ -987,13 +1140,14 @@ def login():
user = ub.session.query(ub.User).filter(ub.User.nickname == form['username'].strip()).first() user = ub.session.query(ub.User).filter(ub.User.nickname == form['username'].strip()).first()
if user and check_password_hash(user.password, form['password']): 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") flash(_(u"you are now logged in as: '%(nickname)s'", nickname=user.nickname), category="success")
return redirect('/' or url_for("index", _external=True)) return redirect('/' or url_for("index", _external=True))
else: else:
flash(_(u"Wrong Username or Password"), category="error") 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') @app.route('/logout')
@login_required @login_required
...@@ -1013,14 +1167,16 @@ def send_to_kindle(book_id): ...@@ -1013,14 +1167,16 @@ def send_to_kindle(book_id):
elif current_user.kindle_mail: elif current_user.kindle_mail:
result = helper.send_mail(book_id, current_user.kindle_mail) result = helper.send_mail(book_id, current_user.kindle_mail)
if result is None: 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)) helper.update_download(book_id, int(current_user.id))
else: 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: else:
flash(_(u"Please configure your kindle email address first..."), category="error") flash(_(u"Please configure your kindle email address first..."), category="error")
return redirect(request.environ["HTTP_REFERER"]) return redirect(request.environ["HTTP_REFERER"])
@app.route("/shelf/add/<int:shelf_id>/<int:book_id>") @app.route("/shelf/add/<int:shelf_id>/<int:book_id>")
@login_required @login_required
def add_to_shelf(shelf_id, book_id): def add_to_shelf(shelf_id, book_id):
...@@ -1033,11 +1189,12 @@ 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.add(ins)
ub.session.commit() 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"]) return redirect(request.environ["HTTP_REFERER"])
@app.route("/shelf/remove/<int:shelf_id>/<int:book_id>") @app.route("/shelf/remove/<int:shelf_id>/<int:book_id>")
@login_required @login_required
def remove_from_shelf(shelf_id, book_id): def remove_from_shelf(shelf_id, book_id):
...@@ -1046,16 +1203,18 @@ 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) flash("Sorry you are not allowed to remove a book from this shelf: %s" % shelf.name)
return redirect(url_for('index', _external=True)) 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.delete(book_shelf)
ub.session.commit() 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"]) return redirect(request.environ["HTTP_REFERER"])
@app.route("/shelf/create", methods=["GET", "POST"]) @app.route("/shelf/create", methods=["GET", "POST"])
@login_required @login_required
def create_shelf(): def create_shelf():
...@@ -1068,17 +1227,18 @@ def create_shelf(): ...@@ -1068,17 +1227,18 @@ def create_shelf():
shelf.user_id = int(current_user.id) shelf.user_id = int(current_user.id)
existing_shelf = ub.session.query(ub.Shelf).filter(ub.Shelf.name == shelf.name).first() existing_shelf = ub.session.query(ub.Shelf).filter(ub.Shelf.name == shelf.name).first()
if existing_shelf: 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: else:
try: try:
ub.session.add(shelf) ub.session.add(shelf)
ub.session.commit() 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: except:
flash(_(u"There was an error"), category="error") 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: 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/<int:shelf_id>") @app.route("/shelf/delete/<int:shelf_id>")
@login_required @login_required
...@@ -1089,21 +1249,28 @@ def delete_shelf(shelf_id): ...@@ -1089,21 +1249,28 @@ def delete_shelf(shelf_id):
deleted = ub.session.query(ub.Shelf).filter(ub.Shelf.id == shelf_id).delete() deleted = ub.session.query(ub.Shelf).filter(ub.Shelf.id == shelf_id).delete()
else: 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: if deleted:
ub.session.query(ub.BookShelf).filter(ub.BookShelf.shelf == shelf_id).delete() ub.session.query(ub.BookShelf).filter(ub.BookShelf.shelf == shelf_id).delete()
ub.session.commit() ub.session.commit()
flash( _("successfully deleted shelf %(name)s", name=cur_shelf.name, category="success") ) flash(_("successfully deleted shelf %(name)s", name=cur_shelf.name, category="success"))
return redirect(url_for('index')) return redirect(url_for('index'))
@app.route("/shelf/<int:shelf_id>") @app.route("/shelf/<int:shelf_id>")
@login_required_if_no_ano @login_required_if_no_ano
def show_shelf(shelf_id): def show_shelf(shelf_id):
if current_user.is_anonymous(): if current_user.is_anonymous():
shelf = ub.session.query(ub.Shelf).filter(ub.Shelf.is_public == 1, ub.Shelf.id == shelf_id).first() shelf = ub.session.query(ub.Shelf).filter(ub.Shelf.is_public == 1, ub.Shelf.id == shelf_id).first()
else : else:
shelf = ub.session.query(ub.Shelf).filter(ub.or_(ub.and_(ub.Shelf.user_id == int(current_user.id), ub.Shelf.id == shelf_id), ub.and_(ub.Shelf.is_public == 1, ub.Shelf.id == shelf_id))).first() 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() result = list()
if shelf: if shelf:
books_in_shelf = ub.session.query(ub.BookShelf).filter(ub.BookShelf.shelf == shelf_id).all() books_in_shelf = ub.session.query(ub.BookShelf).filter(ub.BookShelf.shelf == shelf_id).all()
...@@ -1111,9 +1278,10 @@ def show_shelf(shelf_id): ...@@ -1111,9 +1278,10 @@ def show_shelf(shelf_id):
cur_book = db.session.query(db.Books).filter(db.Books.id == book.book_id).first() cur_book = db.session.query(db.Books).filter(db.Books.id == book.book_id).first()
result.append(cur_book) 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 @login_required
def profile(): def profile():
content = ub.session.query(ub.User).filter(ub.User.id == int(current_user.id)).first() content = ub.session.query(ub.User).filter(ub.User.id == int(current_user.id)).first()
...@@ -1125,7 +1293,7 @@ def profile(): ...@@ -1125,7 +1293,7 @@ def profile():
lang.name = cur_l.get_language_name(get_locale()) lang.name = cur_l.get_language_name(get_locale())
except: except:
lang.name = _(isoLanguages.get(part3=lang.lang_code).name) 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: for book in content.downloads:
downloads.append(db.session.query(db.Books).filter(db.Books.id == book.book_id).first()) downloads.append(db.session.query(db.Books).filter(db.Books.id == book.book_id).first())
if request.method == "POST": if request.method == "POST":
...@@ -1144,14 +1312,34 @@ def profile(): ...@@ -1144,14 +1312,34 @@ def profile():
content.default_language = to_save["default_language"] content.default_language = to_save["default_language"]
if to_save["locale"]: if to_save["locale"]:
content.locale = 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: try:
ub.session.commit() ub.session.commit()
except IntegrityError: except IntegrityError:
ub.session.rollback() ub.session.rollback()
flash(_(u"Found an existing account for this email address."), category="error") 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") 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") @app.route("/admin/user")
@login_required @login_required
...@@ -1161,7 +1349,8 @@ def user_list(): ...@@ -1161,7 +1349,8 @@ def user_list():
settings = ub.session.query(ub.Settings).first() settings = ub.session.query(ub.Settings).first()
return render_template("user_list.html", content=content, email=settings, title=_(u"User list")) 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 @login_required
@admin_required @admin_required
def new_user(): def new_user():
...@@ -1173,7 +1362,7 @@ def new_user(): ...@@ -1173,7 +1362,7 @@ def new_user():
lang.name = cur_l.get_language_name(get_locale()) lang.name = cur_l.get_language_name(get_locale())
except: except:
lang.name = _(isoLanguages.get(part3=lang.lang_code).name) 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": if request.method == "POST":
to_save = request.form.to_dict() to_save = request.form.to_dict()
if not to_save["nickname"] or not to_save["email"] or not to_save["password"]: if not to_save["nickname"] or not to_save["email"] or not to_save["password"]:
...@@ -1211,14 +1400,16 @@ def new_user(): ...@@ -1211,14 +1400,16 @@ def new_user():
try: try:
ub.session.add(content) ub.session.add(content)
ub.session.commit() ub.session.commit()
flash(_("User '%(user)s' created" , user=content.nickname), category="success") flash(_("User '%(user)s' created", user=content.nickname), category="success")
return redirect(url_for('user_list', _external=True)) return redirect(url_for('user_list', _external=True))
except IntegrityError: except IntegrityError:
ub.session.rollback() ub.session.rollback()
flash(_(u"Found an existing account for this email address or nickname."), category="error") flash(_(u"Found an existing account for this email address or nickname."), category="error")
return render_template("user_edit.html", new_user=1, content=content, translations=translations, 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 @login_required
@admin_required @admin_required
def edit_mailsettings(): def edit_mailsettings():
...@@ -1237,11 +1428,21 @@ def edit_mailsettings(): ...@@ -1237,11 +1428,21 @@ def edit_mailsettings():
try: try:
ub.session.commit() ub.session.commit()
flash(_(u"Mail settings updated"), category="success") flash(_(u"Mail settings updated"), category="success")
except (e): except e:
flash(e, category="error") 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")) return render_template("email_edit.html", content=content, title=_("Edit mail settings"))
@app.route("/admin/user/<int:user_id>", methods = ["GET", "POST"])
@app.route("/admin/user/<int:user_id>", methods=["GET", "POST"])
@login_required @login_required
@admin_required @admin_required
def edit_user(user_id): def edit_user(user_id):
...@@ -1261,35 +1462,35 @@ def edit_user(user_id): ...@@ -1261,35 +1462,35 @@ def edit_user(user_id):
to_save = request.form.to_dict() to_save = request.form.to_dict()
if "delete" in to_save: if "delete" in to_save:
ub.session.delete(content) 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)) return redirect(url_for('user_list', _external=True))
else: else:
if to_save["password"]: if to_save["password"]:
content.password = generate_password_hash(to_save["password"]) content.password = generate_password_hash(to_save["password"])
if "admin_role" in to_save and not content.role_admin(): if "admin_role" in to_save and not content.role_admin():
content.role = content.role + ub.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 content.role = content.role - ub.ROLE_ADMIN
if "download_role" in to_save and not content.role_download(): if "download_role" in to_save and not content.role_download():
content.role = content.role + ub.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 content.role = content.role - ub.ROLE_DOWNLOAD
if "upload_role" in to_save and not content.role_upload(): if "upload_role" in to_save and not content.role_upload():
content.role = content.role + ub.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 content.role = content.role - ub.ROLE_UPLOAD
if "edit_role" in to_save and not content.role_edit(): if "edit_role" in to_save and not content.role_edit():
content.role = content.role + ub.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 content.role = content.role - ub.ROLE_EDIT
if "passwd_role" in to_save and not content.role_passwd(): if "passwd_role" in to_save and not content.role_passwd():
content.role = content.role + ub.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.role = content.role - ub.ROLE_PASSWD
content.random_books = 0 content.random_books = 0
content.language_books = 0 content.language_books = 0
...@@ -1316,18 +1517,20 @@ def edit_user(user_id): ...@@ -1316,18 +1517,20 @@ def edit_user(user_id):
content.kindle_mail = to_save["kindle_mail"] content.kindle_mail = to_save["kindle_mail"]
try: try:
ub.session.commit() 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: except IntegrityError:
ub.session.rollback() ub.session.rollback()
flash(_(u"An unknown error occured."), category="error") 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/<int:book_id>", methods=['GET', 'POST']) @app.route("/admin/book/<int:book_id>", methods=['GET', 'POST'])
@login_required @login_required
@edit_required @edit_required
def edit_book(book_id): def edit_book(book_id):
## create the function for sorting... # 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("title_sort", 1, db.title_sort)
cc = db.session.query(db.Custom_Columns).filter(db.Custom_Columns.datatype.notin_(db.cc_exceptions)).all() cc = db.session.query(db.Custom_Columns).filter(db.Custom_Columns.datatype.notin_(db.cc_exceptions)).all()
if current_user.filter_language() != "all": if current_user.filter_language() != "all":
filter = db.Books.languages.any(db.Languages.lang_code == current_user.filter_language()) filter = db.Books.languages.any(db.Languages.lang_code == current_user.filter_language())
...@@ -1338,9 +1541,10 @@ def edit_book(book_id): ...@@ -1338,9 +1541,10 @@ def edit_book(book_id):
if book: if book:
for index in range(0, len(book.languages)): for index in range(0, len(book.languages)):
try: 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: 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: for author in book.authors:
author_names.append(author.name) author_names.append(author.name)
if request.method == 'POST': if request.method == 'POST':
...@@ -1356,7 +1560,7 @@ def edit_book(book_id): ...@@ -1356,7 +1560,7 @@ def edit_book(book_id):
modify_database_object(input_authors, book.authors, db.Authors, db.session, 'author') modify_database_object(input_authors, book.authors, db.Authors, db.session, 'author')
if author0_before_edit != book.authors[0].name: if author0_before_edit != book.authors[0].name:
edited_books_id.add(book.id) edited_books_id.add(book.id)
if to_save["cover_url"] and os.path.splitext(to_save["cover_url"])[1].lower() == ".jpg": if to_save["cover_url"] and os.path.splitext(to_save["cover_url"])[1].lower() == ".jpg":
img = requests.get(to_save["cover_url"]) img = requests.get(to_save["cover_url"])
f = open(os.path.join(config.DB_ROOT, book.path, "cover.jpg"), "wb") f = open(os.path.join(config.DB_ROOT, book.path, "cover.jpg"), "wb")
...@@ -1384,7 +1588,7 @@ def edit_book(book_id): ...@@ -1384,7 +1588,7 @@ def edit_book(book_id):
# retranslate displayed text to language codes # retranslate displayed text to language codes
languages = db.session.query(db.Languages).all() languages = db.session.query(db.Languages).all()
input_l=[] input_l = []
for lang in languages: for lang in languages:
try: try:
lang.name = LC.parse(lang.lang_code).get_language_name(get_locale()).lower() lang.name = LC.parse(lang.lang_code).get_language_name(get_locale()).lower()
...@@ -1399,7 +1603,7 @@ def edit_book(book_id): ...@@ -1399,7 +1603,7 @@ def edit_book(book_id):
old_rating = False old_rating = False
if len(book.ratings) > 0: if len(book.ratings) > 0:
old_rating = book.ratings[0].rating old_rating = book.ratings[0].rating
ratingx2 = int(float(to_save["rating"]) *2) ratingx2 = int(float(to_save["rating"]) * 2)
if ratingx2 != old_rating: if ratingx2 != old_rating:
is_rating = db.session.query(db.Ratings).filter(db.Ratings.rating == ratingx2).first() is_rating = db.session.query(db.Ratings).filter(db.Ratings.rating == ratingx2).first()
if is_rating: if is_rating:
...@@ -1423,43 +1627,45 @@ def edit_book(book_id): ...@@ -1423,43 +1627,45 @@ def edit_book(book_id):
if to_save[cc_string].strip(): if to_save[cc_string].strip():
if c.datatype == 'bool': if c.datatype == 'bool':
if to_save[cc_string] == 'None': if to_save[cc_string] == 'None':
to_save[cc_string]= None to_save[cc_string] = None
else: else:
to_save[cc_string] = 1 if to_save[cc_string] == 'True' else 0 to_save[cc_string] = 1 if to_save[cc_string] == 'True' else 0
if to_save[cc_string] != cc_db_value: if to_save[cc_string] != cc_db_value:
if cc_db_value is not None: if cc_db_value is not None:
if to_save[cc_string] is not None: if to_save[cc_string] is not None:
setattr(getattr(book, cc_string)[0], 'value', to_save[cc_string]) setattr(getattr(book, cc_string)[0], 'value', to_save[cc_string])
else : else:
del_cc = getattr(book, cc_string)[0] del_cc = getattr(book, cc_string)[0]
getattr(book, cc_string).remove(del_cc) getattr(book, cc_string).remove(del_cc)
db.session.delete(del_cc) db.session.delete(del_cc)
else : else:
cc_class = db.cc_classes[c.id] 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) db.session.add(new_cc)
else: else:
if c.datatype == 'rating': 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 to_save[cc_string].strip() != cc_db_value:
if cc_db_value != None: if cc_db_value is not None:
#remove old cc_val # remove old cc_val
del_cc = getattr(book, cc_string)[0] del_cc = getattr(book, cc_string)[0]
getattr(book, cc_string).remove(del_cc) getattr(book, cc_string).remove(del_cc)
if len(del_cc.books) == 0: if len(del_cc.books) == 0:
db.session.delete(del_cc) db.session.delete(del_cc)
cc_class = db.cc_classes[c.id] 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 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()) new_cc = cc_class(value=to_save[cc_string].strip())
db.session.add(new_cc) 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 # add cc value to book
getattr(book, cc_string).append(new_cc) getattr(book, cc_string).append(new_cc)
else: else:
if cc_db_value != None: if cc_db_value is not None:
#remove old cc_val # remove old cc_val
del_cc = getattr(book, cc_string)[0] del_cc = getattr(book, cc_string)[0]
getattr(book, cc_string).remove(del_cc) getattr(book, cc_string).remove(del_cc)
if len(del_cc.books) == 0: if len(del_cc.books) == 0:
...@@ -1476,7 +1682,7 @@ def edit_book(book_id): ...@@ -1476,7 +1682,7 @@ def edit_book(book_id):
for inp_tag in input_tags: for inp_tag in input_tags:
if inp_tag == c_tag.value: if inp_tag == c_tag.value:
found = True found = True
break; break
# if the tag was not found in the new list, add him to remove list # if the tag was not found in the new list, add him to remove list
if not found: if not found:
del_tags.append(c_tag) del_tags.append(c_tag)
...@@ -1487,7 +1693,7 @@ def edit_book(book_id): ...@@ -1487,7 +1693,7 @@ def edit_book(book_id):
for c_tag in getattr(book, cc_string): for c_tag in getattr(book, cc_string):
if inp_tag == c_tag.value: if inp_tag == c_tag.value:
found = True found = True
break; break
if not found: if not found:
add_tags.append(inp_tag) add_tags.append(inp_tag)
# if there are tags to remove, we remove them now # if there are tags to remove, we remove them now
...@@ -1500,12 +1706,14 @@ def edit_book(book_id): ...@@ -1500,12 +1706,14 @@ def edit_book(book_id):
if len(add_tags) > 0: if len(add_tags) > 0:
for add_tag in add_tags: for add_tag in add_tags:
# check if a tag with that name exists # 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 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) new_tag = db.cc_classes[c.id](value=add_tag)
db.session.add(new_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 # add tag to book
getattr(book, cc_string).append(new_tag) getattr(book, cc_string).append(new_tag)
...@@ -1525,18 +1733,16 @@ def edit_book(book_id): ...@@ -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") flash(_(u"Error opening eBook. File does not exist or file is not accessible:"), category="error")
return redirect('/' or url_for("index", _external=True)) 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 @login_required
@upload_required @upload_required
def upload(): def upload():
if not config.UPLOADING: if not config.UPLOADING:
abort(404) abort(404)
## create the function for sorting... # 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("title_sort", 1, db.title_sort)
db.session.connection().connection.connection.create_function('uuid4', 0, lambda : str(uuid4())) db.session.connection().connection.connection.create_function('uuid4', 0, lambda: str(uuid4()))
if request.method == 'POST' and 'btn-upload' in request.files: if request.method == 'POST' and 'btn-upload' in request.files:
file = request.files['btn-upload'] file = request.files['btn-upload']
meta = uploader.upload(file) meta = uploader.upload(file)
...@@ -1582,11 +1788,12 @@ def upload(): ...@@ -1582,11 +1788,12 @@ def upload():
db_author = db.Authors(author, "", "") db_author = db.Authors(author, "", "")
db.session.add(db_author) db.session.add(db_author)
path = os.path.join(author_dir, title_dir) 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_book.authors.append(db_author)
db_data = db.Data(db_book, meta.extension.upper()[1:], file_size, data_name) db_data = db.Data(db_book, meta.extension.upper()[1:], file_size, data_name)
db_book.data.append(db_data) db_book.data.append(db_data)
db.session.add(db_book) db.session.add(db_book)
db.session.commit() db.session.commit()
author_names = [] author_names = []
...@@ -1596,4 +1803,4 @@ def upload(): ...@@ -1596,4 +1803,4 @@ def upload():
if current_user.role_edit() or current_user.role_admin(): if current_user.role_edit() or current_user.role_admin():
return render_template('edit_book.html', book=db_book, authors=author_names, cc=cc) return render_template('edit_book.html', book=db_book, authors=author_names, cc=cc)
book_in_shelfs = [] book_in_shelfs = []
return render_template('detail.html', entry=db_book, cc=cc, title=db_book.title, books_shelfs=book_in_shelfs) return render_template('detail.html', entry=db_book, cc=cc, title=db_book.title, books_shelfs=book_in_shelfs)
\ No newline at end of file
...@@ -8,7 +8,7 @@ msgid "" ...@@ -8,7 +8,7 @@ msgid ""
msgstr "" msgstr ""
"Project-Id-Version: PROJECT VERSION\n" "Project-Id-Version: PROJECT VERSION\n"
"Report-Msgid-Bugs-To: EMAIL@ADDRESS\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" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n" "Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
"Language-Team: LANGUAGE <LL@li.org>\n" "Language-Team: LANGUAGE <LL@li.org>\n"
...@@ -17,244 +17,266 @@ msgstr "" ...@@ -17,244 +17,266 @@ msgstr ""
"Content-Transfer-Encoding: 8bit\n" "Content-Transfer-Encoding: 8bit\n"
"Generated-By: Babel 2.3.4\n" "Generated-By: Babel 2.3.4\n"
#: cps/helper.py:80 cps/templates/detail.html:113 #: cps/book_formats.py:108 cps/book_formats.py:112 cps/web.py:923
msgid "Send to Kindle" msgid "not installed"
msgstr ""
#: cps/helper.py:93
msgid "Calibre-web test email"
msgstr "" msgstr ""
#: cps/helper.py:81 #: cps/helper.py:94 cps/helper.py:147
msgid "This email has been sent via calibre web." msgid "This email has been sent via calibre web."
msgstr "" msgstr ""
#: cps/helper.py:103 cps/helper.py:118 #: cps/helper.py:128 cps/helper.py:216
msgid "Could not find any formats suitable for sending by email" #, python-format
msgid "Failed to send mail: %s"
msgstr "" msgstr ""
#: cps/helper.py:112 #: cps/helper.py:146 cps/templates/detail.html:113
msgid "Could not convert epub to mobi" msgid "Send to Kindle"
msgstr "" msgstr ""
#: cps/helper.py:142 #: cps/helper.py:169 cps/helper.py:184
#, python-format msgid "Could not find any formats suitable for sending by email"
msgid "Failed to send mail: %s"
msgstr "" 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?" msgid "The requested file could not be read. Maybe wrong permissions?"
msgstr "" msgstr ""
#: cps/web.py:639 #: cps/web.py:717
msgid "Latest Books" msgid "Latest Books"
msgstr "" msgstr ""
#: cps/web.py:661 #: cps/web.py:742
msgid "Hot Books (most downloaded)" msgid "Hot Books (most downloaded)"
msgstr "" msgstr ""
#: cps/templates/index.xml:41 cps/web.py:668 #: cps/templates/index.xml:41 cps/web.py:750
msgid "Random Books" msgid "Random Books"
msgstr "" msgstr ""
#: cps/web.py:679 #: cps/web.py:763
msgid "Author list" msgid "Author list"
msgstr "" msgstr ""
#: cps/web.py:695 #: cps/web.py:780
#, python-format #, python-format
msgid "Author: %(nam)s" msgid "Author: %(nam)s"
msgstr "" msgstr ""
#: cps/templates/index.xml:65 cps/web.py:706 #: cps/templates/index.xml:65 cps/web.py:793
msgid "Series list" msgid "Series list"
msgstr "" msgstr ""
#: cps/web.py:714 #: cps/web.py:804
#, python-format #, python-format
msgid "Series: %(serie)s" msgid "Series: %(serie)s"
msgstr "" 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:" msgid "Error opening eBook. File does not exist or file is not accessible:"
msgstr "" msgstr ""
#: cps/web.py:742 #: cps/web.py:837
msgid "Available languages" msgid "Available languages"
msgstr "" msgstr ""
#: cps/web.py:754 #: cps/web.py:852
#, python-format #, python-format
msgid "Language: %(name)s" msgid "Language: %(name)s"
msgstr "" msgstr ""
#: cps/templates/index.xml:57 cps/web.py:765 #: cps/templates/index.xml:57 cps/web.py:865
msgid "Category list" msgid "Category list"
msgstr "" msgstr ""
#: cps/web.py:772 #: cps/web.py:875
#, python-format #, python-format
msgid "Category: %(name)s" msgid "Category: %(name)s"
msgstr "" msgstr ""
#: cps/web.py:810 #: cps/web.py:931
msgid "Statistics" msgid "Statistics"
msgstr "" 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" msgid "Read a Book"
msgstr "" msgstr ""
#: cps/web.py:951 cps/web.py:1179 #: cps/web.py:1100 cps/web.py:1365
msgid "Please fill out all fields!" msgid "Please fill out all fields!"
msgstr "" msgstr ""
#: cps/web.py:967 #: cps/web.py:1116
msgid "An unknown error occured. Please try again later." msgid "An unknown error occured. Please try again later."
msgstr "" msgstr ""
#: cps/web.py:972 #: cps/web.py:1121
msgid "This username or email address is already in use." msgid "This username or email address is already in use."
msgstr "" msgstr ""
#: cps/web.py:975 #: cps/web.py:1124
msgid "register" msgid "register"
msgstr "" msgstr ""
#: cps/web.py:990 #: cps/web.py:1140
#, python-format #, python-format
msgid "you are now logged in as: '%(nickname)s'" msgid "you are now logged in as: '%(nickname)s'"
msgstr "" msgstr ""
#: cps/web.py:993 #: cps/web.py:1143
msgid "Wrong Username or Password" msgid "Wrong Username or Password"
msgstr "" msgstr ""
#: cps/web.py:995 #: cps/web.py:1145
msgid "login" msgid "login"
msgstr "" msgstr ""
#: cps/web.py:1011 #: cps/web.py:1162
msgid "Please configure the SMTP mail settings first..." msgid "Please configure the SMTP mail settings first..."
msgstr "" msgstr ""
#: cps/web.py:1015 #: cps/web.py:1166
#, python-format #, python-format
msgid "Book successfully send to %(kindlemail)s" msgid "Book successfully send to %(kindlemail)s"
msgstr "" msgstr ""
#: cps/web.py:1018 #: cps/web.py:1170
#, python-format #, python-format
msgid "There was an error sending this book: %(res)s" msgid "There was an error sending this book: %(res)s"
msgstr "" msgstr ""
#: cps/web.py:1020 #: cps/web.py:1172
msgid "Please configure your kindle email address first..." msgid "Please configure your kindle email address first..."
msgstr "" msgstr ""
#: cps/web.py:1035 #: cps/web.py:1188
#, python-format #, python-format
msgid "Book has been added to shelf: %(sname)s" msgid "Book has been added to shelf: %(sname)s"
msgstr "" msgstr ""
#: cps/web.py:1054 #: cps/web.py:1209
#, python-format #, python-format
msgid "Book has been removed from shelf: %(sname)s" msgid "Book has been removed from shelf: %(sname)s"
msgstr "" msgstr ""
#: cps/web.py:1070 #: cps/web.py:1226
#, python-format #, python-format
msgid "A shelf with the name '%(title)s' already exists." msgid "A shelf with the name '%(title)s' already exists."
msgstr "" msgstr ""
#: cps/web.py:1075 #: cps/web.py:1231
#, python-format #, python-format
msgid "Shelf %(title)s created" msgid "Shelf %(title)s created"
msgstr "" msgstr ""
#: cps/web.py:1077 #: cps/web.py:1233
msgid "There was an error" msgid "There was an error"
msgstr "" msgstr ""
#: cps/web.py:1078 cps/web.py:1080 #: cps/web.py:1234 cps/web.py:1236
msgid "create a shelf" msgid "create a shelf"
msgstr "" msgstr ""
#: cps/web.py:1096 #: cps/web.py:1256
#, python-format #, python-format
msgid "successfully deleted shelf %(name)s" msgid "successfully deleted shelf %(name)s"
msgstr "" msgstr ""
#: cps/web.py:1113 #: cps/web.py:1277
#, python-format #, python-format
msgid "Shelf: '%(name)s'" msgid "Shelf: '%(name)s'"
msgstr "" msgstr ""
#: cps/web.py:1150 #: cps/web.py:1332
msgid "Found an existing account for this email address." msgid "Found an existing account for this email address."
msgstr "" msgstr ""
#: cps/web.py:1151 cps/web.py:1153 #: cps/web.py:1334 cps/web.py:1337
#, python-format #, python-format
msgid "%(name)s's profile" msgid "%(name)s's profile"
msgstr "" msgstr ""
#: cps/web.py:1152 #: cps/web.py:1335
msgid "Profile updated" msgid "Profile updated"
msgstr "" msgstr ""
#: cps/web.py:1161 #: cps/web.py:1346
msgid "User list" msgid "User list"
msgstr "" 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" msgid "Add new user"
msgstr "" msgstr ""
#: cps/web.py:1213 #: cps/web.py:1399
#, python-format #, python-format
msgid "User '%(user)s' created" msgid "User '%(user)s' created"
msgstr "" msgstr ""
#: cps/web.py:1217 #: cps/web.py:1403
msgid "Found an existing account for this email address or nickname." msgid "Found an existing account for this email address or nickname."
msgstr "" msgstr ""
#: cps/web.py:1238 #: cps/web.py:1426 cps/web.py:1437
msgid "Mail settings updated" msgid "Mail settings updated"
msgstr "" 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" msgid "Edit mail settings"
msgstr "" msgstr ""
#: cps/web.py:1263 #: cps/web.py:1461
#, python-format #, python-format
msgid "User '%(nick)s' deleted" msgid "User '%(nick)s' deleted"
msgstr "" msgstr ""
#: cps/web.py:1318 #: cps/web.py:1516
#, python-format #, python-format
msgid "User '%(nick)s' updated" msgid "User '%(nick)s' updated"
msgstr "" msgstr ""
#: cps/web.py:1321 #: cps/web.py:1519
msgid "An unknown error occured." msgid "An unknown error occured."
msgstr "" msgstr ""
#: cps/web.py:1322 #: cps/web.py:1521
#, python-format #, python-format
msgid "Edit User %(nick)s" msgid "Edit User %(nick)s"
msgstr "" msgstr ""
#: cps/web.py:1556 #: cps/web.py:1759
#, python-format #, python-format
msgid "Failed to create path %s (Permission denied)." msgid "Failed to create path %s (Permission denied)."
msgstr "" msgstr ""
#: cps/web.py:1561 #: cps/web.py:1764
#, python-format #, python-format
msgid "Failed to store file %s (Permission denied)." msgid "Failed to store file %s (Permission denied)."
msgstr "" msgstr ""
#: cps/web.py:1566 #: cps/web.py:1769
#, python-format #, python-format
msgid "Failed to delete file %s (Permission denied)." msgid "Failed to delete file %s (Permission denied)."
msgstr "" msgstr ""
...@@ -339,14 +361,14 @@ msgstr "" ...@@ -339,14 +361,14 @@ msgstr ""
msgid "view book after edit" msgid "view book after edit"
msgstr "" msgstr ""
#: cps/templates/edit_book.html:105 cps/templates/email_edit.html:30 #: cps/templates/edit_book.html:105 cps/templates/login.html:19
#: cps/templates/login.html:19 cps/templates/search_form.html:33 #: cps/templates/search_form.html:33 cps/templates/shelf_edit.html:15
#: cps/templates/shelf_edit.html:15 cps/templates/user_edit.html:93 #: cps/templates/user_edit.html:94
msgid "Submit" msgid "Submit"
msgstr "" msgstr ""
#: cps/templates/edit_book.html:106 cps/templates/email_edit.html:31 #: cps/templates/edit_book.html:106 cps/templates/email_edit.html:32
#: cps/templates/user_edit.html:95 #: cps/templates/user_edit.html:96
msgid "Back" msgid "Back"
msgstr "" msgstr ""
...@@ -374,6 +396,14 @@ msgstr "" ...@@ -374,6 +396,14 @@ msgstr ""
msgid "From e-mail" msgid "From e-mail"
msgstr "" 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 #: cps/templates/feed.xml:14
msgid "Next" msgid "Next"
msgstr "" msgstr ""
...@@ -504,6 +534,14 @@ msgstr "" ...@@ -504,6 +534,14 @@ msgstr ""
msgid "Remember me" msgid "Remember me"
msgstr "" 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 #: cps/templates/register.html:4
msgid "Register a new account" msgid "Register a new account"
msgstr "" msgstr ""
...@@ -544,6 +582,10 @@ msgstr "" ...@@ -544,6 +582,10 @@ msgstr ""
msgid "Delete this Shelf" msgid "Delete this Shelf"
msgstr "" msgstr ""
#: cps/templates/shelf.html:7
msgid "Edit Shelf name"
msgstr ""
#: cps/templates/shelf_edit.html:7 #: cps/templates/shelf_edit.html:7
msgid "Title" msgid "Title"
msgstr "" msgstr ""
...@@ -552,11 +594,27 @@ msgstr "" ...@@ -552,11 +594,27 @@ msgstr ""
msgid "should the shelf be public?" msgid "should the shelf be public?"
msgstr "" 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" msgid "Books in this Library"
msgstr "" msgstr ""
#: cps/templates/stats.html:5 #: cps/templates/stats.html:41
msgid "Authors in this Library" msgid "Authors in this Library"
msgstr "" msgstr ""
...@@ -572,51 +630,51 @@ msgstr "" ...@@ -572,51 +630,51 @@ msgstr ""
msgid "Show all" msgid "Show all"
msgstr "" msgstr ""
#: cps/templates/user_edit.html:46 #: cps/templates/user_edit.html:45
msgid "Show random books" msgid "Show random books"
msgstr "" msgstr ""
#: cps/templates/user_edit.html:50 #: cps/templates/user_edit.html:49
msgid "Show hot books" msgid "Show hot books"
msgstr "" msgstr ""
#: cps/templates/user_edit.html:54 #: cps/templates/user_edit.html:53
msgid "Show language selection" msgid "Show language selection"
msgstr "" msgstr ""
#: cps/templates/user_edit.html:58 #: cps/templates/user_edit.html:57
msgid "Show series selection" msgid "Show series selection"
msgstr "" msgstr ""
#: cps/templates/user_edit.html:62 #: cps/templates/user_edit.html:61
msgid "Show category selection" msgid "Show category selection"
msgstr "" msgstr ""
#: cps/templates/user_edit.html:67 #: cps/templates/user_edit.html:68
msgid "Admin user" msgid "Admin user"
msgstr "" msgstr ""
#: cps/templates/user_edit.html:71 #: cps/templates/user_edit.html:72
msgid "Allow Downloads" msgid "Allow Downloads"
msgstr "" msgstr ""
#: cps/templates/user_edit.html:75 #: cps/templates/user_edit.html:76
msgid "Allow Uploads" msgid "Allow Uploads"
msgstr "" msgstr ""
#: cps/templates/user_edit.html:79 #: cps/templates/user_edit.html:80
msgid "Allow Edit" msgid "Allow Edit"
msgstr "" msgstr ""
#: cps/templates/user_edit.html:83 #: cps/templates/user_edit.html:84
msgid "Allow Changing Password" msgid "Allow Changing Password"
msgstr "" msgstr ""
#: cps/templates/user_edit.html:89 #: cps/templates/user_edit.html:90
msgid "Delete this user" msgid "Delete this user"
msgstr "" msgstr ""
#: cps/templates/user_edit.html:100 #: cps/templates/user_edit.html:101
msgid "Recent Downloads" msgid "Recent Downloads"
msgstr "" msgstr ""
...@@ -664,5 +722,3 @@ msgstr "" ...@@ -664,5 +722,3 @@ msgstr ""
msgid "Change SMTP settings" msgid "Change SMTP settings"
msgstr "" msgstr ""
msgid "Latin"
msgstr ""
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment