Merge pull request #9 from janeczku/master

merge from janeczku/master
This commit is contained in:
Ethan Lin 2017-07-06 10:36:52 +08:00 committed by GitHub
commit 2272013d80
10 changed files with 32218 additions and 139 deletions

View File

@ -11,7 +11,7 @@ from ub import config
import ub
session = None
cc_exceptions = ['datetime', 'int', 'comments', 'float', 'composite', 'series']
cc_exceptions = ['datetime', 'comments', 'float', 'composite', 'series']
cc_classes = None
engine = None
@ -302,7 +302,6 @@ def setup_db():
return False
dbpath = os.path.join(config.config_calibre_dir, "metadata.db")
#engine = create_engine('sqlite:///{0}'.format(dbpath.encode('utf-8')), echo=False, isolation_level="SERIALIZABLE")
engine = create_engine('sqlite:///'+ dbpath, echo=False, isolation_level="SERIALIZABLE")
try:
conn = engine.connect()
@ -340,6 +339,11 @@ def setup_db():
'id': Column(Integer, primary_key=True),
'book': Column(Integer, ForeignKey('books.id')),
'value': Column(Boolean)}
elif row.datatype == 'int':
ccdict = {'__tablename__': 'custom_column_' + str(row.id),
'id': Column(Integer, primary_key=True),
'book': Column(Integer, ForeignKey('books.id')),
'value': Column(Integer)}
else:
ccdict = {'__tablename__': 'custom_column_' + str(row.id),
'id': Column(Integer, primary_key=True),
@ -347,7 +351,7 @@ def setup_db():
cc_classes[row.id] = type('Custom_Column_' + str(row.id), (Base,), ccdict)
for cc_id in cc_ids:
if cc_id[1] == 'bool':
if (cc_id[1] == 'bool') or (cc_id[1] == 'int'):
setattr(Books, 'custom_column_' + str(cc_id[0]), relationship(cc_classes[cc_id[0]],
primaryjoin=(
Books.id == cc_classes[cc_id[0]].book),

View File

@ -52,6 +52,9 @@ except Exception as e:
global_task = None
updater_thread = None
RET_SUCCESS = 1
RET_FAIL = 0
def update_download(book_id, user_id):
check = ub.session.query(ub.Downloads).filter(ub.Downloads.user_id == user_id).filter(ub.Downloads.book_id ==
book_id).first()
@ -63,6 +66,7 @@ def update_download(book_id, user_id):
def make_mobi(book_id, calibrepath):
error_message = None
vendorpath = os.path.join(os.path.normpath(os.path.dirname(os.path.realpath(__file__)) +
os.sep + "../vendor" + os.sep))
if sys.platform == "win32":
@ -70,24 +74,41 @@ def make_mobi(book_id, calibrepath):
else:
kindlegen = (os.path.join(vendorpath, u"kindlegen")).encode(sys.getfilesystemencoding())
if not os.path.exists(kindlegen):
app.logger.error("make_mobi: kindlegen binary not found in: %s" % kindlegen)
return None
error_message = _(u"kindlegen binary %(kindlepath)s not found", kindlepath=kindlegen)
app.logger.error("make_mobi: " + error_message)
return error_message, RET_FAIL
book = db.session.query(db.Books).filter(db.Books.id == book_id).first()
data = db.session.query(db.Data).filter(db.Data.book == book.id).filter(db.Data.format == 'EPUB').first()
if not data:
app.logger.error("make_mobi: epub format not found for book id: %d" % book_id)
return None
error_message = _(u"epub format not found for book id: %(book)d", book=book_id)
app.logger.error("make_mobi: " + error_message)
return error_message, RET_FAIL
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()),
stdout=subprocess.PIPE, stderr=subprocess.PIPE, shell=True)
try:
p = subprocess.Popen((kindlegen + " \"" + file_path + u".epub\"").encode(sys.getfilesystemencoding()),
stdout=subprocess.PIPE, stderr=subprocess.PIPE, shell=True)
except:
error_message = _(u"kindlegen failed, no excecution permissions")
app.logger.error("make_mobi: "+error_message)
return error_message, RET_FAIL
# Poll process for new output until finished
while True:
nextline = p.stdout.readline()
if nextline == '' and p.poll() is not None:
break
if nextline != "\r\n":
# Format of error message (kindlegen translates its output texts):
# Error(prcgen):E23006: Language not recognized in metadata.The dc:Language field is mandatory.Aborting.
conv_error=re.search(".*\(.*\):(E\d+):\s(.*)",nextline)
# If error occoures, log in every case
if conv_error:
error_message = _(u"Kindlegen failed with Error %(error)s. Message: %(message)s",
error=conv_error.group(1), message=conv_error.group(2).decode('utf-8'))
app.logger.info("make_mobi: " + error_message)
app.logger.info(nextline.strip('\r\n'))
app.logger.debug(nextline.strip('\r\n'))
check = p.returncode
@ -99,13 +120,13 @@ def make_mobi(book_id, calibrepath):
uncompressed_size=os.path.getsize(file_path + ".mobi")
))
db.session.commit()
return file_path + ".mobi"
return file_path + ".mobi", RET_SUCCESS
else:
app.logger.error("make_mobi: kindlegen failed with error while converting book")
return None
return None, RET_FAIL
else:
app.logger.error("make_mobie: epub not found: %s.epub" % file_path)
return None
app.logger.error("make_mobi: epub not found: %s.epub" % file_path)
return None, RET_FAIL
class StderrLogger(object):
@ -204,13 +225,11 @@ def send_mail(book_id, kindle_mail, calibrepath):
if 'mobi' in formats:
msg.attach(get_attachment(formats['mobi']))
elif 'epub' in formats:
filepath = make_mobi(book.id, calibrepath)
if filepath is not None:
msg.attach(get_attachment(filepath))
elif filepath is None:
return _("Could not convert epub to mobi")
elif 'pdf' in formats:
msg.attach(get_attachment(formats['pdf']))
data, resultCode = make_mobi(book.id, calibrepath)
if resultCode == RET_SUCCESS:
msg.attach(get_attachment(data))
else:
return data #_("Could not convert epub to mobi")
elif 'pdf' in formats:
msg.attach(get_attachment(formats['pdf']))
else:

View File

@ -51,12 +51,12 @@
<label for="cover_url">{{_('Cover URL (jpg)')}}</label>
<input type="text" class="form-control" name="cover_url" id="cover_url" value="">
</div>
<div class="form-group">
<label for="languages">{{_('Language')}}</label>
<input type="text" class="form-control typeahead" name="languages" id="languages" value="{% for language in book.languages %}{{language.language_name.strip()}}, {% endfor %}">
</div>
{% if cc|length > 0 %}
{% for c in cc %}
<div class="form-group">
@ -68,6 +68,11 @@
<option value="False" {% if book['custom_column_' ~ c.id]|length > 0 %}{% if book['custom_column_' ~ c.id][0].value ==false %}selected{% endif %}{% endif %}>{{_('No')}}</option>
</select>
{% endif %}
{% if c.datatype == 'int' %}
<input type="number" step="1" class="form-control" name="{{ 'custom_column_' ~ c.id }}" id="{{ 'custom_column_' ~ c.id }}" value="{% if book['custom_column_' ~ c.id]|length > 0 %}{{ book['custom_column_' ~ c.id][0].value }}{% endif %}">
{% endif %}
{% if c.datatype in ['text', 'series'] and not c.is_multiple %}
<input type="text" class="form-control" name="{{ 'custom_column_' ~ c.id }}" id="{{ 'custom_column_' ~ c.id }}"
{% if book['custom_column_' ~ c.id]|length > 0 %}
@ -80,12 +85,12 @@
{% if book['custom_column_' ~ c.id]|length > 0 %}
value="{% for column in book['custom_column_' ~ c.id] %}{{ column.value.strip() }}{% if not loop.last %}, {% endif %}{% endfor %}"{% endif %}>
{% endif %}
{% if c.datatype == 'enumeration' %}
<select class="form-control" name="{{ 'custom_column_' ~ c.id }}" id="{{ 'custom_column_' ~ c.id }}">
<option></option>
{% for opt in c.get_display_dict().enum_values %}
<option
<option
{% if book['custom_column_' ~ c.id]|length > 0 %}
{% if book['custom_column_' ~ c.id][0].value == opt %}selected="selected"{% endif %}
{% endif %}
@ -102,9 +107,9 @@
{% endif %}
</div>
{% endfor %}
{% endif %}
{% endif %}
<div class="checkbox">
<label>
<input name="detail_view" type="checkbox" checked> {{_('view book after edit')}}

View File

@ -48,7 +48,7 @@ See https://github.com/adobe-type-tools/cmap-resources
<!--<script src="pdf.js"></script>-->
<script type="text/javascript">
var DEFAULT_URL = "{{ url_for('static', filename=pdffile) }}";
var DEFAULT_URL = "{{ url_for('serve_book', book_id=pdffile,book_format='pdf') }}";
var PDFWORKER_LOCATION="{{ url_for('static', filename='js/libs/pdf.worker.js') }}";
// var IMAGE_LOCATION="{{ url_for('static', filename='css/../images') }}";
var IMAGE_LOCATION="{{ url_for('static', filename='/images/') }}";

View File

@ -109,7 +109,7 @@
$("#area").width($("#area").width());
$("#content").width($("#content").width());
//bind text
$("#content").load("{{ url_for('static', filename=txtfile) }}",function(textStr) {
$("#content").load("{{ url_for('serve_book', book_id=txtfile,book_format='txt') }}",function(textStr) {
$(this).height($(this).parent().height()*0.95);
$(this).text(textStr);
});

Binary file not shown.

File diff suppressed because it is too large Load Diff

View File

@ -1261,13 +1261,16 @@ def stats():
kindlegen = os.path.join(vendorpath, u"kindlegen")
versions['KindlegenVersion'] = _('not installed')
if os.path.exists(kindlegen):
p = subprocess.Popen(kindlegen, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
p.wait()
for lines in p.stdout.readlines():
if isinstance(lines, bytes):
lines = lines.decode('utf-8')
if re.search('Amazon kindlegen\(', lines):
versions['KindlegenVersion'] = lines
try:
p = subprocess.Popen(kindlegen, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
p.wait()
for lines in p.stdout.readlines():
if isinstance(lines, bytes):
lines = lines.decode('utf-8')
if re.search('Amazon kindlegen\(', lines):
versions['KindlegenVersion'] = lines
except:
versions['KindlegenVersion'] = _('Excecution permissions missing')
versions['PythonVersion'] = sys.version
versions['babel'] = babelVersion
versions['sqlalchemy'] = sqlalchemyVersion
@ -1290,6 +1293,12 @@ def delete_book(book_id):
if current_user.role_delete_books():
book = db.session.query(db.Books).filter(db.Books.id == book_id).first()
if book:
# delete book from Shelfs, Downloads, Read list
ub.session.query(ub.BookShelf).filter(ub.BookShelf.book_id == book_id).delete()
ub.session.query(ub.ReadBook).filter(ub.ReadBook.book_id == book_id).delete()
ub.session.query(ub.Downloads).filter(ub.Downloads.book_id == book_id).delete()
ub.session.commit()
if config.config_use_google_drive:
helper.delete_book_gdrive(book) # ToDo really delete file
else:
@ -1307,7 +1316,7 @@ def delete_book(book_id):
cc_string = "custom_column_" + str(c.id)
if not c.is_multiple:
if len(getattr(book, cc_string)) > 0:
if c.datatype == 'bool':
if c.datatype == 'bool' or c.datatype == 'integer':
del_cc = getattr(book, cc_string)[0]
getattr(book, cc_string).remove(del_cc)
db.session.delete(del_cc)
@ -1575,6 +1584,24 @@ def get_cover(cover_path):
else:
return send_from_directory(os.path.join(config.config_calibre_dir, cover_path), "cover.jpg")
@app.route("/show/<book_id>/<book_format>")
@login_required_if_no_ano
def serve_book(book_id,book_format):
book_format = book_format.split(".")[0]
book = db.session.query(db.Books).filter(db.Books.id == book_id).first()
data = db.session.query(db.Data).filter(db.Data.book == book.id).filter(db.Data.format == book_format.upper()).first()
app.logger.info(data.name)
if config.config_use_google_drive:
headers = Headers()
try:
headers["Content-Type"] = mimetypes.types_map['.' + book_format]
except KeyError:
headers["Content-Type"] = "application/octet-stream"
df = gdriveutils.getFileFromEbooksFolder(Gdrive.Instance().drive, book.path, data.name + "." + book_format)
return do_gdrive_download(df, headers)
else:
return send_from_directory(os.path.join(config.config_calibre_dir, book.path), data.name + "." + book_format)
@app.route("/opds/thumb_240_240/<path:book_id>")
@app.route("/opds/cover_240_240/<path:book_id>")
@ -1674,19 +1701,9 @@ def read_book(book_id, book_format):
zfile.close()
return render_title_template('read.html', bookid=book_id, title=_(u"Read a Book"))
elif book_format.lower() == "pdf":
all_name = str(book_id) + "/" + book.data[0].name + ".pdf"
tmp_file = os.path.join(book_dir, book.data[0].name) + ".pdf"
if not os.path.exists(tmp_file):
pdf_file = os.path.join(config.config_calibre_dir, book.path, book.data[0].name) + ".pdf"
copyfile(pdf_file, tmp_file)
return render_title_template('readpdf.html', pdffile=all_name, title=_(u"Read a Book"))
return render_title_template('readpdf.html', pdffile=book_id, title=_(u"Read a Book"))
elif book_format.lower() == "txt":
all_name = str(book_id) + "/" + book.data[0].name + ".txt"
tmp_file = os.path.join(book_dir, book.data[0].name) + ".txt"
if not os.path.exists(all_name):
txt_file = os.path.join(config.config_calibre_dir, book.path, book.data[0].name) + ".txt"
copyfile(txt_file, tmp_file)
return render_title_template('readtxt.html', txtfile=all_name, title=_(u"Read a Book"))
return render_title_template('readtxt.html', txtfile=book_id, title=_(u"Read a Book"))
elif book_format.lower() == "cbr":
all_name = str(book_id) + "/" + book.data[0].name + ".cbr"
tmp_file = os.path.join(book_dir, book.data[0].name) + ".cbr"
@ -1789,10 +1806,10 @@ def login():
if user and check_password_hash(user.password, form['password']):
login_user(user, remember=True)
flash(_(u"you are now logged in as: '%(nickname)s'", nickname=user.nickname), category="success")
# test=
return redirect(url_for("index"))
else:
app.logger.info('Login failed for user "'+form['username']+'"')
ipAdress=request.headers.get('X-Forwarded-For', request.remote_addr)
app.logger.info('Login failed for user "' + form['username'] + '" IP-adress: ' + ipAdress)
flash(_(u"Wrong Username or Password"), category="error")
return render_title_template('login.html', title=_(u"login"))
@ -2345,7 +2362,8 @@ def edit_user(user_id):
if request.method == "POST":
to_save = request.form.to_dict()
if "delete" in to_save:
ub.session.delete(content)
ub.session.query(ub.User).filter(ub.User.id == content.id).delete()
ub.session.commit()
flash(_(u"User '%(nick)s' deleted", nick=content.nickname), category="success")
return redirect(url_for('admin'))
else:
@ -2583,6 +2601,22 @@ def edit_book(book_id):
cc_class = db.cc_classes[c.id]
new_cc = cc_class(value=to_save[cc_string], book=book_id)
db.session.add(new_cc)
elif c.datatype == 'int':
if to_save[cc_string] == 'None':
to_save[cc_string] = None
if to_save[cc_string] != cc_db_value:
if cc_db_value is not None:
if to_save[cc_string] is not None:
setattr(getattr(book, cc_string)[0], 'value', to_save[cc_string])
else:
del_cc = getattr(book, cc_string)[0]
getattr(book, cc_string).remove(del_cc)
db.session.delete(del_cc)
else:
cc_class = db.cc_classes[c.id]
new_cc = cc_class(value=to_save[cc_string], book=book_id)
db.session.add(new_cc)
else:
if c.datatype == 'rating':
to_save[cc_string] = str(int(float(to_save[cc_string]) * 2))

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-05-01 16:20+0200\n"
"POT-Creation-Date: 2017-06-24 12:58+0200\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"
@ -21,31 +21,46 @@ msgstr ""
msgid "not installed"
msgstr ""
#: cps/helper.py:165
#: cps/helper.py:77
#, python-format
msgid "kindlegen binary %(kindlepath)s not found"
msgstr ""
#: cps/helper.py:83
#, python-format
msgid "epub format not found for book id: %(book)d"
msgstr ""
#: cps/helper.py:93
msgid "kindlegen failed, no excecution permissions"
msgstr ""
#: cps/helper.py:109
#, python-format
msgid "Kindlegen failed with Error %(error)s. Message: %(message)s"
msgstr ""
#: cps/helper.py:186
#, python-format
msgid "Failed to send mail: %s"
msgstr ""
#: cps/helper.py:172
#: cps/helper.py:193
msgid "Calibre-web test email"
msgstr ""
#: cps/helper.py:173 cps/helper.py:185
#: cps/helper.py:194 cps/helper.py:206
msgid "This email has been sent via calibre web."
msgstr ""
#: cps/helper.py:182 cps/templates/detail.html:146
#: cps/helper.py:203 cps/templates/detail.html:146
msgid "Send to Kindle"
msgstr ""
#: cps/helper.py:202 cps/helper.py:217
#: cps/helper.py:223 cps/helper.py:236
msgid "Could not find any formats suitable for sending by email"
msgstr ""
#: cps/helper.py:211
msgid "Could not convert epub to mobi"
msgstr ""
#: cps/ub.py:487
msgid "Guest"
msgstr ""
@ -103,8 +118,8 @@ msgstr ""
msgid "Author: %(name)s"
msgstr ""
#: cps/web.py:1078 cps/web.py:1106 cps/web.py:1239 cps/web.py:1699
#: cps/web.py:2638
#: cps/web.py:1078 cps/web.py:1106 cps/web.py:1239 cps/web.py:1716
#: cps/web.py:2672
msgid "Error opening eBook. File does not exist or file is not accessible:"
msgstr ""
@ -135,240 +150,244 @@ msgstr ""
msgid "Category: %(name)s"
msgstr ""
#: cps/web.py:1284
#: cps/web.py:1273
msgid "Excecution permissions missing"
msgstr ""
#: cps/web.py:1287
msgid "Statistics"
msgstr ""
#: cps/web.py:1442
#: cps/web.py:1451
msgid "Server restarted, please reload page"
msgstr ""
#: cps/web.py:1444
#: cps/web.py:1453
msgid "Performing shutdown of server, please close window"
msgstr ""
#: cps/web.py:1460
#: cps/web.py:1469
msgid "Update done"
msgstr ""
#: cps/web.py:1538 cps/web.py:1551
#: cps/web.py:1547 cps/web.py:1560
msgid "search"
msgstr ""
#: cps/web.py:1675 cps/web.py:1682 cps/web.py:1689 cps/web.py:1696
#: cps/web.py:1702 cps/web.py:1704 cps/web.py:1706 cps/web.py:1713
msgid "Read a Book"
msgstr ""
#: cps/web.py:1752 cps/web.py:2234
#: cps/web.py:1769 cps/web.py:2251
msgid "Please fill out all fields!"
msgstr ""
#: cps/web.py:1753 cps/web.py:1769 cps/web.py:1774 cps/web.py:1776
#: cps/web.py:1770 cps/web.py:1786 cps/web.py:1791 cps/web.py:1793
msgid "register"
msgstr ""
#: cps/web.py:1768
#: cps/web.py:1785
msgid "An unknown error occured. Please try again later."
msgstr ""
#: cps/web.py:1773
#: cps/web.py:1790
msgid "This username or email address is already in use."
msgstr ""
#: cps/web.py:1791
#: cps/web.py:1808
#, python-format
msgid "you are now logged in as: '%(nickname)s'"
msgstr ""
#: cps/web.py:1796
#: cps/web.py:1813
msgid "Wrong Username or Password"
msgstr ""
#: cps/web.py:1798
#: cps/web.py:1815
msgid "login"
msgstr ""
#: cps/web.py:1815
#: cps/web.py:1832
msgid "Please configure the SMTP mail settings first..."
msgstr ""
#: cps/web.py:1819
#: cps/web.py:1836
#, python-format
msgid "Book successfully send to %(kindlemail)s"
msgstr ""
#: cps/web.py:1823
#: cps/web.py:1840
#, python-format
msgid "There was an error sending this book: %(res)s"
msgstr ""
#: cps/web.py:1825 cps/web.py:2318
#: cps/web.py:1842 cps/web.py:2335
msgid "Please configure your kindle email address first..."
msgstr ""
#: cps/web.py:1850
#: cps/web.py:1867
#, python-format
msgid "Book has been added to shelf: %(sname)s"
msgstr ""
#: cps/web.py:1870
#: cps/web.py:1887
#, python-format
msgid "Book has been removed from shelf: %(sname)s"
msgstr ""
#: cps/web.py:1888 cps/web.py:1912
#: cps/web.py:1905 cps/web.py:1929
#, python-format
msgid "A shelf with the name '%(title)s' already exists."
msgstr ""
#: cps/web.py:1893
#: cps/web.py:1910
#, python-format
msgid "Shelf %(title)s created"
msgstr ""
#: cps/web.py:1895 cps/web.py:1923
#: cps/web.py:1912 cps/web.py:1940
msgid "There was an error"
msgstr ""
#: cps/web.py:1896 cps/web.py:1898
#: cps/web.py:1913 cps/web.py:1915
msgid "create a shelf"
msgstr ""
#: cps/web.py:1921
#: cps/web.py:1938
#, python-format
msgid "Shelf %(title)s changed"
msgstr ""
#: cps/web.py:1924 cps/web.py:1926
#: cps/web.py:1941 cps/web.py:1943
msgid "Edit a shelf"
msgstr ""
#: cps/web.py:1946
#: cps/web.py:1963
#, python-format
msgid "successfully deleted shelf %(name)s"
msgstr ""
#: cps/web.py:1968
#: cps/web.py:1985
#, python-format
msgid "Shelf: '%(name)s'"
msgstr ""
#: cps/web.py:1999
#: cps/web.py:2016
#, python-format
msgid "Change order of Shelf: '%(name)s'"
msgstr ""
#: cps/web.py:2063
#: cps/web.py:2080
msgid "Found an existing account for this email address."
msgstr ""
#: cps/web.py:2065 cps/web.py:2069
#: cps/web.py:2082 cps/web.py:2086
#, python-format
msgid "%(name)s's profile"
msgstr ""
#: cps/web.py:2066
#: cps/web.py:2083
msgid "Profile updated"
msgstr ""
#: cps/web.py:2080
#: cps/web.py:2097
msgid "Admin page"
msgstr ""
#: cps/web.py:2188
#: cps/web.py:2205
msgid "Calibre-web configuration updated"
msgstr ""
#: cps/web.py:2195 cps/web.py:2201 cps/web.py:2215
#: cps/web.py:2212 cps/web.py:2218 cps/web.py:2232
msgid "Basic Configuration"
msgstr ""
#: cps/web.py:2199
#: cps/web.py:2216
msgid "DB location is not valid, please enter correct path"
msgstr ""
#: cps/templates/admin.html:34 cps/web.py:2236 cps/web.py:2288
#: cps/templates/admin.html:34 cps/web.py:2253 cps/web.py:2305
msgid "Add new user"
msgstr ""
#: cps/web.py:2280
#: cps/web.py:2297
#, python-format
msgid "User '%(user)s' created"
msgstr ""
#: cps/web.py:2284
#: cps/web.py:2301
msgid "Found an existing account for this email address or nickname."
msgstr ""
#: cps/web.py:2306
#: cps/web.py:2323
msgid "Mail settings updated"
msgstr ""
#: cps/web.py:2313
#: cps/web.py:2330
#, python-format
msgid "Test E-Mail successfully send to %(kindlemail)s"
msgstr ""
#: cps/web.py:2316
#: cps/web.py:2333
#, python-format
msgid "There was an error sending the Test E-Mail: %(res)s"
msgstr ""
#: cps/web.py:2320
#: cps/web.py:2337
msgid "E-Mail settings updated"
msgstr ""
#: cps/web.py:2321
#: cps/web.py:2338
msgid "Edit mail settings"
msgstr ""
#: cps/web.py:2349
#: cps/web.py:2367
#, python-format
msgid "User '%(nick)s' deleted"
msgstr ""
#: cps/web.py:2445
#: cps/web.py:2463
#, python-format
msgid "User '%(nick)s' updated"
msgstr ""
#: cps/web.py:2448
#: cps/web.py:2466
msgid "An unknown error occured."
msgstr ""
#: cps/web.py:2451
#: cps/web.py:2469
#, python-format
msgid "Edit User %(nick)s"
msgstr ""
#: cps/web.py:2486 cps/web.py:2490
#: cps/web.py:2504 cps/web.py:2508
msgid "unknown"
msgstr ""
#: cps/web.py:2633 cps/web.py:2636 cps/web.py:2746
#: cps/web.py:2667 cps/web.py:2670 cps/web.py:2780
msgid "edit metadata"
msgstr ""
#: cps/web.py:2657
#: cps/web.py:2691
#, python-format
msgid "File extension \"%s\" is not allowed to be uploaded to this server"
msgstr ""
#: cps/web.py:2663
#: cps/web.py:2697
msgid "File to be uploaded must have an extension"
msgstr ""
#: cps/web.py:2680
#: cps/web.py:2714
#, python-format
msgid "Failed to create path %s (Permission denied)."
msgstr ""
#: cps/web.py:2685
#: cps/web.py:2719
#, python-format
msgid "Failed to store file %s (Permission denied)."
msgstr ""
#: cps/web.py:2690
#: cps/web.py:2724
#, python-format
msgid "Failed to delete file %s (Permission denied)."
msgstr ""
@ -519,7 +538,7 @@ msgid "Ok"
msgstr ""
#: cps/templates/admin.html:103 cps/templates/admin.html:117
#: cps/templates/book_edit.html:115 cps/templates/book_edit.html:137
#: cps/templates/book_edit.html:120 cps/templates/book_edit.html:142
#: cps/templates/config_edit.html:127 cps/templates/email_edit.html:36
#: cps/templates/shelf.html:53 cps/templates/shelf_edit.html:19
#: cps/templates/shelf_order.html:12 cps/templates/user_edit.html:128
@ -542,12 +561,12 @@ msgstr ""
msgid "Book Title"
msgstr ""
#: cps/templates/book_edit.html:26 cps/templates/book_edit.html:176
#: cps/templates/book_edit.html:26 cps/templates/book_edit.html:181
#: cps/templates/search_form.html:10
msgid "Author"
msgstr ""
#: cps/templates/book_edit.html:30 cps/templates/book_edit.html:178
#: cps/templates/book_edit.html:30 cps/templates/book_edit.html:183
msgid "Description"
msgstr ""
@ -584,74 +603,74 @@ msgstr ""
msgid "No"
msgstr ""
#: cps/templates/book_edit.html:110
#: cps/templates/book_edit.html:115
msgid "view book after edit"
msgstr ""
#: cps/templates/book_edit.html:113 cps/templates/book_edit.html:149
#: cps/templates/book_edit.html:118 cps/templates/book_edit.html:154
msgid "Get metadata"
msgstr ""
#: cps/templates/book_edit.html:114 cps/templates/config_edit.html:125
#: cps/templates/book_edit.html:119 cps/templates/config_edit.html:125
#: cps/templates/login.html:19 cps/templates/search_form.html:79
#: cps/templates/shelf_edit.html:17 cps/templates/user_edit.html:126
msgid "Submit"
msgstr ""
#: cps/templates/book_edit.html:128
#: cps/templates/book_edit.html:133
msgid "Are really you sure?"
msgstr ""
#: cps/templates/book_edit.html:131
#: cps/templates/book_edit.html:136
msgid "Book will be deleted from Calibre database"
msgstr ""
#: cps/templates/book_edit.html:132
#: cps/templates/book_edit.html:137
msgid "and from hard disk"
msgstr ""
#: cps/templates/book_edit.html:136
#: cps/templates/book_edit.html:141
msgid "Delete"
msgstr ""
#: cps/templates/book_edit.html:152
#: cps/templates/book_edit.html:157
msgid "Keyword"
msgstr ""
#: cps/templates/book_edit.html:153
#: cps/templates/book_edit.html:158
msgid " Search keyword "
msgstr ""
#: cps/templates/book_edit.html:155 cps/templates/layout.html:60
#: cps/templates/book_edit.html:160 cps/templates/layout.html:60
msgid "Go!"
msgstr ""
#: cps/templates/book_edit.html:156
#: cps/templates/book_edit.html:161
msgid "Click the cover to load metadata to the form"
msgstr ""
#: cps/templates/book_edit.html:160 cps/templates/book_edit.html:173
#: cps/templates/book_edit.html:165 cps/templates/book_edit.html:178
msgid "Loading..."
msgstr ""
#: cps/templates/book_edit.html:163
#: cps/templates/book_edit.html:168
msgid "Close"
msgstr ""
#: cps/templates/book_edit.html:174
#: cps/templates/book_edit.html:179
msgid "Search error!"
msgstr ""
#: cps/templates/book_edit.html:175
#: cps/templates/book_edit.html:180
msgid "No Result! Please try anonther keyword."
msgstr ""
#: cps/templates/book_edit.html:177 cps/templates/detail.html:76
#: cps/templates/book_edit.html:182 cps/templates/detail.html:76
#: cps/templates/search_form.html:14
msgid "Publisher"
msgstr ""
#: cps/templates/book_edit.html:179
#: cps/templates/book_edit.html:184
msgid "Source"
msgstr ""

View File

@ -12,7 +12,7 @@ Calibre Web is a web app providing a clean interface for browsing, reading and d
- full graphical setup
- User management
- Admin interface
- User Interface in english, french, german, polish, russian, simplified chinese, spanish
- User Interface in dutch, english, french, german, polish, russian, simplified chinese, spanish
- OPDS feed for eBook reader apps
- Filter and search by titles, authors, tags, series and language
- Create custom book collection (shelves)