1
0
mirror of https://github.com/janeczku/calibre-web synced 2024-06-26 07:03:17 +00:00

merge conflicts

This commit is contained in:
idalin 2017-02-04 10:57:32 +08:00
commit f11b123686
40 changed files with 1387 additions and 73558 deletions

1
.gitattributes vendored Normal file
View File

@ -0,0 +1 @@
web.py ident export-subst

View File

@ -1,13 +0,0 @@
[General]
DB_ROOT =
APP_DB_ROOT =
MAIN_DIR =
LOG_DIR =
PORT = 8083
NEWEST_BOOKS = 60
[Advanced]
TITLE_REGEX = ^(A|The|An|Der|Die|Das|Den|Ein|Eine|Einen|Dem|Des|Einem|Eines)\s+
DEVELOPMENT = 0
PUBLIC_REG = 0
UPLOADING = 0
ANON_BROWSE = 0

51
cps.py
View File

@ -1,61 +1,30 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
import os
import sys
from threading import Thread
from multiprocessing import Queue
import time
base_path = os.path.dirname(os.path.abspath(__file__))
# Insert local directories into path
sys.path.insert(0, os.path.join(base_path, 'vendor'))
from cps import web
from cps import config
from tornado.wsgi import WSGIContainer
from tornado.httpserver import HTTPServer
from tornado.ioloop import IOLoop
global title_sort
def start_calibreweb(messagequeue):
web.global_queue = messagequeue
if config.DEVELOPMENT:
web.app.run(host="0.0.0.0", port=config.PORT, debug=True)
if __name__ == '__main__':
if web.ub.DEVELOPMENT:
web.app.run(host="0.0.0.0", port=web.ub.config.config_port, debug=True)
else:
http_server = HTTPServer(WSGIContainer(web.app))
http_server.listen(config.PORT)
http_server.listen(web.ub.config.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)
if web.global_task == 0:
print("Performing restart of Calibre-web")
os.execl(sys.executable, sys.executable, *sys.argv)
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()
print("Performing shutdown of Calibre-web")
sys.exit(0)

View File

@ -1,3 +1,6 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
import logging
import uploader
import os

View File

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

163
cps/db.py
View File

@ -5,15 +5,24 @@ from sqlalchemy import *
from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy.orm import *
import os
import config
import re
import ast
from ub import config
import ub
# calibre sort stuff
title_pat = re.compile(config.TITLE_REGEX, re.IGNORECASE)
session = None
cc_exceptions = None
cc_classes = None
cc_ids = None
books_custom_column_links = None
engine = None
# user defined sort function for calibre databases (Series, etc.)
def title_sort(title):
# calibre sort stuff
# config=Config()
title_pat = re.compile(config.config_title_regex, re.IGNORECASE)
match = title_pat.search(title)
if match:
prep = match.group(1)
@ -21,59 +30,32 @@ def title_sort(title):
return title.strip()
dbpath = os.path.join(config.DB_ROOT, "metadata.db")
engine = create_engine('sqlite:///{0}'.format(dbpath.encode('utf-8')), echo=False)
conn = engine.connect()
conn.connection.create_function('title_sort', 1, title_sort)
Base = declarative_base()
books_authors_link = Table('books_authors_link', Base.metadata,
Column('book', Integer, ForeignKey('books.id'), primary_key=True),
Column('author', Integer, ForeignKey('authors.id'), primary_key=True)
)
Column('book', Integer, ForeignKey('books.id'), primary_key=True),
Column('author', Integer, ForeignKey('authors.id'), primary_key=True)
)
books_tags_link = Table('books_tags_link', Base.metadata,
Column('book', Integer, ForeignKey('books.id'), primary_key=True),
Column('tag', Integer, ForeignKey('tags.id'), primary_key=True)
)
Column('book', Integer, ForeignKey('books.id'), primary_key=True),
Column('tag', Integer, ForeignKey('tags.id'), primary_key=True)
)
books_series_link = Table('books_series_link', Base.metadata,
Column('book', Integer, ForeignKey('books.id'), primary_key=True),
Column('series', Integer, ForeignKey('series.id'), primary_key=True)
)
Column('book', Integer, ForeignKey('books.id'), primary_key=True),
Column('series', Integer, ForeignKey('series.id'), primary_key=True)
)
books_ratings_link = Table('books_ratings_link', Base.metadata,
Column('book', Integer, ForeignKey('books.id'), primary_key=True),
Column('rating', Integer, ForeignKey('ratings.id'), primary_key=True)
)
Column('book', Integer, ForeignKey('books.id'), primary_key=True),
Column('rating', Integer, ForeignKey('ratings.id'), primary_key=True)
)
books_languages_link = Table('books_languages_link', Base.metadata,
Column('book', Integer, ForeignKey('books.id'), primary_key=True),
Column('lang_code', Integer, ForeignKey('languages.id'), primary_key=True)
)
cc = conn.execute("SELECT id, datatype FROM custom_columns")
cc_ids = []
cc_exceptions = ['datetime', 'int', 'comments', 'float', 'composite', 'series']
books_custom_column_links = {}
cc_classes = {}
for row in cc:
if row.datatype not in cc_exceptions:
books_custom_column_links[row.id] = Table('books_custom_column_' + str(row.id) + '_link', Base.metadata,
Column('book', Integer, ForeignKey('books.id'), primary_key=True),
Column('value', Integer, ForeignKey('custom_column_' + str(row.id) + '.id'), primary_key=True)
)
cc_ids.append([row.id, row.datatype])
if row.datatype == 'bool':
ccdict = {'__tablename__': 'custom_column_' + str(row.id),
'id': Column(Integer, primary_key=True),
'book': Column(Integer, ForeignKey('books.id')),
'value': Column(Boolean)}
else:
ccdict = {'__tablename__': 'custom_column_' + str(row.id),
'id': Column(Integer, primary_key=True),
'value': Column(String)}
cc_classes[row.id] = type('Custom_Column_' + str(row.id), (Base,), ccdict)
Column('book', Integer, ForeignKey('books.id'), primary_key=True),
Column('lang_code', Integer, ForeignKey('languages.id'), primary_key=True)
)
class Identifiers(Base):
@ -243,9 +225,10 @@ class Books(Base):
series = relationship('Series', secondary=books_series_link, backref='books')
ratings = relationship('Ratings', secondary=books_ratings_link, backref='books')
languages = relationship('Languages', secondary=books_languages_link, backref='books')
identifiers=relationship('Identifiers', backref='books')
identifiers = relationship('Identifiers', backref='books')
def __init__(self, title, sort, author_sort, timestamp, pubdate, series_index, last_modified, path, has_cover, authors, tags):
def __init__(self, title, sort, author_sort, timestamp, pubdate, series_index, last_modified, path, has_cover,
authors, tags): # ToDO check Authors and tags necessary
self.title = title
self.sort = sort
self.author_sort = author_sort
@ -260,15 +243,6 @@ class Books(Base):
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:
if id[1] == 'bool':
setattr(Books, 'custom_column_' + str(id[0]), relationship(cc_classes[id[0]],
primaryjoin=(Books.id == cc_classes[id[0]].book),
backref='books'))
else:
setattr(Books, 'custom_column_' + str(id[0]), relationship(cc_classes[id[0]],
secondary = books_custom_column_links[id[0]],
backref='books'))
class Custom_Columns(Base):
@ -288,7 +262,76 @@ class Custom_Columns(Base):
display_dict = ast.literal_eval(self.display)
return display_dict
# Base.metadata.create_all(engine)
Session = sessionmaker()
Session.configure(bind=engine)
session = Session()
def setup_db():
global session
global cc_exceptions
global cc_classes
global cc_ids
global books_custom_column_links
global engine
if config.config_calibre_dir is None or config.config_calibre_dir == u'':
return False
dbpath = os.path.join(config.config_calibre_dir, "metadata.db")
engine = create_engine('sqlite:///{0}'.format(dbpath.encode('utf-8')), echo=False)
try:
conn = engine.connect()
except:
content = ub.session.query(ub.Settings).first()
content.config_calibre_dir = None
content.db_configured = False
ub.session.commit()
config.loadSettings()
return False
content = ub.session.query(ub.Settings).first()
content.db_configured = True
ub.session.commit()
config.loadSettings()
conn.connection.create_function('title_sort', 1, title_sort)
cc = conn.execute("SELECT id, datatype FROM custom_columns")
cc_ids = []
cc_exceptions = ['datetime', 'int', 'comments', 'float', 'composite', 'series']
books_custom_column_links = {}
cc_classes = {}
for row in cc:
if row.datatype not in cc_exceptions:
books_custom_column_links[row.id] = Table('books_custom_column_' + str(row.id) + '_link', Base.metadata,
Column('book', Integer, ForeignKey('books.id'),
primary_key=True),
Column('value', Integer,
ForeignKey('custom_column_' + str(row.id) + '.id'),
primary_key=True)
)
cc_ids.append([row.id, row.datatype])
if row.datatype == 'bool':
ccdict = {'__tablename__': 'custom_column_' + str(row.id),
'id': Column(Integer, primary_key=True),
'book': Column(Integer, ForeignKey('books.id')),
'value': Column(Boolean)}
else:
ccdict = {'__tablename__': 'custom_column_' + str(row.id),
'id': Column(Integer, primary_key=True),
'value': Column(String)}
cc_classes[row.id] = type('Custom_Column_' + str(row.id), (Base,), ccdict)
for id in cc_ids:
if id[1] == 'bool':
setattr(Books, 'custom_column_' + str(id[0]), relationship(cc_classes[id[0]],
primaryjoin=(
Books.id == cc_classes[id[0]].book),
backref='books'))
else:
setattr(Books, 'custom_column_' + str(id[0]), relationship(cc_classes[id[0]],
secondary=books_custom_column_links[id[0]],
backref='books'))
# Base.metadata.create_all(engine)
Session = sessionmaker()
Session.configure(bind=engine)
session = Session()
return True

View File

@ -1,3 +1,6 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
import zipfile
from lxml import etree
import os

View File

@ -1,9 +1,11 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
from lxml import etree
import os
import uploader
# ToDo: Check usage of original_file_name
def get_fb2_info(tmp_file_path, original_file_name, original_file_extension):
ns = {

View File

@ -1,8 +1,8 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
import db, ub
import config
import db
import ub
from flask import current_app as app
import logging
import smtplib
@ -21,7 +21,7 @@ from email.MIMEText import MIMEText
from email.generator import Generator
from flask_babel import gettext as _
import subprocess
import shutil
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 ==
@ -33,11 +33,13 @@ def update_download(book_id, user_id):
ub.session.commit()
def make_mobi(book_id):
def make_mobi(book_id, calibrepath):
vendorpath = os.path.join(os.path.normpath(os.path.dirname(os.path.realpath(__file__)) +
os.sep + "../vendor" + os.sep))
if sys.platform == "win32":
kindlegen = os.path.join(config.MAIN_DIR, "vendor", u"kindlegen.exe")
kindlegen = os.path.join(vendorpath, u"kindlegen.exe")
else:
kindlegen = os.path.join(config.MAIN_DIR, "vendor", u"kindlegen")
kindlegen = os.path.join(vendorpath, u"kindlegen")
if not os.path.exists(kindlegen):
app.logger.error("make_mobi: kindlegen binary not found in: %s" % kindlegen)
return None
@ -47,7 +49,7 @@ def make_mobi(book_id):
app.logger.error("make_mobi: epub format not found for book id: %d" % book_id)
return None
file_path = os.path.join(config.DB_ROOT, book.path, data.name)
file_path = os.path.join(calibrepath, book.path, data.name)
if os.path.exists(file_path + u".epub"):
p = subprocess.Popen((kindlegen + " \"" + file_path + u".epub\" ").encode(sys.getfilesystemencoding()),
shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE, stdin=subprocess.PIPE)
@ -79,26 +81,26 @@ def make_mobi(book_id):
class StderrLogger(object):
buffer=''
buffer = ''
def __init__(self):
self.logger = logging.getLogger('cps.web')
def write(self, message):
if message=='\n':
if message == '\n':
self.logger.debug(self.buffer)
self.buffer=''
self.buffer = ''
else:
self.buffer=self.buffer+message
self.buffer += message
def send_test_mail(kindle_mail):
def send_raw_email(kindle_mail, msg):
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)
use_ssl = int(settings.get('mail_use_ssl', 0))
# convert MIME message to string
fp = StringIO()
@ -108,21 +110,19 @@ def send_test_mail(kindle_mail):
# send email
try:
timeout=600 # set timeout to 5mins
timeout = 600 # set timeout to 5mins
org_stderr = smtplib.stderr
smtplib.stderr = StderrLogger()
if int(use_ssl) == 2:
if use_ssl == 2:
mailserver = smtplib.SMTP_SSL(settings["mail_server"], settings["mail_port"], timeout)
else:
mailserver = smtplib.SMTP(settings["mail_server"], settings["mail_port"], timeout)
mailserver.set_debuglevel(1)
if int(use_ssl) == 1:
#mailserver.ehlo()
if use_ssl == 1:
mailserver.starttls()
#mailserver.ehlo()
if settings["mail_password"]:
mailserver.login(settings["mail_login"], settings["mail_password"])
@ -138,28 +138,22 @@ def send_test_mail(kindle_mail):
return None
def send_mail(book_id, kindle_mail):
def send_test_mail(kindle_mail):
msg = MIMEMultipart()
msg['Subject'] = _(u'Calibre-web test email')
text = _(u'This email has been sent via calibre web.')
msg.attach(MIMEText(text.encode('UTF-8'), 'plain', 'UTF-8'))
return send_raw_email(kindle_mail, msg)
def send_mail(book_id, kindle_mail, calibrepath):
"""Send email with attachments"""
is_mobi = False
is_azw = False
is_azw3 = False
is_epub = False
is_pdf = False
file_path = None
settings = ub.get_mail_settings()
# create MIME message
msg = MIMEMultipart()
msg['From'] = settings["mail_from"]
msg['To'] = kindle_mail
msg['Subject'] = _(u'Send to Kindle')
text = _(u'This email has been sent via calibre web.')
msg.attach(MIMEText(text.encode('UTF-8'), 'plain', 'UTF-8'))
use_ssl = settings.get('mail_use_ssl', 0)
# attach files
# msg.attach(self.get_attachment(file_path))
book = db.session.query(db.Books).filter(db.Books.id == book_id).first()
data = db.session.query(db.Data).filter(db.Data.book == book.id)
@ -167,11 +161,11 @@ def send_mail(book_id, kindle_mail):
for entry in data:
if entry.format == "MOBI":
formats["mobi"] = os.path.join(config.DB_ROOT, book.path, entry.name + ".mobi")
formats["mobi"] = os.path.join(calibrepath, book.path, entry.name + ".mobi")
if entry.format == "EPUB":
formats["epub"] = os.path.join(config.DB_ROOT, book.path, entry.name + ".epub")
formats["epub"] = os.path.join(calibrepath, book.path, entry.name + ".epub")
if entry.format == "PDF":
formats["pdf"] = os.path.join(config.DB_ROOT, book.path, entry.name + ".pdf")
formats["pdf"] = os.path.join(calibrepath, book.path, entry.name + ".pdf")
if len(formats) == 0:
return _("Could not find any formats suitable for sending by email")
@ -179,7 +173,7 @@ def send_mail(book_id, kindle_mail):
if 'mobi' in formats:
msg.attach(get_attachment(formats['mobi']))
elif 'epub' in formats:
filepath = make_mobi(book.id)
filepath = make_mobi(book.id, calibrepath)
if filepath is not None:
msg.attach(get_attachment(filepath))
elif filepath is None:
@ -191,40 +185,7 @@ def send_mail(book_id, kindle_mail):
else:
return _("Could not find any formats suitable for sending by email")
# 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()
if int(use_ssl) == 2:
mailserver = smtplib.SMTP_SSL(settings["mail_server"], settings["mail_port"], timeout)
else:
mailserver = smtplib.SMTP(settings["mail_server"], settings["mail_port"], timeout)
mailserver.set_debuglevel(1)
if int(use_ssl) == 1:
mailserver.starttls()
if settings["mail_password"]:
mailserver.login(settings["mail_login"], settings["mail_password"])
mailserver.sendmail(settings["mail_login"], kindle_mail, msg)
mailserver.quit()
smtplib.stderr = org_stderr
except (socket.error, smtplib.SMTPRecipientsRefused, smtplib.SMTPException), e:
app.logger.error(traceback.print_exc())
return _("Failed to send mail: %s" % str(e))
return None
return send_raw_email(kindle_mail, msg)
def get_attachment(file_path):
@ -242,8 +203,7 @@ def get_attachment(file_path):
return attachment
except IOError:
traceback.print_exc()
message = (_('The requested file could not be read. Maybe wrong '\
'permissions?'))
message = (_('The requested file could not be read. Maybe wrong permissions?')) # ToDo: What is this?
return None
@ -253,7 +213,7 @@ def get_valid_filename(value, replace_whitespace=True):
filename. Limits num characters to 128 max.
"""
value = value[:128]
re_slugify = re.compile('[^\w\s-]', re.UNICODE)
# re_slugify = re.compile('[^\w\s-]', re.UNICODE)
value = unicodedata.normalize('NFKD', value)
re_slugify = re.compile('[^\w\s-]', re.UNICODE)
value = unicode(re_slugify.sub('', value).strip())
@ -273,10 +233,10 @@ def get_normalized_author(value):
return value
def update_dir_stucture(book_id):
def update_dir_stucture(book_id, calibrepath):
db.session.connection().connection.connection.create_function("title_sort", 1, db.title_sort)
book = db.session.query(db.Books).filter(db.Books.id == book_id).first()
path = os.path.join(config.DB_ROOT, book.path)
path = os.path.join(calibrepath, book.path)
authordir = book.path.split(os.sep)[0]
new_authordir = get_valid_filename(book.authors[0].name, False)
@ -290,7 +250,122 @@ def update_dir_stucture(book_id):
book.path = book.path.split(os.sep)[0] + os.sep + new_titledir
if authordir != new_authordir:
new_author_path = os.path.join(os.path.join(config.DB_ROOT, new_authordir), os.path.basename(path))
new_author_path = os.path.join(os.path.join(calibrepath, new_authordir), os.path.basename(path))
os.renames(path, new_author_path)
book.path = new_authordir + os.sep + book.path.split(os.sep)[1]
db.session.commit()
def file_to_list(file):
return [x.strip() for x in open(file, 'r') if not x.startswith('#EXT')]
def one_minus_two(one, two):
return [x for x in one if x not in set(two)]
def reduce_dirs(delete_files, new_list):
new_delete = []
for file in delete_files:
parts = file.split(os.sep)
sub = ''
for i in range(len(parts)):
sub = os.path.join(sub, parts[i])
if sub == '':
sub = os.sep
count = 0
for song in new_list:
if song.startswith(sub):
count += 1
break
if count == 0:
if sub != '\\':
new_delete.append(sub)
break
return list(set(new_delete))
def reduce_files(remove_items, exclude_items):
rf = []
for item in remove_items:
if not item in exclude_items:
rf.append(item)
return rf
def moveallfiles(root_src_dir, root_dst_dir):
change_permissions = True
if sys.platform == "win32" or sys.platform == "darwin":
change_permissions=False
else:
app.logger.debug('Update on OS-System : '+sys.platform )
#print('OS-System: '+sys.platform )
new_permissions=os.stat(root_dst_dir)
#print new_permissions
for src_dir, dirs, files in os.walk(root_src_dir):
dst_dir = src_dir.replace(root_src_dir, root_dst_dir, 1)
if not os.path.exists(dst_dir):
os.makedirs(dst_dir)
#print('Create-Dir: '+dst_dir)
if change_permissions:
#print('Permissions: User '+str(new_permissions.st_uid)+' Group '+str(new_permissions.st_uid))
os.chown(dst_dir,new_permissions.st_uid,new_permissions.st_gid)
for file_ in files:
src_file = os.path.join(src_dir, file_)
dst_file = os.path.join(dst_dir, file_)
if os.path.exists(dst_file):
if change_permissions:
permission=os.stat(dst_file)
#print('Remove file before copy: '+dst_file)
os.remove(dst_file)
else:
if change_permissions:
permission=new_permissions
shutil.move(src_file, dst_dir)
#print('Move File '+src_file+' to '+dst_dir)
if change_permissions:
try:
os.chown(dst_file, permission.st_uid, permission.st_uid)
#print('Permissions: User '+str(new_permissions.st_uid)+' Group '+str(new_permissions.st_uid))
except:
e = sys.exc_info()
#print('Fail '+str(dst_file)+' error: '+str(e))
return
def update_source(source,destination):
# destination files
old_list=list()
exclude = (['vendor' + os.sep + 'kindlegen.exe','vendor' + os.sep + 'kindlegen','/app.db','vendor','/update.py'])
for root, dirs, files in os.walk(destination, topdown=True):
for name in files:
old_list.append(os.path.join(root, name).replace(destination, ''))
for name in dirs:
old_list.append(os.path.join(root, name).replace(destination, ''))
# source files
new_list = list()
for root, dirs, files in os.walk(source, topdown=True):
for name in files:
new_list.append(os.path.join(root, name).replace(source, ''))
for name in dirs:
new_list.append(os.path.join(root, name).replace(source, ''))
delete_files = one_minus_two(old_list, new_list)
#print('raw delete list', delete_files)
rf= reduce_files(delete_files, exclude)
#print('reduced delete list', rf)
remove_items = reduce_dirs(rf, new_list)
#print('delete files', remove_items)
moveallfiles(source, destination)
for item in remove_items:
item_path = os.path.join(destination, item[1:])
if os.path.isdir(item_path):
print("Delete dir "+ item_path)
shutil.rmtree(item_path)
else:
try:
print("Delete file "+ item_path)
os.remove(item_path)
except:
print("Could not remove:"+item_path)
shutil.rmtree(source, ignore_errors=True)

File diff suppressed because one or more lines are too long

View File

@ -15,7 +15,7 @@
<th>{{_('Passwd')}}</th>
</tr>
{% for user in content %}
{% if not user.role_anonymous() or config.ANON_BROWSE %}
{% if not user.role_anonymous() or config.config_anonbrowse %}
<tr>
<td><a href="{{url_for('edit_user', user_id=user.id)}}">{{user.nickname}}</a></td>
<td>{{user.email}}</td>
@ -56,7 +56,7 @@
<h2>{{_('Configuration')}}</h2>
<table class="table table-striped">
<tr>
<th>{{_('Log File')}}</th>
<th>{{_('Calibre DB dir')}}</th>
<th>{{_('Log Level')}}</th>
<th>{{_('Port')}}</th>
<th>{{_('Books per page')}}</th>
@ -65,17 +65,90 @@
<th>{{_('Anonymous browsing')}}</th>
</tr>
<tr>
<td>{{config.LOG_DIR}}</td>
<td>{{config.LOG_DIR}}</td>
<td>{{config.PORT}}</td>
<td>{{config.NEWEST_BOOKS}}</td>
<td>{% if config.UPLOADING %}<span class="glyphicon glyphicon-ok"></span>{% else %}<span class="glyphicon glyphicon-remove"></span>{% endif %}</td>
<td>{% if config.PUBLIC_REG %}<span class="glyphicon glyphicon-ok"></span>{% else %}<span class="glyphicon glyphicon-remove"></span>{% endif %}</td>
<td>{% if config.ANON_BROWSE %}<span class="glyphicon glyphicon-ok"></span>{% else %}<span class="glyphicon glyphicon-remove"></span>{% endif %}</td>
<td>{{config.config_calibre_dir}}</td>
<td>{{config.get_Log_Level()}}</td>
<td>{{config.config_port}}</td>
<td>{{config.config_books_per_page}}</td>
<td>{% if config.config_uploading %}<span class="glyphicon glyphicon-ok"></span>{% else %}<span class="glyphicon glyphicon-remove"></span>{% endif %}</td>
<td>{% if config.config_public_reg %}<span class="glyphicon glyphicon-ok"></span>{% else %}<span class="glyphicon glyphicon-remove"></span>{% endif %}</td>
<td>{% if config.config_anonbrowse %}<span class="glyphicon glyphicon-ok"></span>{% else %}<span class="glyphicon glyphicon-remove"></span>{% endif %}</td>
</table>
<div class="btn btn-default"><a href="{{url_for('configuration')}}">{{_('Configuration')}}</a></div>
<h2>{{_('Administration')}}</h2>
{% if not config.DEVELOPMENT %}
<div class="btn btn-default"><a href="{{url_for('shutdown')}}">{{_('Restart Calibre-web')}}</a></div>
{% if not development %}
<div class="btn btn-default" data-toggle="modal" data-target="#RestartDialog">{{_('Restart Calibre-web')}}</a></div>
<div class="btn btn-default" data-toggle="modal" data-target="#ShutdownDialog">{{_('Stop Calibre-web')}}</a></div>
<div class="btn btn-default" id="check_for_update">{{_('Check for update')}}</a></div>
<a href="{{url_for('update')}}" class="btn btn-default hidden" id="perform_update">{{_('Perform Update')}}</a>
{% endif %}
</div>
<!-- Modal -->
<div id="RestartDialog" class="modal fade" role="dialog">
<div class="modal-dialog modal-sm">
<!-- Modal content-->
<div class="modal-content">
<div class="modal-header bg-info">
</div>
<div class="modal-body text-center">
<p>{{_('Do you really want to restart Calibre-web?')}}</p>
<button type="button" class="btn btn-default" id="restart" data-dismiss="modal">{{_('Ok')}}</button>
<button type="button" class="btn btn-default" data-dismiss="modal">{{_('Back')}}</button>
</div>
</div>
</div>
</div>
<div id="ShutdownDialog" class="modal fade" role="dialog">
<div class="modal-dialog modal-sm">
<!-- Modal content-->
<div class="modal-content">
<div class="modal-header bg-info">
</div>
<div class="modal-body text-center">
<p>{{_('Do you really want to stop Calibre-web?')}}</p>
<button type="button" class="btn btn-default" id="shutdown" data-dismiss="modal">{{_('Ok')}}</button>
<button type="button" class="btn btn-default" data-dismiss="modal">{{_('Back')}}</button>
</div>
</div>
</div>
</div>
{% endblock %}
{% block js %}
<script type="text/javascript">
$("#restart").click(function() {
$.ajax({
dataType: 'json',
url: "{{url_for('shutdown')}}",
data: {"parameter":0},
//data: data,
success: function(data) {
return alert(data.text);}
});
});
$("#shutdown").click(function() {
$.ajax({
dataType: 'json',
url: "{{url_for('shutdown')}}",
data: {"parameter":1},
success: function(data) {
return alert(data.text);}
});
});
$("#check_for_update").click(function() {
$("#check_for_update").html('Checking...');
$.ajax({
dataType: 'json',
url: "{{url_for('get_update_status')}}",
success: function(data) {
if (data.status == true) {
$("#check_for_update").addClass('hidden');
$("#perform_update").removeClass('hidden');
}else{
$("#check_for_update").html('{{_('Check for update')}}');
};}
});
});
</script>
{% endblock %}

View File

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

View File

@ -15,7 +15,7 @@
<h2>{{entry.title}}</h2>
<p class="author">
{% for author in entry.authors %}
<a href="{{url_for('author', name=author.name) }}">{{author.name}}</a>
<a href="{{url_for('author', id=author.id ) }}">{{author.name}}</a>
{% if not loop.last %}
&amp;
{% endif %}
@ -37,7 +37,7 @@
{% endif %}
{% if entry.series|length > 0 %}
<p>{{_('Book')}} {{entry.series_index}} {{_('of')}} <a href="{{url_for('series', name=entry.series[0].name)}}">{{entry.series[0].name}}</a></p>
<p>{{_('Book')}} {{entry.series_index}} {{_('of')}} <a href="{{url_for('series', id=entry.series[0].id)}}">{{entry.series[0].name}}</a></p>
{% endif %}
{% if entry.languages.__len__() > 0 %}
@ -58,20 +58,21 @@
</p>
{% endif %}
{% if entry.tags|length > 0 %}
<p>
<div class="tags">
<span class="glyphicon glyphicon-tags"></span>
{% for tag in entry.tags %}
<a href="{{ url_for('category', name=tag.name) }}" class="btn btn-xs btn-info" role="button">{{tag.name}}</a>
<a href="{{ url_for('category', id=tag.id) }}" class="btn btn-xs btn-info" role="button">{{tag.name}}</a>
{%endfor%}
</div>
</p>
{% endif %}
{% if entry.pubdate != '0101-01-01 00:00:00' %}
<p>{{_('Publishing date')}}: {{entry.pubdate[:10]}} </p>
{% endif %}
{% if cc|length > 0 %}
<p>
<div class="custom_columns">
@ -101,7 +102,7 @@
{% endif %}
{% if entry.comments|length > 0 %}
{% if entry.comments|length > 0 and entry.comments[0].text|length > 0%}
<h3>{{_('Description:')}}</h3>
{{entry.comments[0].text|safe}}
{% endif %}
@ -191,7 +192,6 @@
<div class="btn-toolbar" role="toolbar">
<div class="btn-group" role="group" aria-label="Edit/Delete book">
<a href="{{ url_for('edit_book', book_id=entry.id) }}" class="btn btn-sm btn-warning" role="button"><span class="glyphicon glyphicon-edit"></span> {{_('Edit metadata')}}</a>
<!-- <a href="{{ url_for('edit_book', book_id=entry.id) }}" class="btn btn-sm btn-danger" role="button"><span class="glyphicon glyphicon-trash"></span> Delete</a> -->
</div>
{% endif %}

View File

@ -9,13 +9,13 @@
<div class="cover">
{% if entry.has_cover is defined %}
<a href="{{ url_for('show_book', id=entry.id) }}">
<img src="{{ url_for('get_cover', cover_path=entry.path) }}" />
<img src="{{ url_for('get_cover', cover_path=entry.path.replace('\\','/')) }}" />
</a>
{% endif %}
</div>
<div class="meta">
<p class="title">{{entry.title|shortentitle}}</p>
<p class="author"><a href="{{url_for('author', name=entry.authors[0].name) }}">{{entry.authors[0].name}}</a></p>
<p class="author"><a href="{{url_for('author', name=entry.authors[0].name | urlencode) }}">{{entry.authors[0].name}}</a></p>
{% if entry.ratings.__len__() > 0 %}
<div class="rating">
{% for number in range((entry.ratings[0].rating/2)|int(2)) %}

View File

@ -29,9 +29,9 @@
<link rel="search"
href="{{url_for('feed_osd')}}"
type="application/opensearchdescription+xml"/>
<title>Calibre Web</title>
<title>{{instance}}</title>
<author>
<name>Calibre Web</name>
<name>{{instance}}</name>
<uri>https://github.com/janeczku/calibre-web</uri>
</author>
@ -60,28 +60,12 @@
{% endfor %}
</entry>
{% endfor %}
{% for author in authors %}
<entry>
<title>{{author.name}}</title>
<id>{{ url_for('feed_author', id=author.id) }}</id>
<link type="application/atom+xml;profile=opds-catalog;type=feed;kind=acquisition" href="{{url_for('feed_author', id=author.id)}}"/>
<link type="application/atom+xml" href="{{url_for('feed_author', id=author.id)}}" rel="subsection"/>
</entry>
{% endfor %}
{% for entry in categorys %}
{% for entry in listelements %}
<entry>
<title>{{entry.name}}</title>
<id>{{ url_for('feed_category', id=entry.id) }}</id>
<link type="application/atom+xml;profile=opds-catalog;type=feed;kind=acquisition" href="{{url_for('feed_category', id=entry.id)}}"/>
<link type="application/atom+xml" href="{{url_for('feed_category', id=entry.id)}}" rel="subsection"/>
</entry>
{% endfor %}
{% for entry in series %}
<entry>
<title>{{entry.name}}</title>
<id>{{ url_for('feed_series', id=entry.id) }}</id>
<link type="application/atom+xml;profile=opds-catalog;type=feed;kind=acquisition" href="{{url_for('feed_series', id=entry.id)}}" />
<link type="application/atom+xml" href="{{url_for('feed_series', id=entry.id)}}" rel="subsection"/>
<id>{{ url_for(folder, id=entry.id) }}</id>
<link type="application/atom+xml;profile=opds-catalog;type=feed;kind=acquisition" href="{{url_for(folder, id=entry.id)}}"/>
<link type="application/atom+xml" href="{{url_for(folder, id=entry.id)}}" rel="subsection"/>
</entry>
{% endfor %}
</feed>

View File

@ -1,6 +1,6 @@
{% extends "layout.html" %}
{% block body %}
{% if g.user.show_random_books() %}
{% if g.user.show_detail_random() %}
<div class="discover">
<h2>{{_('Discover (Random Books)')}}</h2>
<div class="row">
@ -18,7 +18,7 @@
</div>
<div class="meta">
<p class="title">{{entry.title|shortentitle}}</p>
<p class="author"><a href="{{url_for('author', name=entry.authors[0].name) }}">{{entry.authors[0].name}}</a></p>
<p class="author"><a href="{{url_for('author', id=entry.authors[0].id) }}">{{entry.authors[0].name}}</a></p>
{% if entry.ratings.__len__() > 0 %}
<div class="rating">
{% for number in range((entry.ratings[0].rating/2)|int(2)) %}
@ -55,7 +55,7 @@
<p class="title">{{entry.title|shortentitle}}</p>
<p class="author">
{% for author in entry.authors %}
<a href="{{url_for('author', name=author.name) }}">{{author.name}}</a>
<a href="{{url_for('author', id=author.id) }}">{{author.name}}</a>
{% if not loop.last %}
&amp;
{% endif %}

View File

@ -6,9 +6,9 @@
type="application/atom+xml;profile=opds-catalog;kind=navigation"/>
<link rel="search" title="{{_('Search')}}" href="{{url_for('feed_osd')}}"
type="application/opensearchdescription+xml"/>
<title>Calibre Web</title>
<title>{{instance}}</title>
<author>
<name>Calibre Web</name>
<name>{{instance}}</name>
<uri>https://github.com/janeczku/calibre-web</uri>
</author>
<entry>

View File

@ -1,7 +1,7 @@
<!DOCTYPE html>
<html>
<head>
<title>calibre web | {{title}}</title>
<title>{{instance}} | {{title}}</title>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1">

View File

@ -5,7 +5,7 @@
{% for entry in entries %}
<div class="row">
<div class="col-xs-1" align="left"><span class="badge">{{entry.count}}</span></div>
<div class="col-xs-6"><a href="{{url_for(folder, name=entry[0].name)}}">{{entry[0].name}}</a></div>
<div class="col-xs-6"><a href="{{url_for(folder, id=entry[0].id )}}">{{entry[0].name}}</a></div>
</div>
{% endfor %}
</div>

View File

@ -1,16 +1,16 @@
<?xml version="1.0" encoding="UTF-8"?>
<OpenSearchDescription xmlns="http://a9.com/-/spec/opensearch/1.1/">
<LongName>Calibre Web</LongName>
<ShortName>Calibre Web</ShortName>
<Description>Calibre Web ebook catalog</Description>
<Developer>janeczku</Developer>
<LongName>{{instance}}</LongName>
<ShortName>{{instance}}</ShortName>
<Description>{{_('instanceCalibre Web ebook catalog')}}</Description>
<Developer>Janeczku</Developer>
<Contact>https://github.com/janeczku/calibre-web</Contact>
<Url type="text/html"
template="{{url_for('search')}}?query={searchTerms}"/>
<Url type="application/atom+xml"
template="{{url_for('feed_normal_search')}}?query={searchTerms}"/>
<SyndicationRight>open</SyndicationRight>
<Language>de-DE</Language>
<Language>{{lang}}</Language>
<OutputEncoding>UTF-8</OutputEncoding>
<InputEncoding>UTF-8</InputEncoding>
</OpenSearchDescription>

View File

@ -57,7 +57,7 @@
<!-- Full Screen -->
<!--<script src="js/libs/screenfull.min.js"></script>-->
<script src="{{ url_for('static', filename='js/libs/screenfull.min.js') }}"></script>
<script src="{{ url_for('static', filename='js/screenfull.min.js') }}"></script>
<!-- Render -->
<!--<script src="js/epub.min.js"></script>-->

View File

@ -16,7 +16,7 @@
<div class="cover">
{% if entry.has_cover is defined %}
<a href="{{ url_for('show_book', id=entry.id) }}">
<img src="{{ url_for('get_cover', cover_path=entry.path) }}" />
<img src="{{ url_for('get_cover', cover_path=entry.path.replace('\\','/')) }}" />
</a>
{% endif %}
</div>
@ -24,7 +24,7 @@
<p class="title">{{entry.title|shortentitle}}</p>
<p class="author">
{% for author in entry.authors %}
<a href="{{url_for('author', name=author.name) }}">{{author.name}}</a>
<a href="{{url_for('author', name=author.name | urlencode) }}">{{author.name}}</a>
{% if not loop.last %}
&amp;
{% endif %}
@ -44,7 +44,6 @@
{% endif %}
</div>
</div>
<!-- <p><a href="{{ url_for('edit_book', book_id=entry.id) }}">{{entry.authors[0].name}}: {{entry.title}}</a></p> -->
{% endfor %}
</div>
</div>

View File

@ -1,20 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<OpenSearchDescription xmlns="http://a9.com/-/spec/opensearch/1.1/">
<LongName>Calibre Web</LongName>
<ShortName>Calibre Web</ShortName>
<Description>Calibre Web ebook catalog</Description>
<Developer>janeczku</Developer>
<Contact>https://github.com/janeczku/calibre-web</Contact>
<Url type="text/html"
template="{{url_for('search')}}?query={searchTerms}"/>
<Url type="application/atom+xml"
template="{{url_for('feed_search')}}?query={searchTerms}"/>
<SyndicationRight>open</SyndicationRight>
<Language>de-DE</Language>
<OutputEncoding>UTF-8</OutputEncoding>
<InputEncoding>UTF-8</InputEncoding>
</OpenSearchDescription>

View File

@ -15,13 +15,13 @@
<div class="cover">
{% if entry.has_cover is defined %}
<a href="{{ url_for('show_book', id=entry.id) }}">
<img src="{{ url_for('get_cover', cover_path=entry.path) }}" />
<img src="{{ url_for('get_cover', cover_path=entry.path.replace('\\','/')) }}" />
</a>
{% endif %}
</div>
<div class="meta">
<p class="title">{{entry.title|shortentitle}}</p>
<p class="author"><a href="{{url_for('author', name=entry.authors[0].name) }}">{{entry.authors[0].name}}</a></p>
<p class="author"><a href="{{url_for('author', name=entry.authors[0].name | urlencode) }}">{{entry.authors[0].name}}</a></p>
{% if entry.ratings.__len__() > 0 %}
<div class="rating">
{% for number in range((entry.ratings[0].rating/2)|int(2)) %}
@ -36,7 +36,6 @@
{% endif %}
</div>
</div>
<!-- <p><a href="{{ url_for('edit_book', book_id=entry.id) }}">{{entry.authors[0].name}}: {{entry.title}}</a></p> -->
{% endfor %}
</div>
</div>

View File

@ -12,19 +12,19 @@
<tbody>
<tr>
<th>Python</th>
<td>{{Versions['PythonVersion']}}</td>
<td>{{versions['PythonVersion']}}</td>
</tr>
<tr>
<th>Kindlegen</th>
<td>{{Versions['KindlegenVersion']}}</td>
<td>{{versions['KindlegenVersion']}}</td>
</tr>
<tr>
<th>ImageMagick</th>
<td>{{Versions['ImageVersion']}}</td>
<td>{{versions['ImageVersion']}}</td>
</tr>
<tr>
<th>PyPDF2</th>
<td>{{Versions['PyPdfVersion']}}</td>
<td>{{versions['PyPdfVersion']}}</td>
</tr>
</tbody>
</table>
@ -40,6 +40,14 @@
<th>{{authorcounter}}</th>
<td>{{_('Authors in this Library')}}</td>
</tr>
<tr>
<th>{{categorycounter}}</th>
<td>{{_('Categories in this Library')}}</td>
</tr>
<tr>
<th>{{seriecounter}}</th>
<td>{{_('Series in this Library')}}</td>
</tr>
</tbody>
</table>
{% endblock %}

View File

@ -27,39 +27,47 @@
<label for="locale">{{_('Language')}}</label>
<select name="locale" id="locale" class="form-control">
{% for translation in translations %}
<option value="{{translation}}" {% if translation|string == content.locale %}selected{% endif %}>{{ translation.display_name }}</option>
<option value="{{translation.language}}" {% if translation.language == content.locale %}selected{% endif %} {% if new_user == 1 and loop.first %}selected{% endif %}>{{ translation.display_name }}</option>
{% endfor %}
</select>
</div>
<div class="form-group">
<label for="default_language">{{_('Show books with language')}}</label>
<select name="default_language" id="default_language" class="form-control">
<option value="all" >{{ _('Show all') }}</option>
<option value="all" {% if new_user == 1 %}selected{% endif %}>{{ _('Show all') }}</option>
{% for language in languages %}
<option value="{{ language.lang_code }}" {% if content.default_language == language.lang_code %}selected{% endif %}>{{ language.name }}</option>
{% endfor %}
</select>
</div>
<div class="form-group">
<input type="checkbox" name="show_random" {% if content.random_books %}checked{% endif %}>
<input type="checkbox" name="show_random" id="show_random" {% if content.show_random_books() %}checked{% endif %}>
<label for="show_random">{{_('Show random books')}}</label>
</div>
<div class="form-group">
<input type="checkbox" name="show_hot" {% if content.hot_books %}checked{% endif %}>
<input type="checkbox" name="show_hot" id="show_hot" {% if content.show_hot_books() %}checked{% endif %}>
<label for="show_hot">{{_('Show hot books')}}</label>
</div>
<div class="form-group">
<input type="checkbox" name="show_language" {% if content.language_books %}checked{% endif %}>
<input type="checkbox" name="show_language" id="show_language" {% if content.show_language() %}checked{% endif %}>
<label for="show_language">{{_('Show language selection')}}</label>
</div>
<div class="form-group">
<input type="checkbox" name="show_series" {% if content.series_books %}checked{% endif %}>
<input type="checkbox" name="show_series" id="show_series" {% if content.show_series() %}checked{% endif %}>
<label for="show_series">{{_('Show series selection')}}</label>
</div>
<div class="form-group">
<input type="checkbox" name="show_category" {% if content.category_books %}checked{% endif %}>
<input type="checkbox" name="show_category" id="show_category" {% if content.show_category() %}checked{% endif %}>
<label for="show_category">{{_('Show category selection')}}</label>
</div>
<div class="form-group">
<input type="checkbox" name="show_author" id="show_author" {% if content.show_author() %}checked{% endif %}>
<label for="show_author">{{_('Show author selection')}}</label>
</div>
<div class="form-group">
<input type="checkbox" name="show_detail_random" id="show_detail_random" {% if content.show_detail_random() %}checked{% endif %}>
<label for="show_detail_random">{{_('Show random books in detail view')}}</label>
</div>
{% if g.user and g.user.role_admin() and not profile %}
{% if not content.role_anonymous() %}
@ -105,7 +113,7 @@
{% for entry in downloads %}
<div class="col-sm-2">
<a class="pull-left" href="{{ url_for('show_book', id=entry.id) }}">
<img class="media-object" width="100" src="{{ url_for('get_cover', cover_path=entry.path) }}" alt="...">
<img class="media-object" width="100" src="{{ url_for('get_cover', cover_path=entry.path.replace('\\','/')) }}" alt="...">
</a>
</div>
{% endfor %}

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@ -1,842 +0,0 @@
# Chinese (Simplified, China) translations for PROJECT.
# Copyright (C) 2017 ORGANIZATION
# This file is distributed under the same license as the PROJECT project.
# FIRST AUTHOR <EMAIL@ADDRESS>, 2017.
#
msgid ""
msgstr ""
"Project-Id-Version: PROJECT VERSION\n"
"Report-Msgid-Bugs-To: EMAIL@ADDRESS\n"
"POT-Creation-Date: 2017-01-18 19:12+0100\n"
"PO-Revision-Date: 2017-01-06 17:00+0800\n"
"Last-Translator: dalin <dalin.lin@gmail.com>\n"
"Language: zh_Hans_CN\n"
"Language-Team: zh_Hans_CN <LL@li.org>\n"
"Plural-Forms: nplurals=1; plural=0\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=utf-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Generated-By: Babel 2.3.4\n"
#: cps/book_formats.py:109 cps/book_formats.py:113 cps/web.py:948
msgid "not installed"
msgstr "未安装"
#: cps/helper.py:98
msgid "Calibre-web test email"
msgstr "Calibre-web 测试邮件"
#: cps/helper.py:99 cps/helper.py:155
msgid "This email has been sent via calibre web."
msgstr "此邮件由calibre web发送"
#: cps/helper.py:136 cps/helper.py:225
#, python-format
msgid "Failed to send mail: %s"
msgstr "发送邮件失败: %s"
#: cps/helper.py:154 cps/templates/detail.html:127
msgid "Send to Kindle"
msgstr "发送到Kindle"
#: cps/helper.py:177 cps/helper.py:192
msgid "Could not find any formats suitable for sending by email"
msgstr "无法找到适合邮件发送的格式"
#: cps/helper.py:186
msgid "Could not convert epub to mobi"
msgstr "无法转换epub到mobi"
#: cps/helper.py:245
msgid "The requested file could not be read. Maybe wrong permissions?"
msgstr "无法读取所请求的文件。可能是权限不对?"
#: cps/ub.py:259
msgid "Guest"
msgstr "游客"
#: cps/web.py:742
msgid "Latest Books"
msgstr "最新书籍"
#: cps/web.py:767
msgid "Hot Books (most downloaded)"
msgstr "热门书籍(最多下载)"
#: cps/templates/index.xml:29 cps/web.py:775
msgid "Random Books"
msgstr "随机书籍"
#: cps/web.py:788
msgid "Author list"
msgstr "作者列表"
#: cps/web.py:805
#, python-format
msgid "Author: %(nam)s"
msgstr "作者: %(nam)s"
#: cps/templates/index.xml:50 cps/web.py:818
msgid "Series list"
msgstr "丛书列表"
#: cps/web.py:829
#, python-format
msgid "Series: %(serie)s"
msgstr "丛书: %(serie)s"
#: cps/web.py:831 cps/web.py:927 cps/web.py:1126 cps/web.py:1874
msgid "Error opening eBook. File does not exist or file is not accessible:"
msgstr "无法打开电子书。 文件不存在或者文件不可访问:"
#: cps/web.py:862
msgid "Available languages"
msgstr "可用语言"
#: cps/web.py:877
#, python-format
msgid "Language: %(name)s"
msgstr "语言: %(name)s"
#: cps/templates/index.xml:43 cps/web.py:890
msgid "Category list"
msgstr "分类列表"
#: cps/web.py:900
#, python-format
msgid "Category: %(name)s"
msgstr "分类: %(name)s"
#: cps/web.py:956
msgid "Statistics"
msgstr "统计"
#: cps/web.py:965
msgid "Server restarts"
msgstr "重启服务器"
#: cps/web.py:1102 cps/web.py:1109 cps/web.py:1116 cps/web.py:1123
msgid "Read a Book"
msgstr "阅读一本书"
#: cps/web.py:1172 cps/web.py:1510
msgid "Please fill out all fields!"
msgstr "请填写所有字段"
#: cps/web.py:1188
msgid "An unknown error occured. Please try again later."
msgstr "发生一个未知错误。请稍后再试。"
#: cps/web.py:1193
msgid "This username or email address is already in use."
msgstr "此用户名或邮箱已被使用。"
#: cps/web.py:1196
msgid "register"
msgstr "注册"
#: cps/web.py:1212
#, python-format
msgid "you are now logged in as: '%(nickname)s'"
msgstr "您现在已以'%(nickname)s'身份登录"
#: cps/web.py:1216
msgid "Wrong Username or Password"
msgstr "用户名或密码错误"
#: cps/web.py:1218
msgid "login"
msgstr "登录"
#: cps/web.py:1235
msgid "Please configure the SMTP mail settings first..."
msgstr "请先配置SMTP邮箱..."
#: cps/web.py:1239
#, python-format
msgid "Book successfully send to %(kindlemail)s"
msgstr "此书已被成功发给 %(kindlemail)s"
#: cps/web.py:1243
#, python-format
msgid "There was an error sending this book: %(res)s"
msgstr "发送这本书的时候出现错误: %(res)s"
#: cps/web.py:1245
msgid "Please configure your kindle email address first..."
msgstr "请先配置您的kindle电子邮箱地址..."
#: cps/web.py:1265
#, python-format
msgid "Book has been added to shelf: %(sname)s"
msgstr "此书已被添加到书架: %(sname)s"
#: cps/web.py:1286
#, python-format
msgid "Book has been removed from shelf: %(sname)s"
msgstr "此书已从书架 %(sname)s 中删除"
#: cps/web.py:1304 cps/web.py:1325
#, python-format
msgid "A shelf with the name '%(title)s' already exists."
msgstr "已存在书架 '%(title)s'。"
#: cps/web.py:1309
#, python-format
msgid "Shelf %(title)s created"
msgstr "书架 %(title)s 已被创建"
#: cps/web.py:1311 cps/web.py:1336
msgid "There was an error"
msgstr "发生错误"
#: cps/web.py:1312 cps/web.py:1314
msgid "create a shelf"
msgstr "创建书架"
#: cps/web.py:1334
#, python-format
msgid "Shelf %(title)s changed"
msgstr "书架 %(title)s 已被修改"
#: cps/web.py:1337 cps/web.py:1339
msgid "Edit a shelf"
msgstr "编辑书架"
#: cps/web.py:1360
#, python-format
msgid "successfully deleted shelf %(name)s"
msgstr "成功删除书架 %(name)s"
#: cps/web.py:1381
#, python-format
msgid "Shelf: '%(name)s'"
msgstr "书架: '%(name)s'"
#: cps/web.py:1409
#, python-format
msgid "Change order of Shelf: '%(name)s'"
msgstr "修改书架 '%(name)s' 顺序"
#: cps/web.py:1469
msgid "Found an existing account for this email address."
msgstr "找到已使用此邮箱的账号。"
#: cps/web.py:1471 cps/web.py:1474
#, python-format
msgid "%(name)s's profile"
msgstr "%(name)s 的资料"
#: cps/web.py:1472
msgid "Profile updated"
msgstr "资料已更新"
#: cps/web.py:1483 cps/web.py:1491
msgid "Admin page"
msgstr "管理页"
#: cps/templates/admin.html:33 cps/web.py:1511
msgid "Add new user"
msgstr "添加新用户"
#: cps/web.py:1544
#, python-format
msgid "User '%(user)s' created"
msgstr "用户 '%(user)s' 已被创建"
#: cps/web.py:1548
msgid "Found an existing account for this email address or nickname."
msgstr "已找到使用此邮箱或昵称的账号。"
#: cps/web.py:1568
msgid "Mail settings updated"
msgstr "邮箱设置已更新"
#: cps/web.py:1574
#, python-format
msgid "Test E-Mail successfully send to %(kindlemail)s"
msgstr "测试邮件已成功发送到 %(kindlemail)s"
#: cps/web.py:1577
#, python-format
msgid "There was an error sending the Test E-Mail: %(res)s"
msgstr "发送测试邮件时发生错误: %(res)s"
#: cps/web.py:1578
msgid "Edit mail settings"
msgstr "编辑邮箱设置"
#: cps/web.py:1606
#, python-format
msgid "User '%(nick)s' deleted"
msgstr "用户 '%(nick)s' 已被删除"
#: cps/web.py:1661
#, python-format
msgid "User '%(nick)s' updated"
msgstr "用户 '%(nick)s' 已被更新"
#: cps/web.py:1664
msgid "An unknown error occured."
msgstr "发生未知错误。"
#: cps/web.py:1666
#, python-format
msgid "Edit User %(nick)s"
msgstr "编辑用户 %(nick)s"
#: cps/web.py:1904
#, python-format
msgid "Failed to create path %s (Permission denied)."
msgstr "创建路径 %s 失败(权限拒绝)。"
#: cps/web.py:1909
#, python-format
msgid "Failed to store file %s (Permission denied)."
msgstr "存储文件 %s 失败(权限拒绝)。"
#: cps/web.py:1914
#, python-format
msgid "Failed to delete file %s (Permission denied)."
msgstr "删除文件 %s 失败(权限拒绝)。"
#: cps/templates/admin.html:4
msgid "User list"
msgstr "用户列表"
#: cps/templates/admin.html:7
msgid "Nickname"
msgstr "昵称"
#: cps/templates/admin.html:8
msgid "Email"
msgstr ""
#: cps/templates/admin.html:9
msgid "Kindle"
msgstr ""
#: cps/templates/admin.html:10
msgid "DLS"
msgstr ""
#: cps/templates/admin.html:11 cps/templates/layout.html:83
msgid "Admin"
msgstr "管理"
#: cps/templates/admin.html:12 cps/templates/detail.html:114
msgid "Download"
msgstr "下载"
#: cps/templates/admin.html:13 cps/templates/layout.html:76
msgid "Upload"
msgstr "上传"
#: cps/templates/admin.html:14
msgid "Edit"
msgstr "编辑"
#: cps/templates/admin.html:15
msgid "Passwd"
msgstr "修改密码"
#: cps/templates/admin.html:34
msgid "SMTP mail settings"
msgstr "SMTP设置"
#: cps/templates/admin.html:37 cps/templates/email_edit.html:7
msgid "SMTP hostname"
msgstr "SMTP地址"
#: cps/templates/admin.html:38
msgid "SMTP port"
msgstr "SMTP端口"
#: cps/templates/admin.html:39
msgid "SSL"
msgstr ""
#: cps/templates/admin.html:40 cps/templates/email_edit.html:23
msgid "SMTP login"
msgstr "SMTP用户名"
#: cps/templates/admin.html:41 cps/templates/email_edit.html:27
msgid "SMTP password"
msgstr "SMTP密码"
#: cps/templates/admin.html:42
msgid "From mail"
msgstr "来自邮箱"
#: cps/templates/admin.html:54
msgid "Change SMTP settings"
msgstr "修改SMTP设置"
#: cps/templates/admin.html:56
msgid "Configuration"
msgstr "配置"
#: cps/templates/admin.html:59
msgid "Log File"
msgstr "日志文件"
#: cps/templates/admin.html:60
msgid "Log Level"
msgstr "日志级别"
#: cps/templates/admin.html:61
msgid "Port"
msgstr "端口"
#: cps/templates/admin.html:62
msgid "Books per page"
msgstr "每页书籍数"
#: cps/templates/admin.html:63
msgid "Uploading"
msgstr "上传"
#: cps/templates/admin.html:64
msgid "Public registration"
msgstr "开放注册"
#: cps/templates/admin.html:65
msgid "Anonymous browsing"
msgstr "匿名浏览"
#: cps/templates/admin.html:76
msgid "Administration"
msgstr "管理"
#: cps/templates/admin.html:78
msgid "Restart Calibre-web"
msgstr "重启 Calibre-web"
#: cps/templates/detail.html:38
msgid "Book"
msgstr ""
#: cps/templates/detail.html:38
msgid "of"
msgstr ""
#: cps/templates/detail.html:44
msgid "language"
msgstr "语言"
#: cps/templates/detail.html:103
msgid "Description:"
msgstr "简介:"
#: cps/templates/detail.html:131
msgid "Read in browser"
msgstr "在浏览器中阅读"
#: cps/templates/detail.html:151
msgid "Add to shelf"
msgstr "添加到书架"
#: cps/templates/detail.html:191
msgid "Edit metadata"
msgstr "编辑元数据"
#: cps/templates/edit_book.html:14 cps/templates/search_form.html:6
msgid "Book Title"
msgstr "书名"
#: cps/templates/edit_book.html:18 cps/templates/search_form.html:10
msgid "Author"
msgstr "作者"
#: cps/templates/edit_book.html:22
msgid "Description"
msgstr "简介"
#: cps/templates/edit_book.html:26 cps/templates/search_form.html:13
msgid "Tags"
msgstr "标签"
#: cps/templates/edit_book.html:31 cps/templates/layout.html:133
#: cps/templates/search_form.html:33
msgid "Series"
msgstr "丛书"
#: cps/templates/edit_book.html:35
msgid "Series id"
msgstr "丛书ID"
#: cps/templates/edit_book.html:39
msgid "Rating"
msgstr "评分"
#: cps/templates/edit_book.html:43
msgid "Cover URL (jpg)"
msgstr "封面URL (jpg)"
#: cps/templates/edit_book.html:48 cps/templates/user_edit.html:27
msgid "Language"
msgstr "语言"
#: cps/templates/edit_book.html:59
msgid "Yes"
msgstr "确认"
#: cps/templates/edit_book.html:60
msgid "No"
msgstr ""
#: cps/templates/edit_book.html:102
msgid "view book after edit"
msgstr "编辑后查看书籍"
#: cps/templates/edit_book.html:105 cps/templates/login.html:19
#: cps/templates/search_form.html:75 cps/templates/shelf_edit.html:15
#: cps/templates/user_edit.html:97
msgid "Submit"
msgstr "提交"
#: cps/templates/edit_book.html:106 cps/templates/email_edit.html:36
#: cps/templates/shelf_edit.html:17 cps/templates/shelf_order.html:12
#: cps/templates/user_edit.html:99
msgid "Back"
msgstr "后退"
#: cps/templates/email_edit.html:11
msgid "SMTP port (usually 25 for plain SMTP and 465 for SSL and 587 for STARTTLS)"
msgstr "SMTP端口(明文SMTP通常是25, SSL加密的是465, STARTTLS的是587)"
#: cps/templates/email_edit.html:15
msgid "Encryption"
msgstr "加密方式"
#: cps/templates/email_edit.html:17
msgid "None"
msgstr "无"
#: cps/templates/email_edit.html:18
msgid "STARTTLS"
msgstr ""
#: cps/templates/email_edit.html:19
msgid "SSL/TLS"
msgstr ""
#: cps/templates/email_edit.html:31
msgid "From e-mail"
msgstr "来自邮箱"
#: cps/templates/email_edit.html:34
msgid "Save settings"
msgstr "保存设置"
#: cps/templates/email_edit.html:35
msgid "Save settings and send Test E-Mail"
msgstr "保存设置并发送测试邮件"
#: cps/templates/feed.xml:20
msgid "Next"
msgstr "下一个"
#: cps/templates/index.html:5
msgid "Discover (Random Books)"
msgstr "发现(随机书籍)"
#: cps/templates/index.xml:5
msgid "Start"
msgstr "开始"
#: cps/templates/index.xml:7 cps/templates/layout.html:61
msgid "Search"
msgstr "搜索"
#: cps/templates/index.xml:15 cps/templates/layout.html:124
msgid "Hot Books"
msgstr "热门书籍"
#: cps/templates/index.xml:19
msgid "Popular publications from this catalog based on Rating."
msgstr "此目录中的书籍是基于评分的热门出版物。"
#: cps/templates/index.xml:22 cps/templates/layout.html:122
msgid "New Books"
msgstr "新书"
#: cps/templates/index.xml:26
msgid "The latest Books"
msgstr "最新书籍"
#: cps/templates/index.xml:33
msgid "Show Random Books"
msgstr "显示随机书籍"
#: cps/templates/index.xml:36 cps/templates/layout.html:135
msgid "Authors"
msgstr "作者"
#: cps/templates/index.xml:40
msgid "Books ordered by Author"
msgstr "书籍按作者组织"
#: cps/templates/index.xml:47
msgid "Books ordered by category"
msgstr "书籍按分类组织"
#: cps/templates/index.xml:54
msgid "Books ordered by series"
msgstr "书籍按丛书组织"
#: cps/templates/layout.html:48
msgid "Toggle navigation"
msgstr "切换导航"
#: cps/templates/layout.html:63
msgid "Go!"
msgstr "走起!"
#: cps/templates/layout.html:66
msgid "Advanced Search"
msgstr "高级搜索"
#: cps/templates/layout.html:87
msgid "Logout"
msgstr "注销"
#: cps/templates/layout.html:91 cps/templates/login.html:4
msgid "Login"
msgstr "登录"
#: cps/templates/layout.html:92 cps/templates/register.html:18
msgid "Register"
msgstr "注册"
#: cps/templates/layout.html:121
msgid "Browse"
msgstr "浏览"
#: cps/templates/layout.html:127
msgid "Discover"
msgstr "发现"
#: cps/templates/layout.html:130
msgid "Categories"
msgstr "分类"
#: cps/templates/layout.html:137 cps/templates/search_form.html:54
msgid "Languages"
msgstr "语言"
#: cps/templates/layout.html:140
msgid "Public Shelves"
msgstr "公开书架"
#: cps/templates/layout.html:144
msgid "Your Shelves"
msgstr "您的书架"
#: cps/templates/layout.html:149
msgid "Create a Shelf"
msgstr "创建书架"
#: cps/templates/layout.html:150
msgid "About"
msgstr "关于"
#: cps/templates/login.html:7 cps/templates/login.html:8
#: cps/templates/register.html:7 cps/templates/user_edit.html:8
msgid "Username"
msgstr "用户名"
#: cps/templates/login.html:11 cps/templates/login.html:12
#: cps/templates/register.html:11 cps/templates/user_edit.html:18
msgid "Password"
msgstr "密码"
#: cps/templates/login.html:16
msgid "Remember me"
msgstr "记住我"
#: cps/templates/read.html:136
msgid "Reflow text when sidebars are open."
msgstr ""
#: cps/templates/readpdf.html:29
msgid "PDF.js viewer"
msgstr "PDF.js查看器"
#: cps/templates/readtxt.html:6
msgid "Basic txt Reader"
msgstr "简单的txt阅读器"
#: cps/templates/register.html:4
msgid "Register a new account"
msgstr "注册新用户"
#: cps/templates/register.html:8
msgid "Choose a username"
msgstr "选择一个用户名"
#: cps/templates/register.html:12
msgid "Choose a password"
msgstr "选择一个密码"
#: cps/templates/register.html:15 cps/templates/user_edit.html:13
msgid "Email address"
msgstr "邮箱地址"
#: cps/templates/register.html:16
msgid "Your email address"
msgstr "您的邮箱地址"
#: cps/templates/search.html:6
msgid "No Results for:"
msgstr "找不到结果:"
#: cps/templates/search.html:7
msgid "Please try a diffrent Search"
msgstr "请尝试别的关键字"
#: cps/templates/search.html:9
msgid "Results for:"
msgstr "结果:"
#: cps/templates/search_form.html:23
msgid "Exclude Tags"
msgstr "排除标签"
#: cps/templates/search_form.html:43
msgid "Exclude Series"
msgstr "排除丛书"
#: cps/templates/search_form.html:64
msgid "Exclude Languages"
msgstr "排除语言"
#: cps/templates/shelf.html:6
msgid "Delete this Shelf"
msgstr "删除此书架"
#: cps/templates/shelf.html:7
msgid "Edit Shelf name"
msgstr "编辑书架名"
#: cps/templates/shelf.html:8 cps/templates/shelf_order.html:11
msgid "Change order"
msgstr "修改顺序"
#: cps/templates/shelf_edit.html:7
msgid "Title"
msgstr "标题"
#: cps/templates/shelf_edit.html:12
msgid "should the shelf be public?"
msgstr "要公开此书架吗?"
#: cps/templates/shelf_order.html:5
msgid "Drag 'n drop to rearrange order"
msgstr "通过拖拽进行重新排序"
#: 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 "Calibre书库统计"
#: cps/templates/stats.html:37
msgid "Books in this Library"
msgstr "本书在此书库"
#: cps/templates/stats.html:41
msgid "Authors in this Library"
msgstr "个作者在此书库"
#: cps/templates/user_edit.html:23
msgid "Kindle E-Mail"
msgstr ""
#: cps/templates/user_edit.html:35
msgid "Show books with language"
msgstr "按语言显示书籍"
#: cps/templates/user_edit.html:37
msgid "Show all"
msgstr "显示全部"
#: cps/templates/user_edit.html:45
msgid "Show random books"
msgstr "显示随机书籍"
#: cps/templates/user_edit.html:49
msgid "Show hot books"
msgstr "显示热门书籍"
#: cps/templates/user_edit.html:53
msgid "Show language selection"
msgstr "显示语言选择"
#: cps/templates/user_edit.html:57
msgid "Show series selection"
msgstr "显示丛书选择"
#: cps/templates/user_edit.html:61
msgid "Show category selection"
msgstr "显示分类选择"
#: cps/templates/user_edit.html:68
msgid "Admin user"
msgstr "管理用户"
#: cps/templates/user_edit.html:73
msgid "Allow Downloads"
msgstr "允许下载"
#: cps/templates/user_edit.html:77
msgid "Allow Uploads"
msgstr "允许上传"
#: cps/templates/user_edit.html:81
msgid "Allow Edit"
msgstr "允许编辑"
#: cps/templates/user_edit.html:86
msgid "Allow Changing Password"
msgstr "允许修改密码"
#: cps/templates/user_edit.html:93
msgid "Delete this user"
msgstr "删除此用户"
#: cps/templates/user_edit.html:104
msgid "Recent Downloads"
msgstr "最近下载"
#~ msgid "SMTP port (usually 25 for plain SMTP and 587 for SSL)"
#~ msgstr "SMTP端口(不加密的SMTP通常是25, SSL加密的是587)"
#~ msgid "Server uses SSL (StartTLS)"
#~ msgstr "服务器使用SSL (StartTLS)"
#~ msgid "change order"
#~ msgstr "修改顺序"
#~ msgid "Series in this Library"
#~ msgstr "个丛书在此书库"
#~ msgid "Tags in this Library"
#~ msgstr "个标签在此书库"
#~ msgid "Usercount for calibre web"
#~ msgstr ""
#~ msgid "Latin"
#~ msgstr ""

223
cps/ub.py
View File

@ -7,26 +7,42 @@ from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy.orm import *
from flask_login import AnonymousUserMixin
import os
import config
import traceback
import logging
from werkzeug.security import generate_password_hash
from flask_babel import gettext as _
dbpath = os.path.join(config.APP_DB_ROOT, "app.db")
dbpath = os.path.join(os.path.normpath(os.path.dirname(os.path.realpath(__file__)) + os.sep + ".." + os.sep), "app.db")
engine = create_engine('sqlite:///{0}'.format(dbpath), echo=False)
Base = declarative_base()
ROLE_USER = 0
ROLE_ADMIN = 1
ROLE_DOWNLOAD = 2
ROLE_UPLOAD = 4
ROLE_UPLOAD = 4
ROLE_EDIT = 8
ROLE_PASSWD = 16
ROLE_ANONYMOUS = 32
DETAIL_RANDOM = 1
SIDEBAR_LANGUAGE = 2
SIDEBAR_SERIES = 4
SIDEBAR_CATEGORY = 8
SIDEBAR_HOT = 16
SIDEBAR_RANDOM = 32
SIDEBAR_AUTHOR = 64
DEFAULT_PASS = "admin123"
class UserBase():
DEVELOPMENT = False
class UserBase:
@staticmethod
def is_authenticated(self):
return True
@ -79,25 +95,55 @@ class UserBase():
return self.default_language
def show_random_books(self):
return self.random_books
if self.sidebar_view is not None:
return True if self.sidebar_view & SIDEBAR_RANDOM == SIDEBAR_RANDOM else False
else:
return False
def show_language(self):
return self.language_books
if self.sidebar_view is not None:
return True if self.sidebar_view & SIDEBAR_LANGUAGE == SIDEBAR_LANGUAGE else False
else:
return False
def show_hot_books(self):
return self.hot_books
if self.sidebar_view is not None:
return True if self.sidebar_view & SIDEBAR_HOT == SIDEBAR_HOT else False
else:
return False
def show_series(self):
return self.series_books
if self.sidebar_view is not None:
return True if self.sidebar_view & SIDEBAR_SERIES == SIDEBAR_SERIES else False
else:
return False
def show_category(self):
return self.category_books
if self.sidebar_view is not None:
return True if self.sidebar_view & SIDEBAR_CATEGORY == SIDEBAR_CATEGORY else False
else:
return False
def show_author(self):
if self.sidebar_view is not None:
return True if self.sidebar_view & SIDEBAR_AUTHOR == SIDEBAR_AUTHOR else False
else:
return False
def show_detail_random(self):
if self.sidebar_view is not None:
return True if self.sidebar_view & DETAIL_RANDOM == DETAIL_RANDOM else False
else:
return False
def __repr__(self):
return '<User %r>' % self.nickname
class User(UserBase,Base):
# Baseclass for Users in Calibre-web, settings which are depending on certain users are stored here. It is derived from
# User Base (all access methods are declared there)
class User(UserBase, Base):
__tablename__ = 'user'
id = Column(Integer, primary_key=True)
@ -109,30 +155,34 @@ class User(UserBase,Base):
shelf = relationship('Shelf', backref='user', lazy='dynamic')
downloads = relationship('Downloads', backref='user', lazy='dynamic')
locale = Column(String(2), default="en")
random_books = Column(Integer, default=1)
language_books = Column(Integer, default=1)
series_books = Column(Integer, default=1)
category_books = Column(Integer, default=1)
hot_books = Column(Integer, default=1)
sidebar_view = Column(Integer, default=1)
#language_books = Column(Integer, default=1)
#series_books = Column(Integer, default=1)
#category_books = Column(Integer, default=1)
#hot_books = Column(Integer, default=1)
default_language = Column(String(3), default="all")
class Anonymous(AnonymousUserMixin,UserBase):
# Class for anonymous user is derived from User base and complets overrides methods and properties for the
# anonymous user
class Anonymous(AnonymousUserMixin, UserBase):
def __init__(self):
self.loadSettings()
def loadSettings(self):
data=session.query(User).filter(User.role.op('&')(ROLE_ANONYMOUS) == ROLE_ANONYMOUS).first()
data = session.query(User).filter(User.role.op('&')(ROLE_ANONYMOUS) == ROLE_ANONYMOUS).first()
settings = session.query(Settings).first()
self.nickname = data.nickname
self.role = data.role
self.random_books = data.random_books
self.sidebar_view = data.sidebar_view
self.default_language = data.default_language
self.language_books = data.language_books
self.series_books = data.series_books
self.category_books = data.category_books
self.hot_books = data.hot_books
#self.language_books = data.language_books
#self.series_books = data.series_books
#self.category_books = data.category_books
#self.hot_books = data.hot_books
self.default_language = data.default_language
self.locale = data.locale
self.anon_browse = settings.config_anonbrowse
def role_admin(self):
return False
@ -141,9 +191,10 @@ class Anonymous(AnonymousUserMixin,UserBase):
return False
def is_anonymous(self):
return config.ANON_BROWSE
return self.anon_browse
# Baseclass representing Shelfs in calibre-web inapp.db
class Shelf(Base):
__tablename__ = 'shelf'
@ -155,6 +206,8 @@ class Shelf(Base):
def __repr__(self):
return '<Shelf %r>' % self.name
# Baseclass representing Relationship between books and Shelfs in Calibre-web in app.db (N:M)
class BookShelf(Base):
__tablename__ = 'book_shelf_link'
@ -167,6 +220,7 @@ class BookShelf(Base):
return '<Book %r>' % self.id
# Baseclass representing Downloads from calibre-web in app.db
class Downloads(Base):
__tablename__ = 'downloads'
@ -177,52 +231,133 @@ class Downloads(Base):
def __repr__(self):
return '<Download %r' % self.book_id
# Baseclass for representing settings in app.db with email server settings and Calibre database settings
# (application settings)
class Settings(Base):
__tablename__ = 'settings'
id = Column(Integer, primary_key=True)
mail_server = Column(String)
mail_port = Column(Integer, default = 25)
mail_use_ssl = Column(SmallInteger, default = 0)
mail_port = Column(Integer, default=25)
mail_use_ssl = Column(SmallInteger, default=0)
mail_login = Column(String)
mail_password = Column(String)
mail_from = Column(String)
config_calibre_dir = Column(String)
config_port = Column(Integer, default=8083)
config_calibre_web_title = Column(String, default=u'Calibre-web')
config_books_per_page = Column(Integer, default=60)
config_random_books = Column(Integer, default=4)
config_title_regex = Column(String, default=u'^(A|The|An|Der|Die|Das|Den|Ein|Eine|Einen|Dem|Des|Einem|Eines)\s+')
config_log_level = Column(SmallInteger, default=logging.INFO)
config_uploading = Column(SmallInteger, default=0)
config_anonbrowse = Column(SmallInteger, default=0)
config_public_reg = Column(SmallInteger, default=0)
def __repr__(self):
#return '<Smtp %r>' % (self.mail_server)
pass
# Class holds all application specific settings in calibre-web
class Config:
def __init__(self):
self.config_main_dir = os.path.join(os.path.normpath(os.path.dirname(
os.path.realpath(__file__)) + os.sep + ".." + os.sep))
self.db_configured = None
self.loadSettings()
def loadSettings(self):
data = session.query(Settings).first()
self.config_calibre_dir = data.config_calibre_dir
self.config_port = data.config_port
self.config_calibre_web_title = data.config_calibre_web_title
self.config_books_per_page = data.config_books_per_page
self.config_random_books = data.config_random_books
self.config_title_regex = data.config_title_regex
self.config_log_level = data.config_log_level
self.config_uploading = data.config_uploading
self.config_anonbrowse = data.config_anonbrowse
self.config_public_reg = data.config_public_reg
if self.config_calibre_dir is not None: # and (self.db_configured is None or self.db_configured is True):
self.db_configured = True
else:
self.db_configured = False
@property
def get_main_dir(self):
return self.config_main_dir
def get_Log_Level(self):
ret_value=""
if self.config_log_level == logging.INFO:
ret_value='INFO'
elif self.config_log_level == logging.DEBUG:
ret_value='DEBUG'
elif self.config_log_level == logging.WARNING:
ret_value='WARNING'
elif self.config_log_level == logging.ERROR:
ret_value='ERROR'
return ret_value
# Migrate database to current version, has to be updated after every database change. Currently migration from
# everywhere to curent should work. Migration is done by checking if relevant coloums are existing, and than adding
# rows with SQL commands
def migrate_Database():
if session.query(User).filter(User.role.op('&')(ROLE_ANONYMOUS) == ROLE_ANONYMOUS).first() is None:
create_anonymous_user()
try:
session.query(exists().where(User.random_books)).scalar()
session.query(exists().where(User.locale)).scalar()
session.commit()
except exc.OperationalError: # Database is not compatible, some rows are missing
conn = engine.connect()
conn.execute("ALTER TABLE user ADD column random_books INTEGER DEFAULT 1")
conn.execute("ALTER TABLE user ADD column locale String(2) DEFAULT 'en'")
conn.execute("ALTER TABLE user ADD column default_language String(3) DEFAULT 'all'")
session.commit()
try:
session.query(exists().where(User.language_books)).scalar()
session.query(exists().where(Settings.config_calibre_dir)).scalar()
session.commit()
except exc.OperationalError: # Database is not compatible, some rows are missing
conn = engine.connect()
conn.execute("ALTER TABLE user ADD column language_books INTEGER DEFAULT 1")
conn.execute("ALTER TABLE user ADD column series_books INTEGER DEFAULT 1")
conn.execute("ALTER TABLE user ADD column category_books INTEGER DEFAULT 1")
conn.execute("ALTER TABLE user ADD column hot_books INTEGER DEFAULT 1")
conn.execute("ALTER TABLE Settings ADD column `config_calibre_dir` String")
conn.execute("ALTER TABLE Settings ADD column `config_port` INTEGER DEFAULT 8083")
conn.execute("ALTER TABLE Settings ADD column `config_calibre_web_title` String DEFAULT 'Calibre-web'")
conn.execute("ALTER TABLE Settings ADD column `config_books_per_page` INTEGER DEFAULT 60")
conn.execute("ALTER TABLE Settings ADD column `config_random_books` INTEGER DEFAULT 4")
conn.execute("ALTER TABLE Settings ADD column `config_title_regex` String DEFAULT "
"'^(A|The|An|Der|Die|Das|Den|Ein|Eine|Einen|Dem|Des|Einem|Eines)\s+'")
conn.execute("ALTER TABLE Settings ADD column `config_log_level` SmallInteger DEFAULT " + str(logging.INFO))
conn.execute("ALTER TABLE Settings ADD column `config_uploading` SmallInteger DEFAULT 0")
conn.execute("ALTER TABLE Settings ADD column `config_anonbrowse` SmallInteger DEFAULT 0")
conn.execute("ALTER TABLE Settings ADD column `config_public_reg` SmallInteger DEFAULT 0")
session.commit()
try:
session.query(exists().where(BookShelf.order)).scalar()
session.commit()
except exc.OperationalError: # Database is not compatible, some rows are missing
conn = engine.connect()
conn.execute("ALTER TABLE book_shelf_link ADD column `order` INTEGER DEFAULT 1")
conn.execute("ALTER TABLE book_shelf_link ADD column 'order' INTEGER DEFAULT 1")
session.commit()
try:
create = False
session.query(exists().where(User.sidebar_view)).scalar()
session.commit()
except exc.OperationalError: # Database is not compatible, some rows are missing
conn = engine.connect()
conn.execute("ALTER TABLE user ADD column `sidebar_view` Integer DEFAULT 1")
session.commit()
create=True
try:
if create:
conn.execute("SELET language_books FROM user")
session.commit()
except exc.OperationalError:
conn = engine.connect()
conn.execute("UPDATE user SET 'sidebar_view' = (random_books*"+str(SIDEBAR_RANDOM)+"+ language_books *"+
str(SIDEBAR_LANGUAGE)+"+ series_books *"+str(SIDEBAR_SERIES)+"+ category_books *"+str(SIDEBAR_CATEGORY)+
"+ hot_books *"+str(SIDEBAR_HOT)+"+"+str(SIDEBAR_AUTHOR)+"+"+str(DETAIL_RANDOM)+")")
session.commit()
if session.query(User).filter(User.role.op('&')(ROLE_ANONYMOUS) == ROLE_ANONYMOUS).first() is None:
create_anonymous_user()
def create_default_config():
settings = Settings()
@ -254,10 +389,12 @@ def get_mail_settings():
return data
# Generate user Guest (translated text), as anoymous user, no rights
def create_anonymous_user():
user = User()
user.nickname = _("Guest")
user.email='no@email'
user.email = 'no@email'
user.role = ROLE_ANONYMOUS
user.password = generate_password_hash('1')
@ -269,10 +406,14 @@ def create_anonymous_user():
pass
# Generate User admin with admin123 password, and access to everything
def create_admin_user():
user = User()
user.nickname = "admin"
user.role = ROLE_USER + ROLE_ADMIN + ROLE_DOWNLOAD + ROLE_UPLOAD + ROLE_EDIT + ROLE_PASSWD
user.sidebar_view = DETAIL_RANDOM + SIDEBAR_LANGUAGE + SIDEBAR_SERIES + SIDEBAR_CATEGORY + SIDEBAR_HOT + \
SIDEBAR_RANDOM + SIDEBAR_AUTHOR
user.password = generate_password_hash(DEFAULT_PASS)
session.add(user)
@ -282,10 +423,13 @@ def create_admin_user():
session.rollback()
pass
# Open session for database connection
Session = sessionmaker()
Session.configure(bind=engine)
session = Session()
# generate database and admin and guest user, if no database is existing
if not os.path.exists(dbpath):
try:
Base.metadata.create_all(engine)
@ -296,3 +440,6 @@ if not os.path.exists(dbpath):
pass
else:
migrate_Database()
# Generate global Settings Object accecable from every file
config = Config()

View File

@ -1,3 +1,6 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
import os
from tempfile import gettempdir
import hashlib

File diff suppressed because it is too large Load Diff

View File

@ -8,7 +8,7 @@ msgid ""
msgstr ""
"Project-Id-Version: PROJECT VERSION\n"
"Report-Msgid-Bugs-To: EMAIL@ADDRESS\n"
"POT-Creation-Date: 2017-01-18 19:12+0100\n"
"POT-Creation-Date: 2017-01-28 20:35+0100\n"
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
"Language-Team: LANGUAGE <LL@li.org>\n"
@ -17,284 +17,308 @@ msgstr ""
"Content-Transfer-Encoding: 8bit\n"
"Generated-By: Babel 2.3.4\n"
#: cps/book_formats.py:109 cps/book_formats.py:113 cps/web.py:948
#: cps/book_formats.py:109 cps/book_formats.py:113 cps/web.py:982
msgid "not installed"
msgstr ""
#: cps/helper.py:98
msgid "Calibre-web test email"
msgstr ""
#: cps/helper.py:99 cps/helper.py:155
msgid "This email has been sent via calibre web."
msgstr ""
#: cps/helper.py:136 cps/helper.py:225
#: cps/helper.py:136
#, python-format
msgid "Failed to send mail: %s"
msgstr ""
#: cps/helper.py:154 cps/templates/detail.html:127
#: cps/helper.py:143
msgid "Calibre-web test email"
msgstr ""
#: cps/helper.py:144 cps/helper.py:154
msgid "This email has been sent via calibre web."
msgstr ""
#: cps/helper.py:153 cps/templates/detail.html:129
msgid "Send to Kindle"
msgstr ""
#: cps/helper.py:177 cps/helper.py:192
#: cps/helper.py:171 cps/helper.py:186
msgid "Could not find any formats suitable for sending by email"
msgstr ""
#: cps/helper.py:186
#: cps/helper.py:180
msgid "Could not convert epub to mobi"
msgstr ""
#: cps/helper.py:245
#: cps/helper.py:206
msgid "The requested file could not be read. Maybe wrong permissions?"
msgstr ""
#: cps/ub.py:259
#: cps/ub.py:380
msgid "Guest"
msgstr ""
#: cps/web.py:742
#: cps/web.py:774
msgid "Latest Books"
msgstr ""
#: cps/web.py:767
#: cps/web.py:799
msgid "Hot Books (most downloaded)"
msgstr ""
#: cps/templates/index.xml:29 cps/web.py:775
#: cps/templates/index.xml:29 cps/web.py:808
msgid "Random Books"
msgstr ""
#: cps/web.py:788
#: cps/web.py:821
msgid "Author list"
msgstr ""
#: cps/web.py:805
#: cps/web.py:838
#, python-format
msgid "Author: %(nam)s"
msgstr ""
#: cps/templates/index.xml:50 cps/web.py:818
#: cps/templates/index.xml:50 cps/web.py:851
msgid "Series list"
msgstr ""
#: cps/web.py:829
#: cps/web.py:862
#, python-format
msgid "Series: %(serie)s"
msgstr ""
#: cps/web.py:831 cps/web.py:927 cps/web.py:1126 cps/web.py:1874
#: cps/web.py:864 cps/web.py:961 cps/web.py:1179 cps/web.py:2041
msgid "Error opening eBook. File does not exist or file is not accessible:"
msgstr ""
#: cps/web.py:862
#: cps/web.py:895
msgid "Available languages"
msgstr ""
#: cps/web.py:877
#: cps/web.py:910
#, python-format
msgid "Language: %(name)s"
msgstr ""
#: cps/templates/index.xml:43 cps/web.py:890
#: cps/templates/index.xml:43 cps/web.py:923
msgid "Category list"
msgstr ""
#: cps/web.py:900
#: cps/web.py:933
#, python-format
msgid "Category: %(name)s"
msgstr ""
#: cps/web.py:956
#: cps/web.py:992
msgid "Statistics"
msgstr ""
#: cps/web.py:965
msgid "Server restarts"
#: cps/web.py:1013
msgid "Performing Restart, please reload page"
msgstr ""
#: cps/web.py:1102 cps/web.py:1109 cps/web.py:1116 cps/web.py:1123
#: cps/web.py:1015
msgid "Performing shutdown of server, please close window"
msgstr ""
#: cps/web.py:1091 cps/web.py:1104
msgid "search"
msgstr ""
#: cps/web.py:1155 cps/web.py:1162 cps/web.py:1169 cps/web.py:1176
msgid "Read a Book"
msgstr ""
#: cps/web.py:1172 cps/web.py:1510
#: cps/web.py:1227 cps/web.py:1649
msgid "Please fill out all fields!"
msgstr ""
#: cps/web.py:1188
msgid "An unknown error occured. Please try again later."
msgstr ""
#: cps/web.py:1193
msgid "This username or email address is already in use."
msgstr ""
#: cps/web.py:1196
#: cps/web.py:1228 cps/web.py:1244 cps/web.py:1249 cps/web.py:1251
msgid "register"
msgstr ""
#: cps/web.py:1212
#: cps/web.py:1243
msgid "An unknown error occured. Please try again later."
msgstr ""
#: cps/web.py:1248
msgid "This username or email address is already in use."
msgstr ""
#: cps/web.py:1266
#, python-format
msgid "you are now logged in as: '%(nickname)s'"
msgstr ""
#: cps/web.py:1216
#: cps/web.py:1270
msgid "Wrong Username or Password"
msgstr ""
#: cps/web.py:1218
#: cps/web.py:1272
msgid "login"
msgstr ""
#: cps/web.py:1235
#: cps/web.py:1289
msgid "Please configure the SMTP mail settings first..."
msgstr ""
#: cps/web.py:1239
#: cps/web.py:1293
#, python-format
msgid "Book successfully send to %(kindlemail)s"
msgstr ""
#: cps/web.py:1243
#: cps/web.py:1297
#, python-format
msgid "There was an error sending this book: %(res)s"
msgstr ""
#: cps/web.py:1245
#: cps/web.py:1299
msgid "Please configure your kindle email address first..."
msgstr ""
#: cps/web.py:1265
#: cps/web.py:1319
#, python-format
msgid "Book has been added to shelf: %(sname)s"
msgstr ""
#: cps/web.py:1286
#: cps/web.py:1340
#, python-format
msgid "Book has been removed from shelf: %(sname)s"
msgstr ""
#: cps/web.py:1304 cps/web.py:1325
#: cps/web.py:1359 cps/web.py:1383
#, python-format
msgid "A shelf with the name '%(title)s' already exists."
msgstr ""
#: cps/web.py:1309
#: cps/web.py:1364
#, python-format
msgid "Shelf %(title)s created"
msgstr ""
#: cps/web.py:1311 cps/web.py:1336
#: cps/web.py:1366 cps/web.py:1394
msgid "There was an error"
msgstr ""
#: cps/web.py:1312 cps/web.py:1314
#: cps/web.py:1367 cps/web.py:1369
msgid "create a shelf"
msgstr ""
#: cps/web.py:1334
#: cps/web.py:1392
#, python-format
msgid "Shelf %(title)s changed"
msgstr ""
#: cps/web.py:1337 cps/web.py:1339
#: cps/web.py:1395 cps/web.py:1397
msgid "Edit a shelf"
msgstr ""
#: cps/web.py:1360
#: cps/web.py:1415
#, python-format
msgid "successfully deleted shelf %(name)s"
msgstr ""
#: cps/web.py:1381
#: cps/web.py:1437
#, python-format
msgid "Shelf: '%(name)s'"
msgstr ""
#: cps/web.py:1409
#: cps/web.py:1468
#, python-format
msgid "Change order of Shelf: '%(name)s'"
msgstr ""
#: cps/web.py:1469
#: cps/web.py:1528
msgid "Found an existing account for this email address."
msgstr ""
#: cps/web.py:1471 cps/web.py:1474
#: cps/web.py:1530 cps/web.py:1534
#, python-format
msgid "%(name)s's profile"
msgstr ""
#: cps/web.py:1472
#: cps/web.py:1531
msgid "Profile updated"
msgstr ""
#: cps/web.py:1483 cps/web.py:1491
#: cps/web.py:1544
msgid "Admin page"
msgstr ""
#: cps/templates/admin.html:33 cps/web.py:1511
#: cps/web.py:1604
msgid "Calibre-web configuration updated"
msgstr ""
#: cps/web.py:1611 cps/web.py:1617 cps/web.py:1630
msgid "Basic Configuration"
msgstr ""
#: cps/web.py:1615
msgid "DB location is not valid, please enter correct path"
msgstr ""
#: cps/templates/admin.html:33 cps/web.py:1651 cps/web.py:1693
msgid "Add new user"
msgstr ""
#: cps/web.py:1544
#: cps/web.py:1687
#, python-format
msgid "User '%(user)s' created"
msgstr ""
#: cps/web.py:1548
#: cps/web.py:1691
msgid "Found an existing account for this email address or nickname."
msgstr ""
#: cps/web.py:1568
#: cps/web.py:1711
msgid "Mail settings updated"
msgstr ""
#: cps/web.py:1574
#: cps/web.py:1717
#, python-format
msgid "Test E-Mail successfully send to %(kindlemail)s"
msgstr ""
#: cps/web.py:1577
#: cps/web.py:1720
#, python-format
msgid "There was an error sending the Test E-Mail: %(res)s"
msgstr ""
#: cps/web.py:1578
#: cps/web.py:1721
msgid "Edit mail settings"
msgstr ""
#: cps/web.py:1606
#: cps/web.py:1749
#, python-format
msgid "User '%(nick)s' deleted"
msgstr ""
#: cps/web.py:1661
#: cps/web.py:1825
#, python-format
msgid "User '%(nick)s' updated"
msgstr ""
#: cps/web.py:1664
#: cps/web.py:1828
msgid "An unknown error occured."
msgstr ""
#: cps/web.py:1666
#: cps/web.py:1831
#, python-format
msgid "Edit User %(nick)s"
msgstr ""
#: cps/web.py:1904
#: cps/web.py:2036 cps/web.py:2039 cps/web.py:2113
msgid "edit metadata"
msgstr ""
#: cps/web.py:2071
#, python-format
msgid "Failed to create path %s (Permission denied)."
msgstr ""
#: cps/web.py:1909
#: cps/web.py:2076
#, python-format
msgid "Failed to store file %s (Permission denied)."
msgstr ""
#: cps/web.py:1914
#: cps/web.py:2081
#, python-format
msgid "Failed to delete file %s (Permission denied)."
msgstr ""
@ -323,7 +347,7 @@ msgstr ""
msgid "Admin"
msgstr ""
#: cps/templates/admin.html:12 cps/templates/detail.html:114
#: cps/templates/admin.html:12 cps/templates/detail.html:116
msgid "Download"
msgstr ""
@ -371,15 +395,15 @@ msgstr ""
msgid "Change SMTP settings"
msgstr ""
#: cps/templates/admin.html:56
#: cps/templates/admin.html:56 cps/templates/admin.html:76
msgid "Configuration"
msgstr ""
#: cps/templates/admin.html:59
msgid "Log File"
msgid "Calibre DB dir"
msgstr ""
#: cps/templates/admin.html:60
#: cps/templates/admin.html:60 cps/templates/config_edit.html:32
msgid "Log Level"
msgstr ""
@ -387,7 +411,7 @@ msgstr ""
msgid "Port"
msgstr ""
#: cps/templates/admin.html:62
#: cps/templates/admin.html:62 cps/templates/config_edit.html:19
msgid "Books per page"
msgstr ""
@ -403,101 +427,155 @@ msgstr ""
msgid "Anonymous browsing"
msgstr ""
#: cps/templates/admin.html:76
#: cps/templates/admin.html:77
msgid "Administration"
msgstr ""
#: cps/templates/admin.html:78
#: cps/templates/admin.html:79
msgid "Restart Calibre-web"
msgstr ""
#: cps/templates/detail.html:38
msgid "Book"
#: cps/templates/admin.html:80
msgid "Stop Calibre-web"
msgstr ""
#: cps/templates/detail.html:38
msgid "of"
#: cps/templates/admin.html:91
msgid "Do you really want to restart Calibre-web?"
msgstr ""
#: cps/templates/detail.html:44
msgid "language"
#: cps/templates/admin.html:92 cps/templates/admin.html:107
msgid "Ok"
msgstr ""
#: cps/templates/detail.html:103
msgid "Description:"
#: cps/templates/admin.html:93 cps/templates/admin.html:108
#: cps/templates/book_edit.html:108 cps/templates/config_edit.html:54
#: cps/templates/email_edit.html:36 cps/templates/shelf_edit.html:17
#: cps/templates/shelf_order.html:12 cps/templates/user_edit.html:107
msgid "Back"
msgstr ""
#: cps/templates/detail.html:131
msgid "Read in browser"
#: cps/templates/admin.html:106
msgid "Do you really want to stop Calibre-web?"
msgstr ""
#: cps/templates/detail.html:151
msgid "Add to shelf"
msgstr ""
#: cps/templates/detail.html:191
msgid "Edit metadata"
msgstr ""
#: cps/templates/edit_book.html:14 cps/templates/search_form.html:6
#: cps/templates/book_edit.html:16 cps/templates/search_form.html:6
msgid "Book Title"
msgstr ""
#: cps/templates/edit_book.html:18 cps/templates/search_form.html:10
#: cps/templates/book_edit.html:20 cps/templates/search_form.html:10
msgid "Author"
msgstr ""
#: cps/templates/edit_book.html:22
#: cps/templates/book_edit.html:24
msgid "Description"
msgstr ""
#: cps/templates/edit_book.html:26 cps/templates/search_form.html:13
#: cps/templates/book_edit.html:28 cps/templates/search_form.html:13
msgid "Tags"
msgstr ""
#: cps/templates/edit_book.html:31 cps/templates/layout.html:133
#: cps/templates/book_edit.html:33 cps/templates/layout.html:133
#: cps/templates/search_form.html:33
msgid "Series"
msgstr ""
#: cps/templates/edit_book.html:35
#: cps/templates/book_edit.html:37
msgid "Series id"
msgstr ""
#: cps/templates/edit_book.html:39
#: cps/templates/book_edit.html:41
msgid "Rating"
msgstr ""
#: cps/templates/edit_book.html:43
#: cps/templates/book_edit.html:45
msgid "Cover URL (jpg)"
msgstr ""
#: cps/templates/edit_book.html:48 cps/templates/user_edit.html:27
#: cps/templates/book_edit.html:50 cps/templates/user_edit.html:27
msgid "Language"
msgstr ""
#: cps/templates/edit_book.html:59
#: cps/templates/book_edit.html:61
msgid "Yes"
msgstr ""
#: cps/templates/edit_book.html:60
#: cps/templates/book_edit.html:62
msgid "No"
msgstr ""
#: cps/templates/edit_book.html:102
#: cps/templates/book_edit.html:104
msgid "view book after edit"
msgstr ""
#: cps/templates/edit_book.html:105 cps/templates/login.html:19
#: cps/templates/search_form.html:75 cps/templates/shelf_edit.html:15
#: cps/templates/user_edit.html:97
#: cps/templates/book_edit.html:107 cps/templates/config_edit.html:52
#: cps/templates/login.html:19 cps/templates/search_form.html:75
#: cps/templates/shelf_edit.html:15 cps/templates/user_edit.html:105
msgid "Submit"
msgstr ""
#: cps/templates/edit_book.html:106 cps/templates/email_edit.html:36
#: cps/templates/shelf_edit.html:17 cps/templates/shelf_order.html:12
#: cps/templates/user_edit.html:99
msgid "Back"
#: cps/templates/config_edit.html:7
msgid "Location of Calibre database"
msgstr ""
#: cps/templates/config_edit.html:11
msgid "Server Port"
msgstr ""
#: cps/templates/config_edit.html:15 cps/templates/shelf_edit.html:7
msgid "Title"
msgstr ""
#: cps/templates/config_edit.html:23
msgid "No. of random books to show"
msgstr ""
#: cps/templates/config_edit.html:28
msgid "Regular expression for title sorting"
msgstr ""
#: cps/templates/config_edit.html:42
msgid "Enable uploading"
msgstr ""
#: cps/templates/config_edit.html:46
msgid "Enable anonymous browsing"
msgstr ""
#: cps/templates/config_edit.html:50
msgid "Enable public registration"
msgstr ""
#: cps/templates/config_edit.html:57 cps/templates/layout.html:91
#: cps/templates/login.html:4
msgid "Login"
msgstr ""
#: cps/templates/detail.html:40
msgid "Book"
msgstr ""
#: cps/templates/detail.html:40
msgid "of"
msgstr ""
#: cps/templates/detail.html:46
msgid "language"
msgstr ""
#: cps/templates/detail.html:105
msgid "Description:"
msgstr ""
#: cps/templates/detail.html:133
msgid "Read in browser"
msgstr ""
#: cps/templates/detail.html:153
msgid "Add to shelf"
msgstr ""
#: cps/templates/detail.html:193
msgid "Edit metadata"
msgstr ""
#: cps/templates/email_edit.html:11
@ -600,10 +678,6 @@ msgstr ""
msgid "Logout"
msgstr ""
#: cps/templates/layout.html:91 cps/templates/login.html:4
msgid "Login"
msgstr ""
#: cps/templates/layout.html:92 cps/templates/register.html:18
msgid "Register"
msgstr ""
@ -722,10 +796,6 @@ msgstr ""
msgid "Change order"
msgstr ""
#: cps/templates/shelf_edit.html:7
msgid "Title"
msgstr ""
#: cps/templates/shelf_edit.html:12
msgid "should the shelf be public?"
msgstr ""
@ -790,31 +860,39 @@ msgstr ""
msgid "Show category selection"
msgstr ""
#: cps/templates/user_edit.html:68
#: cps/templates/user_edit.html:65
msgid "Show author selection"
msgstr ""
#: cps/templates/user_edit.html:69
msgid "Show random books in detail view"
msgstr ""
#: cps/templates/user_edit.html:76
msgid "Admin user"
msgstr ""
#: cps/templates/user_edit.html:73
#: cps/templates/user_edit.html:81
msgid "Allow Downloads"
msgstr ""
#: cps/templates/user_edit.html:77
#: cps/templates/user_edit.html:85
msgid "Allow Uploads"
msgstr ""
#: cps/templates/user_edit.html:81
#: cps/templates/user_edit.html:89
msgid "Allow Edit"
msgstr ""
#: cps/templates/user_edit.html:86
#: cps/templates/user_edit.html:94
msgid "Allow Changing Password"
msgstr ""
#: cps/templates/user_edit.html:93
#: cps/templates/user_edit.html:101
msgid "Delete this user"
msgstr ""
#: cps/templates/user_edit.html:104
#: cps/templates/user_edit.html:112
msgid "Recent Downloads"
msgstr ""

View File

@ -8,6 +8,7 @@ Calibre Web is a web app providing a clean interface for browsing, reading and d
##Features
- Bootstrap 3 HTML5 interface
- full graphical setup
- User management
- Admin interface
- User Interface in english, french, german, simplified chinese, spanish
@ -23,12 +24,14 @@ Calibre Web is a web app providing a clean interface for browsing, reading and d
- Upload new books in PDF, epub, fb2 format
- Support for Calibre custom columns
- Fine grained per-user permissions
- Self update capability
## Quick start
1. Rename `config.ini.example` to `config.ini` and set `DB_ROOT` to the path of the folder where your Calibre library (metadata.db) lives
2. Execute the command: `python cps.py`
3. Point your browser to `http://localhost:8083` or `http://localhost:8083/opds` for the OPDS catalog
1. Execute the command: `python cps.py`
2. Point your browser to `http://localhost:8083` or `http://localhost:8083/opds` for the OPDS catalog
3. Set `Location of Calibre database` to the path of the folder where your Calibre library (metadata.db) lives, push "submit" button
4. Go to Login page
**Default admin login:**
*Username:* admin
@ -36,12 +39,19 @@ Calibre Web is a web app providing a clean interface for browsing, reading and d
## Runtime Configuration Options
`PUBLIC_REG`
Set to 1 to enable public user registration.
`ANON_BROWSE`
Set to 1 to allow not logged in users to browse the catalog.
`UPLOADING`
Set to 1 to enable PDF uploading. This requires the imagemagick library to be installed.
The configuration can be changed as admin in the admin panel under "Configuration"
Server Port:
Changes the port calibre-web is listening, changes take effect after pressing submit button
Enable public registration:
Tick to enable public user registration.
Enable anonymous browsing:
Tick to allow not logged in users to browse the catalog, anonymous user permissions can be set as admin ("Guest" user)
Enable uploading:
Tick to enable uploading of PDF, epub, FB2. This requires the imagemagick library to be installed.
## Requirements