Merge pull request #14 from janeczku/master

merge from janeczku/master
pull/610/head
Ethan Lin 7 years ago committed by GitHub
commit 54d8240dc0

@ -20,11 +20,11 @@ def init_cache_busting(app):
app.logger.debug('Computing cache-busting values...')
# compute file hashes
for dirpath, dirnames, filenames in os.walk(static_folder):
for dirpath, __, filenames in os.walk(static_folder):
for filename in filenames:
# compute version component
rooted_filename = os.path.join(dirpath, filename)
with open(rooted_filename, 'r') as f:
with open(rooted_filename, 'rb') as f:
file_hash = hashlib.md5(f.read()).hexdigest()[:7]
# save version to tables

@ -73,7 +73,8 @@ if not os.path.exists(dbpath):
migrate()
def getDrive(gauth=None):
def getDrive(drive=None, gauth=None):
if not drive:
if not gauth:
gauth = GoogleAuth(settings_file='settings.yaml')
# Try to load saved client credentials
@ -86,13 +87,13 @@ def getDrive(gauth=None):
gauth.Authorize()
# Save the current credentials to a file
return GoogleDrive(gauth)
if drive.auth.access_token_expired:
drive.auth.Refresh()
return drive
def getEbooksFolder(drive=None):
if not drive:
drive = getDrive()
if drive.auth.access_token_expired:
drive.auth.Refresh()
drive = getDrive(drive)
ebooksFolder = "title = '%s' and 'root' in parents and mimeType = 'application/vnd.google-apps.folder' and trashed = false" % config.config_google_drive_folder
fileList = drive.ListFile({'q': ebooksFolder}).GetList()
@ -113,20 +114,14 @@ def getEbooksFolderId(drive=None):
def getFolderInFolder(parentId, folderName, drive=None):
if not drive:
drive = getDrive()
if drive.auth.access_token_expired:
drive.auth.Refresh()
drive = getDrive(drive)
folder = "title = '%s' and '%s' in parents and mimeType = 'application/vnd.google-apps.folder' and trashed = false" % (folderName.replace("'", "\\'"), parentId)
fileList = drive.ListFile({'q': folder}).GetList()
return fileList[0]
def getFile(pathId, fileName, drive=None):
if not drive:
drive = getDrive()
if drive.auth.access_token_expired:
drive.auth.Refresh()
drive = getDrive(drive)
metaDataFile = "'%s' in parents and trashed = false and title = '%s'" % (pathId, fileName.replace("'", "\\'"))
fileList = drive.ListFile({'q': metaDataFile}).GetList()
@ -134,10 +129,7 @@ def getFile(pathId, fileName, drive=None):
def getFolderId(path, drive=None):
if not drive:
drive = getDrive()
if drive.auth.access_token_expired:
drive.auth.Refresh()
drive = getDrive(drive)
currentFolderId = getEbooksFolderId(drive)
sqlCheckPath = path if path[-1] == '/' else path + '/'
storedPathName = session.query(GdriveId).filter(GdriveId.path == sqlCheckPath).first()
@ -168,10 +160,7 @@ def getFolderId(path, drive=None):
def getFileFromEbooksFolder(drive, path, fileName):
if not drive:
drive = getDrive()
if drive.auth.access_token_expired:
drive.auth.Refresh()
drive = getDrive(drive)
if path:
# sqlCheckPath=path if path[-1] =='/' else path + '/'
folderId = getFolderId(path, drive)
@ -182,10 +171,7 @@ def getFileFromEbooksFolder(drive, path, fileName):
def copyDriveFileRemote(drive, origin_file_id, copy_title):
if not drive:
drive = getDrive()
if drive.auth.access_token_expired:
drive.auth.Refresh()
drive = getDrive(drive)
copied_file = {'title': copy_title}
try:
file_data = drive.auth.service.files().copy(
@ -197,19 +183,13 @@ def copyDriveFileRemote(drive, origin_file_id, copy_title):
def downloadFile(drive, path, filename, output):
if not drive:
drive = getDrive()
if drive.auth.access_token_expired:
drive.auth.Refresh()
drive = getDrive(drive)
f = getFileFromEbooksFolder(drive, path, filename)
f.GetContentFile(output)
def backupCalibreDbAndOptionalDownload(drive, f=None):
if not drive:
drive = getDrive()
if drive.auth.access_token_expired:
drive.auth.Refresh()
drive = getDrive(drive)
metaDataFile = "'%s' in parents and title = 'metadata.db' and trashed = false" % getEbooksFolderId()
fileList = drive.ListFile({'q': metaDataFile}).GetList()
@ -221,12 +201,10 @@ def backupCalibreDbAndOptionalDownload(drive, f=None):
def copyToDrive(drive, uploadFile, createRoot, replaceFiles,
ignoreFiles=[],
ignoreFiles=None,
parent=None, prevDir=''):
if not drive:
drive = getDrive()
if drive.auth.access_token_expired:
drive.auth.Refresh()
ignoreFiles = ignoreFiles or []
drive = getDrive(drive)
isInitial = not bool(parent)
if not parent:
parent = getEbooksFolder(drive)
@ -254,10 +232,7 @@ def copyToDrive(drive, uploadFile, createRoot, replaceFiles,
def uploadFileToEbooksFolder(drive, destFile, f):
if not drive:
drive = getDrive()
if drive.auth.access_token_expired:
drive.auth.Refresh()
drive = getDrive(drive)
parent = getEbooksFolder(drive)
splitDir = destFile.split('/')
for i, x in enumerate(splitDir):
@ -281,10 +256,7 @@ def uploadFileToEbooksFolder(drive, destFile, f):
def watchChange(drive, channel_id, channel_type, channel_address,
channel_token=None, expiration=None):
if not drive:
drive = getDrive()
if drive.auth.access_token_expired:
drive.auth.Refresh()
drive = getDrive(drive)
# Watch for all changes to a user's Drive.
# Args:
# service: Drive API service instance.
@ -327,10 +299,7 @@ def watchFile(drive, file_id, channel_id, channel_type, channel_address,
Raises:
apiclient.errors.HttpError: if http request to create channel fails.
"""
if not drive:
drive = getDrive()
if drive.auth.access_token_expired:
drive.auth.Refresh()
drive = getDrive(drive)
body = {
'id': channel_id,
@ -353,10 +322,7 @@ def stopChannel(drive, channel_id, resource_id):
Raises:
apiclient.errors.HttpError: if http request to create channel fails.
"""
if not drive:
drive = getDrive()
if drive.auth.access_token_expired:
drive.auth.Refresh()
drive = getDrive(drive)
# service=drive.auth.service
body = {
'id': channel_id,
@ -366,10 +332,7 @@ def stopChannel(drive, channel_id, resource_id):
def getChangeById (drive, change_id):
if not drive:
drive = getDrive()
if drive.auth.access_token_expired:
drive.auth.Refresh()
drive = getDrive(drive)
# Print a single Change resource information.
#
# Args:

@ -266,6 +266,7 @@ def get_valid_filename(value, replace_whitespace=True):
"""
if value[-1:] == u'.':
value = value[:-1]+u'_'
value = value.replace("/", "_").replace(":", "_").strip('\0')
if use_unidecode:
value=(unidecode.unidecode(value)).strip()
else:

@ -0,0 +1,105 @@
body {
background: #444;
overflow: hidden;
color: white;
font-family: sans-serif;
margin: 0px;
}
.main {
position: re;
left: 5px;
overflow: hidden;
right: 5px;
text-align: center;
top: 5px;
}
#progress {
position: absolute;
display: inline;
left: 90px;
right: 160px;
height: 20px;
margin-top: 1px;
text-align: right;
}
.hide {
display: none !important;
}
#mainText {
text-align: left;
width: 90%;
position: relative;
top: 10px;
background: #ccc;
color: black;
margin-right: auto;
margin-left: auto;
padding: 10px;
word-wrap: break-word;
}
#mainImage{
margin-top: 32px;
}
#titlebar.main {
opacity: 0;
position: absolute;
top: 0;
height: 30px;
left: 0;
right: 0;
background-color: black;
padding-bottom: 70px;
-webkit-transition: opacity 0.2s ease;
-moz-transition: opacity 0.2s ease;
transition: opacity 0.2s ease;
background: -moz-linear-gradient(top, rgba(0,2,34,1) 0%, rgba(0,1,24,1) 30%, rgba(0,0,0,0) 100%); /* FF3.6+ */
background: -webkit-gradient(linear, left top, left bottom, color-stop(0%,rgba(0,2,34,1)), color-stop(30%,rgba(0,1,24,1)), color-stop(100%,rgba(0,0,0,0))); /* Chrome,Safari4+ */
background: -webkit-linear-gradient(top, rgba(0,2,34,1) 0%,rgba(0,1,24,1) 30%,rgba(0,0,0,0) 100%); /* Chrome10+,Safari5.1+ */
background: -o-linear-gradient(top, rgba(0,2,34,1) 0%,rgba(0,1,24,1) 30%,rgba(0,0,0,0) 100%); /* Opera11.10+ */
background: -ms-linear-gradient(top, rgba(0,2,34,1) 0%,rgba(0,1,24,1) 30%,rgba(0,0,0,0) 100%); /* IE10+ */
filter: progid:DXImageTransform.Microsoft.gradient( startColorstr='#000222', endColorstr='#00000000',GradientType=0 ); /* IE6-9 */
background: linear-gradient(top, rgba(0,2,34,1) 0%,rgba(0,1,24,1) 30%,rgba(0,0,0,0) 100%); /* W3C */
}
#prev {
left: 40px;
}
#next {
right: 40px;
}
.arrow {
position: absolute;
top: 50%;
margin-top: -32px;
font-size: 64px;
color: #E2E2E2;
font-family: arial, sans-serif;
font-weight: bold;
cursor: pointer;
-webkit-user-select: none;
-khtml-user-select: none;
-moz-user-select: none;
-ms-user-select: none;
user-select: none;
}
.arrow:hover {
color: #777;
}
.arrow:active,
.arrow.active {
color: #000;
}

@ -90,3 +90,9 @@ input.pill:not(:checked) + label .glyphicon {
#meta-info img { max-height: 150px; max-width: 100px; cursor: pointer; }
.padded-bottom { margin-bottom: 15px; }
.upload-format-input-text {display: initial;}
#btn-upload-format {display: none;}
.upload-format-input-text {display: initial;}
#btn-upload-format {display: none;}

@ -1 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="673.826" height="144" viewBox="0 0 673.826 144"><g fill="#5A481C"><path d="M66.66 86.444h-.315c-3.34 14.507-18.19 22.964-32.213 22.964C11.33 109.408 0 91.212 0 70.163c0-22.008 12.146-40.18 35.245-40.18 15.643 0 27.917 10.368 31.1 23.76h.315v-21.85h3.194v79.26C69.854 133.474 57.09 144 35.71 144c-16.576 0-30.78-7.49-31.257-25.843h3.205c.642 16.273 13.08 22.65 27.896 22.65 19.787 0 31.106-9.402 31.106-29.656V86.445zM35.245 33.176c-21.215 0-32.062 17.06-32.062 36.987 0 20.25 10.846 36.062 30.768 36.062 21.065 0 32.558-16.295 32.558-36.062.152-18.825-10.678-36.987-31.263-36.987zM115.787 29.982c23.926 0 36.825 20.58 36.825 42.897 0 22.482-12.898 42.894-36.987 42.894-23.915 0-36.853-20.41-36.853-42.895 0-22.318 12.938-42.898 37.015-42.898zm0 82.598c21.828 0 33.642-18.972 33.642-39.7 0-20.418-11.815-39.704-33.643-39.704-22.176 0-33.81 19.287-33.81 39.703 0 20.728 11.634 39.7 33.81 39.7zM194.57 29.982c23.908 0 36.824 20.58 36.824 42.897 0 22.482-12.916 42.894-36.987 42.894-23.925 0-36.84-20.41-36.84-42.895-.002-22.318 12.914-42.898 37.003-42.898zm0 82.598c21.856 0 33.643-18.972 33.643-39.7 0-20.418-11.786-39.704-33.643-39.704-22.17 0-33.822 19.287-33.822 39.703 0 20.728 11.65 39.7 33.822 39.7zM304.436 0h3.194v113.86h-3.194V90.91h-.326c-4.14 14.337-16.082 24.863-32.837 24.863-21.7 0-34.942-18.027-34.942-42.73 0-22.97 12.3-43.06 34.943-43.06 17.386 0 29.02 10.053 32.837 24.87h.326V0zm-33.163 33.176c-22.493 0-31.736 20.883-31.736 39.866 0 21.04 10.526 39.538 31.736 39.538 21.052 0 33.163-18.32 33.163-39.538 0-25.36-13.236-39.866-33.163-39.866zM323.093 31.58h9.25v19.286h.327c5.103-13.248 16.253-21.052 31.098-20.4v10.042c-18.196-.967-30.62 12.427-30.62 29.492v43.86h-10.054V31.58zM372.38 75.426c.147 14.684 7.806 32.363 27.092 32.363 14.688 0 22.65-8.604 25.832-21.03h10.064c-4.308 18.656-15.16 29.486-35.896 29.486-26.124 0-37.146-20.097-37.146-43.52 0-21.693 11.02-43.543 37.146-43.543 26.483 0 37.032 23.132 36.21 46.243h-63.3zm53.25-8.446c-.462-15.148-9.886-29.363-26.158-29.363-16.42 0-25.495 14.372-27.09 29.363h53.248zM444.297 56.775c.945-19.293 14.508-27.592 33.333-27.592 14.54 0 30.342 4.47 30.342 26.46v43.71c0 3.836 1.923 6.063 5.915 6.063 1.113 0 2.36-.326 3.183-.64v8.445c-2.238.484-3.835.642-6.557.642-10.2 0-11.82-5.735-11.82-14.36h-.28c-7.04 10.683-14.226 16.745-30.05 16.745-15.124 0-27.573-7.467-27.573-24.078 0-23.12 22.48-23.913 44.185-26.46 8.31-.978 12.933-2.09 12.933-11.172 0-13.557-9.728-16.92-21.56-16.92-12.436 0-21.67 5.76-22.03 19.16H444.3zm53.61 12.106h-.314c-1.27 2.397-5.758 3.207-8.457 3.68-17.082 3.024-38.292 2.867-38.292 18.968 0 10.054 8.93 16.262 18.342 16.262 15.317 0 28.89-9.716 28.722-25.82V68.88zM596.488 113.86h-9.232V98.24h-.326c-4.308 10.685-17.386 18.006-29.34 18.006-25.068 0-37.01-20.23-37.01-43.52 0-23.29 11.94-43.543 37.01-43.543 12.27 0 24.223 6.22 28.52 18.016h.348V0h10.03v113.86zm-38.9-6.07c21.356 0 28.868-18.017 28.868-35.063 0-17.083-7.512-35.11-28.868-35.11-19.14 0-26.956 18.027-26.956 35.11 0 17.046 7.817 35.062 26.956 35.062zM660.926 55.645c-.494-12.438-10.043-18.027-21.535-18.027-8.94 0-19.443 3.52-19.443 14.215 0 8.918 10.188 12.112 17.06 13.877l13.395 3.014c11.47 1.76 23.425 8.457 23.425 22.804 0 17.88-17.667 24.72-33.007 24.72-19.14 0-32.208-8.93-33.805-29.016h10.03c.82 13.54 10.864 20.558 24.27 20.558 9.38 0 22.47-4.14 22.47-15.62 0-9.56-8.92-12.745-18.005-14.99l-12.933-2.855c-13.068-3.51-22.976-7.984-22.976-22.008 0-16.745 16.43-23.132 30.95-23.132 16.418 0 29.52 8.625 30.14 26.46h-10.034z"/></g></svg>
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 673.826 144"><g fill="#5A481C"><path d="M66.66 86.444h-.315c-3.34 14.507-18.19 22.964-32.213 22.964C11.33 109.408 0 91.212 0 70.163c0-22.008 12.146-40.18 35.245-40.18 15.643 0 27.917 10.368 31.1 23.76h.315v-21.85h3.194v79.26C69.854 133.474 57.09 144 35.71 144c-16.576 0-30.78-7.49-31.257-25.843h3.205c.642 16.273 13.08 22.65 27.896 22.65 19.787 0 31.106-9.402 31.106-29.656V86.445zM35.245 33.176c-21.215 0-32.062 17.06-32.062 36.987 0 20.25 10.846 36.062 30.768 36.062 21.065 0 32.558-16.295 32.558-36.062.152-18.825-10.678-36.987-31.263-36.987zM115.787 29.982c23.926 0 36.825 20.58 36.825 42.897 0 22.482-12.898 42.894-36.987 42.894-23.915 0-36.853-20.41-36.853-42.895 0-22.318 12.938-42.898 37.015-42.898zm0 82.598c21.828 0 33.642-18.972 33.642-39.7 0-20.418-11.815-39.704-33.643-39.704-22.176 0-33.81 19.287-33.81 39.703 0 20.728 11.634 39.7 33.81 39.7zM194.57 29.982c23.908 0 36.824 20.58 36.824 42.897 0 22.482-12.916 42.894-36.987 42.894-23.925 0-36.84-20.41-36.84-42.895-.002-22.318 12.914-42.898 37.003-42.898zm0 82.598c21.856 0 33.643-18.972 33.643-39.7 0-20.418-11.786-39.704-33.643-39.704-22.17 0-33.822 19.287-33.822 39.703 0 20.728 11.65 39.7 33.822 39.7zM304.436 0h3.194v113.86h-3.194V90.91h-.326c-4.14 14.337-16.082 24.863-32.837 24.863-21.7 0-34.942-18.027-34.942-42.73 0-22.97 12.3-43.06 34.943-43.06 17.386 0 29.02 10.053 32.837 24.87h.326V0zm-33.163 33.176c-22.493 0-31.736 20.883-31.736 39.866 0 21.04 10.526 39.538 31.736 39.538 21.052 0 33.163-18.32 33.163-39.538 0-25.36-13.236-39.866-33.163-39.866zM323.093 31.58h9.25v19.286h.327c5.103-13.248 16.253-21.052 31.098-20.4v10.042c-18.196-.967-30.62 12.427-30.62 29.492v43.86h-10.054V31.58zM372.38 75.426c.147 14.684 7.806 32.363 27.092 32.363 14.688 0 22.65-8.604 25.832-21.03h10.064c-4.308 18.656-15.16 29.486-35.896 29.486-26.124 0-37.146-20.097-37.146-43.52 0-21.693 11.02-43.543 37.146-43.543 26.483 0 37.032 23.132 36.21 46.243h-63.3zm53.25-8.446c-.462-15.148-9.886-29.363-26.158-29.363-16.42 0-25.495 14.372-27.09 29.363h53.248zM444.297 56.775c.945-19.293 14.508-27.592 33.333-27.592 14.54 0 30.342 4.47 30.342 26.46v43.71c0 3.836 1.923 6.063 5.915 6.063 1.113 0 2.36-.326 3.183-.64v8.445c-2.238.484-3.835.642-6.557.642-10.2 0-11.82-5.735-11.82-14.36h-.28c-7.04 10.683-14.226 16.745-30.05 16.745-15.124 0-27.573-7.467-27.573-24.078 0-23.12 22.48-23.913 44.185-26.46 8.31-.978 12.933-2.09 12.933-11.172 0-13.557-9.728-16.92-21.56-16.92-12.436 0-21.67 5.76-22.03 19.16H444.3zm53.61 12.106h-.314c-1.27 2.397-5.758 3.207-8.457 3.68-17.082 3.024-38.292 2.867-38.292 18.968 0 10.054 8.93 16.262 18.342 16.262 15.317 0 28.89-9.716 28.722-25.82V68.88zM596.488 113.86h-9.232V98.24h-.326c-4.308 10.685-17.386 18.006-29.34 18.006-25.068 0-37.01-20.23-37.01-43.52 0-23.29 11.94-43.543 37.01-43.543 12.27 0 24.223 6.22 28.52 18.016h.348V0h10.03v113.86zm-38.9-6.07c21.356 0 28.868-18.017 28.868-35.063 0-17.083-7.512-35.11-28.868-35.11-19.14 0-26.956 18.027-26.956 35.11 0 17.046 7.817 35.062 26.956 35.062zM660.926 55.645c-.494-12.438-10.043-18.027-21.535-18.027-8.94 0-19.443 3.52-19.443 14.215 0 8.918 10.188 12.112 17.06 13.877l13.395 3.014c11.47 1.76 23.425 8.457 23.425 22.804 0 17.88-17.667 24.72-33.007 24.72-19.14 0-32.208-8.93-33.805-29.016h10.03c.82 13.54 10.864 20.558 24.27 20.558 9.38 0 22.47-4.14 22.47-15.62 0-9.56-8.92-12.745-18.005-14.99l-12.933-2.855c-13.068-3.51-22.976-7.984-22.976-22.008 0-16.745 16.43-23.132 30.95-23.132 16.418 0 29.52 8.625 30.14 26.46h-10.034z"/></g></svg>

Before

Width:  |  Height:  |  Size: 3.5 KiB

After

Width:  |  Height:  |  Size: 3.4 KiB

@ -0,0 +1,364 @@
/**
* archive.js
*
* Provides base functionality for unarchiving.
*
* Licensed under the MIT License
*
* Copyright(c) 2011 Google Inc.
*/
/* global bitjs */
var bitjs = bitjs || {};
bitjs.archive = bitjs.archive || {};
(function() {
// ===========================================================================
// Stolen from Closure because it's the best way to do Java-like inheritance.
bitjs.base = function(me, optMethodName, varArgs) {
var caller = arguments.callee.caller;
if (caller.superClass_) {
// This is a constructor. Call the superclass constructor.
return caller.superClass_.constructor.apply(
me, Array.prototype.slice.call(arguments, 1));
}
var args = Array.prototype.slice.call(arguments, 2);
var foundCaller = false;
for (var ctor = me.constructor;
ctor; ctor = ctor.superClass_ && ctor.superClass_.constructor) {
if (ctor.prototype[optMethodName] === caller) {
foundCaller = true;
} else if (foundCaller) {
return ctor.prototype[optMethodName].apply(me, args);
}
}
// If we did not find the caller in the prototype chain,
// then one of two things happened:
// 1) The caller is an instance method.
// 2) This method was not called by the right caller.
if (me[optMethodName] === caller) {
return me.constructor.prototype[optMethodName].apply(me, args);
} else {
throw Error(
"goog.base called from a method of one name " +
"to a method of a different name");
}
};
bitjs.inherits = function(childCtor, parentCtor) {
/** @constructor */
function TempCtor() {}
TempCtor.prototype = parentCtor.prototype;
childCtor.superClass_ = parentCtor.prototype;
childCtor.prototype = new TempCtor();
childCtor.prototype.constructor = childCtor;
};
// ===========================================================================
/**
* An unarchive event.
*
* @param {string} type The event type.
* @constructor
*/
bitjs.archive.UnarchiveEvent = function(type) {
/**
* The event type.
*
* @type {string}
*/
this.type = type;
};
/**
* The UnarchiveEvent types.
*/
bitjs.archive.UnarchiveEvent.Type = {
START: "start",
PROGRESS: "progress",
EXTRACT: "extract",
FINISH: "finish",
INFO: "info",
ERROR: "error"
};
/**
* Useful for passing info up to the client (for debugging).
*
* @param {string} msg The info message.
*/
bitjs.archive.UnarchiveInfoEvent = function(msg) {
bitjs.base(this, bitjs.archive.UnarchiveEvent.Type.INFO);
/**
* The information message.
*
* @type {string}
*/
this.msg = msg;
};
bitjs.inherits(bitjs.archive.UnarchiveInfoEvent, bitjs.archive.UnarchiveEvent);
/**
* An unrecoverable error has occured.
*
* @param {string} msg The error message.
*/
bitjs.archive.UnarchiveErrorEvent = function(msg) {
bitjs.base(this, bitjs.archive.UnarchiveEvent.Type.ERROR);
/**
* The information message.
*
* @type {string}
*/
this.msg = msg;
};
bitjs.inherits(bitjs.archive.UnarchiveErrorEvent, bitjs.archive.UnarchiveEvent);
/**
* Start event.
*
* @param {string} msg The info message.
*/
bitjs.archive.UnarchiveStartEvent = function() {
bitjs.base(this, bitjs.archive.UnarchiveEvent.Type.START);
};
bitjs.inherits(bitjs.archive.UnarchiveStartEvent, bitjs.archive.UnarchiveEvent);
/**
* Finish event.
*
* @param {string} msg The info message.
*/
bitjs.archive.UnarchiveFinishEvent = function() {
bitjs.base(this, bitjs.archive.UnarchiveEvent.Type.FINISH);
};
bitjs.inherits(bitjs.archive.UnarchiveFinishEvent, bitjs.archive.UnarchiveEvent);
/**
* Progress event.
*/
bitjs.archive.UnarchiveProgressEvent = function(
currentFilename,
currentFileNumber,
currentBytesUnarchivedInFile,
currentBytesUnarchived,
totalUncompressedBytesInArchive,
totalFilesInArchive)
{
bitjs.base(this, bitjs.archive.UnarchiveEvent.Type.PROGRESS);
this.currentFilename = currentFilename;
this.currentFileNumber = currentFileNumber;
this.currentBytesUnarchivedInFile = currentBytesUnarchivedInFile;
this.totalFilesInArchive = totalFilesInArchive;
this.currentBytesUnarchived = currentBytesUnarchived;
this.totalUncompressedBytesInArchive = totalUncompressedBytesInArchive;
};
bitjs.inherits(bitjs.archive.UnarchiveProgressEvent, bitjs.archive.UnarchiveEvent);
/**
* All extracted files returned by an Unarchiver will implement
* the following interface:
*
* interface UnarchivedFile {
* string filename
* TypedArray fileData
* }
*
*/
/**
* Extract event.
*/
bitjs.archive.UnarchiveExtractEvent = function(unarchivedFile) {
bitjs.base(this, bitjs.archive.UnarchiveEvent.Type.EXTRACT);
/**
* @type {UnarchivedFile}
*/
this.unarchivedFile = unarchivedFile;
};
bitjs.inherits(bitjs.archive.UnarchiveExtractEvent, bitjs.archive.UnarchiveEvent);
/**
* Base class for all Unarchivers.
*
* @param {ArrayBuffer} arrayBuffer The Array Buffer.
* @param {string} optPathToBitJS Optional string for where the BitJS files are located.
* @constructor
*/
bitjs.archive.Unarchiver = function(arrayBuffer, optPathToBitJS) {
/**
* The ArrayBuffer object.
* @type {ArrayBuffer}
* @protected
*/
this.ab = arrayBuffer;
/**
* The path to the BitJS files.
* @type {string}
* @private
*/
this.pathToBitJS_ = optPathToBitJS || "";
/**
* A map from event type to an array of listeners.
* @type {Map.<string, Array>}
*/
this.listeners_ = {};
for (var type in bitjs.archive.UnarchiveEvent.Type) {
this.listeners_[bitjs.archive.UnarchiveEvent.Type[type]] = [];
}
};
/**
* Private web worker initialized during start().
* @type {Worker}
* @private
*/
bitjs.archive.Unarchiver.prototype.worker_ = null;
/**
* This method must be overridden by the subclass to return the script filename.
* @return {string} The script filename.
* @protected.
*/
bitjs.archive.Unarchiver.prototype.getScriptFileName = function() {
throw "Subclasses of AbstractUnarchiver must overload getScriptFileName()";
};
/**
* Adds an event listener for UnarchiveEvents.
*
* @param {string} Event type.
* @param {function} An event handler function.
*/
bitjs.archive.Unarchiver.prototype.addEventListener = function(type, listener) {
if (type in this.listeners_) {
if (this.listeners_[type].indexOf(listener) === -1) {
this.listeners_[type].push(listener);
}
}
};
/**
* Removes an event listener.
*
* @param {string} Event type.
* @param {EventListener|function} An event listener or handler function.
*/
bitjs.archive.Unarchiver.prototype.removeEventListener = function(type, listener) {
if (type in this.listeners_) {
var index = this.listeners_[type].indexOf(listener);
if (index !== -1) {
this.listeners_[type].splice(index, 1);
}
}
};
/**
* Receive an event and pass it to the listener functions.
*
* @param {bitjs.archive.UnarchiveEvent} e
* @private
*/
bitjs.archive.Unarchiver.prototype.handleWorkerEvent_ = function(e) {
if ((e instanceof bitjs.archive.UnarchiveEvent || e.type) &&
this.listeners_[e.type] instanceof Array) {
this.listeners_[e.type].forEach(function (listener) {
listener(e);
});
if (e.type === bitjs.archive.UnarchiveEvent.Type.FINISH) {
this.worker_.terminate();
}
} else {
console.log(e);
}
};
/**
* Starts the unarchive in a separate Web Worker thread and returns immediately.
*/
bitjs.archive.Unarchiver.prototype.start = function() {
var me = this;
var scriptFileName = this.pathToBitJS_ + this.getScriptFileName();
if (scriptFileName) {
this.worker_ = new Worker(scriptFileName);
this.worker_.onerror = function(e) {
console.log("Worker error: message = " + e.message);
throw e;
};
this.worker_.onmessage = function(e) {
if (typeof e.data === "string") {
// Just log any strings the workers pump our way.
console.log(e.data);
} else {
// Assume that it is an UnarchiveEvent. Some browsers preserve the 'type'
// so that instanceof UnarchiveEvent returns true, but others do not.
me.handleWorkerEvent_(e.data);
}
};
this.worker_.postMessage({file: this.ab});
}
};
/**
* Terminates the Web Worker for this Unarchiver and returns immediately.
*/
bitjs.archive.Unarchiver.prototype.stop = function() {
if (this.worker_) {
this.worker_.terminate();
}
};
/**
* Unzipper
* @extends {bitjs.archive.Unarchiver}
* @constructor
*/
bitjs.archive.Unzipper = function(arrayBuffer, optPathToBitJS) {
bitjs.base(this, arrayBuffer, optPathToBitJS);
};
bitjs.inherits(bitjs.archive.Unzipper, bitjs.archive.Unarchiver);
bitjs.archive.Unzipper.prototype.getScriptFileName = function() {
return "unzip.js";
};
/**
* Unrarrer
* @extends {bitjs.archive.Unarchiver}
* @constructor
*/
bitjs.archive.Unrarrer = function(arrayBuffer, optPathToBitJS) {
bitjs.base(this, arrayBuffer, optPathToBitJS);
};
bitjs.inherits(bitjs.archive.Unrarrer, bitjs.archive.Unarchiver);
bitjs.archive.Unrarrer.prototype.getScriptFileName = function() {
return "unrar.js";
};
/**
* Untarrer
* @extends {bitjs.archive.Unarchiver}
* @constructor
*/
bitjs.archive.Untarrer = function(arrayBuffer, optPathToBitJS) {
bitjs.base(this, arrayBuffer, optPathToBitJS);
};
bitjs.inherits(bitjs.archive.Untarrer, bitjs.archive.Unarchiver);
bitjs.archive.Untarrer.prototype.getScriptFileName = function() {
return "untar.js";
};
})();

@ -4,31 +4,31 @@
/* global Bloodhound, language, Modernizr, tinymce */
if ($("#description").length) {
tinymce.init({
tinymce.init({
selector: "#description",
branding: false,
menubar: "edit view format",
language
});
language: language
});
if (!Modernizr.inputtypes.date) {
if (!Modernizr.inputtypes.date) {
$("#pubdate").datepicker({
format: "yyyy-mm-dd",
language
language: language
}).on("change", function () {
// Show localized date over top of the standard YYYY-MM-DD date
let pubDate;
const results = /(\d{4})[-\/\\](\d{1,2})[-\/\\](\d{1,2})/.exec(this.value); // YYYY-MM-DD
var pubDate;
var results = /(\d{4})[-\/\\](\d{1,2})[-\/\\](\d{1,2})/.exec(this.value); // YYYY-MM-DD
if (results) {
pubDate = new Date(results[1], parseInt(results[2], 10)-1, results[3]) || new Date(this.value);
}
pubDate = new Date(results[1], parseInt(results[2], 10) - 1, results[3]) || new Date(this.value);
$("#fake_pubdate")
.val(pubDate.toLocaleDateString(language))
.removeClass("hidden");
}
}).trigger("change");
}
}
}
/*
Takes a prefix, query typeahead callback, Bloodhound typeahead adapter
and returns the completions it gets from the bloodhound engine prefixed.
@ -43,6 +43,7 @@ function prefixedSource(prefix, query, cb, bhAdapter) {
cb(matches);
});
}
function getPath() {
var jsFileLocation = $("script[src*=edit_books]").attr("src"); // the js file path
jsFileLocation = jsFileLocation.replace("/static/js/edit_books.js", ""); // the js folder path
@ -56,7 +57,7 @@ var authors = new Bloodhound({
},
queryTokenizer: Bloodhound.tokenizers.whitespace,
remote: {
url: getPath()+"/get_authors_json?q=%QUERY"
url: getPath() + "/get_authors_json?q=%QUERY"
}
});
@ -69,9 +70,9 @@ var series = new Bloodhound({
return [query];
},
remote: {
url: getPath()+"/get_series_json?q=",
url: getPath() + "/get_series_json?q=",
replace: function replace(url, query) {
return url+encodeURIComponent(query);
return url + encodeURIComponent(query);
}
}
});
@ -84,11 +85,11 @@ var tags = new Bloodhound({
},
queryTokenizer: function queryTokenizer(query) {
var tokens = query.split(",");
tokens = [tokens[tokens.length-1].trim()];
tokens = [tokens[tokens.length - 1].trim()];
return tokens;
},
remote: {
url: getPath()+"/get_tags_json?q=%QUERY"
url: getPath() + "/get_tags_json?q=%QUERY"
}
});
@ -101,9 +102,9 @@ var languages = new Bloodhound({
return [query];
},
remote: {
url: getPath()+"/get_languages_json?q=",
url: getPath() + "/get_languages_json?q=",
replace: function replace(url, query) {
return url+encodeURIComponent(query);
return url + encodeURIComponent(query);
}
}
});
@ -112,9 +113,9 @@ function sourceSplit(query, cb, split, source) {
var bhAdapter = source.ttAdapter();
var tokens = query.split(split);
var currentSource = tokens[tokens.length-1].trim();
var currentSource = tokens[tokens.length - 1].trim();
tokens.splice(tokens.length-1, 1); // remove last element
tokens.splice(tokens.length - 1, 1); // remove last element
var prefix = "";
var newSplit;
if (split === "&") {
@ -192,7 +193,7 @@ promiseLanguages.done(function() {
$("#search").on("change input.typeahead:selected", function() {
var form = $("form").serialize();
$.getJSON( getPath()+"/get_matching_tags", form, function( data ) {
$.getJSON( getPath() + "/get_matching_tags", form, function( data ) {
$(".tags_click").each(function() {
if ($.inArray(parseInt($(this).children("input").first().val(), 10), data.tags) === -1 ) {
if (!($(this).hasClass("active"))) {
@ -204,3 +205,11 @@ $("#search").on("change input.typeahead:selected", function() {
});
});
});
$("#btn-upload-format").on("change", function () {
var filename = $(this).val();
if (filename.substring(3, 11) === "fakepath") {
filename = filename.substring(12);
} // Remove c:\fake at beginning from localhost chrome
$("#upload-format").html(filename);
});

@ -3,8 +3,8 @@
* Created by idalin<dalin.lin@gmail.com>
* Google Books api document: https://developers.google.com/books/docs/v1/using
* Douban Books api document: https://developers.douban.com/wiki/?title=book_v2 (Chinese Only)
*/
/* global _, i18nMsg, tinymce */
*/
/* global _, i18nMsg, tinymce */
var dbResults = [];
var ggResults = [];
@ -103,6 +103,10 @@ $(function () {
}
};
if (book.rating > 0) {
book.rating /= 2;
}
var $book = $(templates.bookResult(book));
$book.find("img").on("click", function () {
populateForm(book);

@ -0,0 +1,484 @@
/*
* io.js
*
* Provides readers for bit/byte streams (reading) and a byte buffer (writing).
*
* Licensed under the MIT License
*
* Copyright(c) 2011 Google Inc.
* Copyright(c) 2011 antimatter15
*/
/* global bitjs, Uint8Array */
var bitjs = bitjs || {};
bitjs.io = bitjs.io || {};
(function() {
// mask for getting the Nth bit (zero-based)
bitjs.BIT = [ 0x01, 0x02, 0x04, 0x08,
0x10, 0x20, 0x40, 0x80,
0x100, 0x200, 0x400, 0x800,
0x1000, 0x2000, 0x4000, 0x8000];
// mask for getting N number of bits (0-8)
var BITMASK = [0, 0x01, 0x03, 0x07, 0x0F, 0x1F, 0x3F, 0x7F, 0xFF ];
/**
* This bit stream peeks and consumes bits out of a binary stream.
*
* @param {ArrayBuffer} ab An ArrayBuffer object or a Uint8Array.
* @param {boolean} rtl Whether the stream reads bits from the byte starting
* from bit 7 to 0 (true) or bit 0 to 7 (false).
* @param {Number} optOffset The offset into the ArrayBuffer
* @param {Number} optLength The length of this BitStream
*/
bitjs.io.BitStream = function(ab, rtl, optOffset, optLength) {
if (!ab || !ab.toString || ab.toString() !== "[object ArrayBuffer]") {
throw "Error! BitArray constructed with an invalid ArrayBuffer object";
}
var offset = optOffset || 0;
var length = optLength || ab.byteLength;
this.bytes = new Uint8Array(ab, offset, length);
this.bytePtr = 0; // tracks which byte we are on
this.bitPtr = 0; // tracks which bit we are on (can have values 0 through 7)
this.peekBits = rtl ? this.peekBitsRtl : this.peekBitsLtr;
};
/**
* byte0 byte1 byte2 byte3
* 7......0 | 7......0 | 7......0 | 7......0
*
* The bit pointer starts at bit0 of byte0 and moves left until it reaches
* bit7 of byte0, then jumps to bit0 of byte1, etc.
* @param {number} n The number of bits to peek.
* @param {boolean=} movePointers Whether to move the pointer, defaults false.
* @return {number} The peeked bits, as an unsigned number.
*/
bitjs.io.BitStream.prototype.peekBitsLtr = function(n, movePointers) {
if (n <= 0 || typeof n !== typeof 1) {
return 0;
}
var movePointers = movePointers || false;
var bytePtr = this.bytePtr;
var bitPtr = this.bitPtr;
var result = 0;
var bitsIn = 0;
var bytes = this.bytes;
// keep going until we have no more bits left to peek at
// TODO: Consider putting all bits from bytes we will need into a variable and then
// shifting/masking it to just extract the bits we want.
// This could be considerably faster when reading more than 3 or 4 bits at a time.
while (n > 0) {
if (bytePtr >= bytes.length) {
throw "Error! Overflowed the bit stream! n=" + n + ", bytePtr=" + bytePtr + ", bytes.length=" +
bytes.length + ", bitPtr=" + bitPtr;
}
var numBitsLeftInThisByte = (8 - bitPtr);
var mask;
if (n >= numBitsLeftInThisByte) {
mask = (BITMASK[numBitsLeftInThisByte] << bitPtr);
result |= (((bytes[bytePtr] & mask) >> bitPtr) << bitsIn);
bytePtr++;
bitPtr = 0;
bitsIn += numBitsLeftInThisByte;
n -= numBitsLeftInThisByte;
} else {
mask = (BITMASK[n] << bitPtr);
result |= (((bytes[bytePtr] & mask) >> bitPtr) << bitsIn);
bitPtr += n;
bitsIn += n;
n = 0;
}
}
if (movePointers) {
this.bitPtr = bitPtr;
this.bytePtr = bytePtr;
}
return result;
};
/**
* byte0 byte1 byte2 byte3
* 7......0 | 7......0 | 7......0 | 7......0
*
* The bit pointer starts at bit7 of byte0 and moves right until it reaches
* bit0 of byte0, then goes to bit7 of byte1, etc.
* @param {number} n The number of bits to peek.
* @param {boolean=} movePointers Whether to move the pointer, defaults false.
* @return {number} The peeked bits, as an unsigned number.
*/
bitjs.io.BitStream.prototype.peekBitsRtl = function(n, movePointers) {
if (n <= 0 || typeof n != typeof 1) {
return 0;
}
var movePointers = movePointers || false;
var bytePtr = this.bytePtr;
var bitPtr = this.bitPtr;
var result = 0;
var bytes = this.bytes;
// keep going until we have no more bits left to peek at
// TODO: Consider putting all bits from bytes we will need into a variable and then
// shifting/masking it to just extract the bits we want.
// This could be considerably faster when reading more than 3 or 4 bits at a time.
while (n > 0) {
if (bytePtr >= bytes.length) {
throw "Error! Overflowed the bit stream! n=" + n + ", bytePtr=" + bytePtr + ", bytes.length=" +
bytes.length + ", bitPtr=" + bitPtr;
// return -1;
}
var numBitsLeftInThisByte = (8 - bitPtr);
if (n >= numBitsLeftInThisByte) {
result <<= numBitsLeftInThisByte;
result |= (BITMASK[numBitsLeftInThisByte] & bytes[bytePtr]);
bytePtr++;
bitPtr = 0;
n -= numBitsLeftInThisByte;
}
else {
result <<= n;
result |= ((bytes[bytePtr] & (BITMASK[n] << (8 - n - bitPtr))) >> (8 - n - bitPtr));
bitPtr += n;
n = 0;
}
}
if (movePointers) {
this.bitPtr = bitPtr;
this.bytePtr = bytePtr;
}
return result;
};
/**
* Some voodoo magic.
*/
bitjs.io.BitStream.prototype.getBits = function() {
return (((((this.bytes[this.bytePtr] & 0xff) << 16) +
((this.bytes[this.bytePtr + 1] & 0xff) << 8) +
((this.bytes[this.bytePtr + 2] & 0xff))) >>> (8 - this.bitPtr)) & 0xffff);
};
/**
* Reads n bits out of the stream, consuming them (moving the bit pointer).
* @param {number} n The number of bits to read.
* @return {number} The read bits, as an unsigned number.
*/
bitjs.io.BitStream.prototype.readBits = function(n) {
return this.peekBits(n, true);
};
/**
* This returns n bytes as a sub-array, advancing the pointer if movePointers
* is true. Only use this for uncompressed blocks as this throws away remaining
* bits in the current byte.
* @param {number} n The number of bytes to peek.
* @param {boolean=} movePointers Whether to move the pointer, defaults false.
* @return {Uint8Array} The subarray.
*/
bitjs.io.BitStream.prototype.peekBytes = function(n, movePointers) {
if (n <= 0 || typeof n != typeof 1) {
return 0;
}
// from http://tools.ietf.org/html/rfc1951#page-11
// "Any bits of input up to the next byte boundary are ignored."
while (this.bitPtr !== 0) {
this.readBits(1);
}
movePointers = movePointers || false;
var bytePtr = this.bytePtr;
// var bitPtr = this.bitPtr;
var result = this.bytes.subarray(bytePtr, bytePtr + n);
if (movePointers) {
this.bytePtr += n;
}
return result;
};
/**
* @param {number} n The number of bytes to read.
* @return {Uint8Array} The subarray.
*/
bitjs.io.BitStream.prototype.readBytes = function(n) {
return this.peekBytes(n, true);
};
/**
* This object allows you to peek and consume bytes as numbers and strings
* out of an ArrayBuffer. In this buffer, everything must be byte-aligned.
*
* @param {ArrayBuffer} ab The ArrayBuffer object.
* @param {number=} optOffset The offset into the ArrayBuffer
* @param {number=} optLength The length of this BitStream
* @constructor
*/
bitjs.io.ByteStream = function(ab, optOffset, optLength) {
var offset = optOffset || 0;
var length = optLength || ab.byteLength;
this.bytes = new Uint8Array(ab, offset, length);
this.ptr = 0;
};
/**
* Peeks at the next n bytes as an unsigned number but does not advance the
* pointer
* TODO: This apparently cannot read more than 4 bytes as a number?
* @param {number} n The number of bytes to peek at.
* @return {number} The n bytes interpreted as an unsigned number.
*/
bitjs.io.ByteStream.prototype.peekNumber = function(n) {
// TODO: return error if n would go past the end of the stream?
if (n <= 0 || typeof n !== typeof 1) {
return -1;
}
var result = 0;
// read from last byte to first byte and roll them in
var curByte = this.ptr + n - 1;
while (curByte >= this.ptr) {
result <<= 8;
result |= this.bytes[curByte];
--curByte;
}
return result;
};
/**
* Returns the next n bytes as an unsigned number (or -1 on error)
* and advances the stream pointer n bytes.
* @param {number} n The number of bytes to read.
* @return {number} The n bytes interpreted as an unsigned number.
*/
bitjs.io.ByteStream.prototype.readNumber = function(n) {
var num = this.peekNumber( n );
this.ptr += n;
return num;
};
/**
* Returns the next n bytes as a signed number but does not advance the
* pointer.
* @param {number} n The number of bytes to read.
* @return {number} The bytes interpreted as a signed number.
*/
bitjs.io.ByteStream.prototype.peekSignedNumber = function(n) {
var num = this.peekNumber(n);
var HALF = Math.pow(2, (n * 8) - 1);
var FULL = HALF * 2;
if (num >= HALF) num -= FULL;
return num;
};
/**
* Returns the next n bytes as a signed number and advances the stream pointer.
* @param {number} n The number of bytes to read.
* @return {number} The bytes interpreted as a signed number.
*/
bitjs.io.ByteStream.prototype.readSignedNumber = function(n) {
var num = this.peekSignedNumber(n);
this.ptr += n;
return num;
};
/**
* This returns n bytes as a sub-array, advancing the pointer if movePointers
* is true.
* @param {number} n The number of bytes to read.
* @param {boolean} movePointers Whether to move the pointers.
* @return {Uint8Array} The subarray.
*/
bitjs.io.ByteStream.prototype.peekBytes = function(n, movePointers) {
if (n <= 0 || typeof n != typeof 1) {
return null;
}
var result = this.bytes.subarray(this.ptr, this.ptr + n);
if (movePointers) {
this.ptr += n;
}
return result;
};
/**
* Reads the next n bytes as a sub-array.
* @param {number} n The number of bytes to read.
* @return {Uint8Array} The subarray.
*/
bitjs.io.ByteStream.prototype.readBytes = function(n) {
return this.peekBytes(n, true);
};
/**
* Peeks at the next n bytes as a string but does not advance the pointer.
* @param {number} n The number of bytes to peek at.
* @return {string} The next n bytes as a string.
*/
bitjs.io.ByteStream.prototype.peekString = function(n) {
if (n <= 0 || typeof n != typeof 1) {
return "";
}
var result = "";
for (var p = this.ptr, end = this.ptr + n; p < end; ++p) {
result += String.fromCharCode(this.bytes[p]);
}
return result;
};
/**
* Returns the next n bytes as an ASCII string and advances the stream pointer
* n bytes.
* @param {number} n The number of bytes to read.
* @return {string} The next n bytes as a string.
*/
bitjs.io.ByteStream.prototype.readString = function(n) {
var strToReturn = this.peekString(n);
this.ptr += n;
return strToReturn;
};
/**
* A write-only Byte buffer which uses a Uint8 Typed Array as a backing store.
* @param {number} numBytes The number of bytes to allocate.
* @constructor
*/
bitjs.io.ByteBuffer = function(numBytes) {
if (typeof numBytes !== typeof 1 || numBytes <= 0) {
throw "Error! ByteBuffer initialized with '" + numBytes + "'";
}
this.data = new Uint8Array(numBytes);
this.ptr = 0;
};
/**
* @param {number} b The byte to insert.
*/
bitjs.io.ByteBuffer.prototype.insertByte = function(b) {
// TODO: throw if byte is invalid?
this.data[this.ptr++] = b;
};
/**
* @param {Array.<number>|Uint8Array|Int8Array} bytes The bytes to insert.
*/
bitjs.io.ByteBuffer.prototype.insertBytes = function(bytes) {
// TODO: throw if bytes is invalid?
this.data.set(bytes, this.ptr);
this.ptr += bytes.length;
};
/**
* Writes an unsigned number into the next n bytes. If the number is too large
* to fit into n bytes or is negative, an error is thrown.
* @param {number} num The unsigned number to write.
* @param {number} numBytes The number of bytes to write the number into.
*/
bitjs.io.ByteBuffer.prototype.writeNumber = function(num, numBytes) {
if (numBytes < 1) {
throw "Trying to write into too few bytes: " + numBytes;
}
if (num < 0) {
throw "Trying to write a negative number (" + num +
") as an unsigned number to an ArrayBuffer";
}
if (num > (Math.pow(2, numBytes * 8) - 1)) {
throw "Trying to write " + num + " into only " + numBytes + " bytes";
}
// Roll 8-bits at a time into an array of bytes.
var bytes = [];
while (numBytes-- > 0) {
var eightBits = num & 255;
bytes.push(eightBits);
num >>= 8;
}
this.insertBytes(bytes);
};
/**
* Writes a signed number into the next n bytes. If the number is too large
* to fit into n bytes, an error is thrown.
* @param {number} num The signed number to write.
* @param {number} numBytes The number of bytes to write the number into.
*/
bitjs.io.ByteBuffer.prototype.writeSignedNumber = function(num, numBytes) {
if (numBytes < 1) {
throw "Trying to write into too few bytes: " + numBytes;
}
var HALF = Math.pow(2, (numBytes * 8) - 1);
if (num >= HALF || num < -HALF) {
throw "Trying to write " + num + " into only " + numBytes + " bytes";
}
// Roll 8-bits at a time into an array of bytes.
var bytes = [];
while (numBytes-- > 0) {
var eightBits = num & 255;
bytes.push(eightBits);
num >>= 8;
}
this.insertBytes(bytes);
};
/**
* @param {string} str The ASCII string to write.
*/
bitjs.io.ByteBuffer.prototype.writeASCIIString = function(str) {
for (var i = 0; i < str.length; ++i) {
var curByte = str.charCodeAt(i);
if (curByte < 0 || curByte > 255) {
throw "Trying to write a non-ASCII string!";
}
this.insertByte(curByte);
}
};
})();

@ -0,0 +1,592 @@
/*
* kthoom.js
*
* Licensed under the MIT License
*
* Copyright(c) 2011 Google Inc.
* Copyright(c) 2011 antimatter15
*/
/* Reference Documentation:
* Web Workers: http://www.whatwg.org/specs/web-workers/current-work/
* Web Workers in Mozilla: https://developer.mozilla.org/En/Using_web_workers
* File API (FileReader): http://www.w3.org/TR/FileAPI/
* Typed Arrays: http://www.khronos.org/registry/typedarray/specs/latest/#6
*/
/* global bitjs */
if (window.opera) {
window.console.log = function(str) {
opera.postError(str);
};
}
var kthoom;
// gets the element with the given id
function getElem(id) {
if (document.documentElement.querySelector) {
// querySelector lookup
return document.body.querySelector("#" + id);
}
// getElementById lookup
return document.getElementById(id);
}
if (window.kthoom === undefined) {
kthoom = {};
}
// key codes
kthoom.Key = {
ESCAPE: 27,
LEFT: 37,
UP: 38,
RIGHT: 39,
DOWN: 40,
A: 65, B: 66, C: 67, D: 68, E: 69, F: 70, G: 71, H: 72, I: 73, J: 74, K: 75, L: 76, M: 77,
N: 78, O: 79, P: 80, Q: 81, R: 82, S: 83, T: 84, U: 85, V: 86, W: 87, X: 88, Y: 89, Z: 90,
QUESTION_MARK: 191,
LEFT_SQUARE_BRACKET: 219,
RIGHT_SQUARE_BRACKET: 221
};
// The rotation orientation of the comic.
kthoom.rotateTimes = 0;
// global variables
var unarchiver = null;
var currentImage = 0;
var imageFiles = [];
var imageFilenames = [];
var totalImages = 0;
var lastCompletion = 0;
var hflip = false, vflip = false, fitMode = kthoom.Key.B;
var canKeyNext = true, canKeyPrev = true;
kthoom.saveSettings = function() {
localStorage.kthoomSettings = JSON.stringify({
rotateTimes: kthoom.rotateTimes,
hflip: hflip,
vflip: vflip,
fitMode: fitMode
});
};
kthoom.loadSettings = function() {
try {
if (localStorage.kthoomSettings.length < 10){
return;
}
var s = JSON.parse(localStorage.kthoomSettings);
kthoom.rotateTimes = s.rotateTimes;
hflip = s.hflip;
vflip = s.vflip;
fitMode = s.fitMode;
} catch (err) {
alert("Error load settings");
}
};
var createURLFromArray = function(array, mimeType) {
var offset = array.byteOffset, len = array.byteLength;
var url;
var blob;
// TODO: Move all this browser support testing to a common place
// and do it just once.
// Blob constructor, see http://dev.w3.org/2006/webapi/FileAPI/#dfn-Blob.
if (typeof Blob === "function") {
blob = new Blob([array], {type: mimeType});
} else {
throw "Browser support for Blobs is missing.";
}
if (blob.slice) {
blob = blob.slice(offset, offset + len, mimeType);
} else {
throw "Browser support for Blobs is missing.";
}
if ((typeof URL !== "function" && typeof URL !== "object") ||
typeof URL.createObjectURL !== "function") {
throw "Browser support for Object URLs is missing";
}
return URL.createObjectURL(blob);
};
// Stores an image filename and its data: URI.
// TODO: investigate if we really need to store as base64 (leave off ;base64 and just
// non-safe URL characters are encoded as %xx ?)
// This would save 25% on memory since base64-encoded strings are 4/3 the size of the binary
kthoom.ImageFile = function(file) {
this.filename = file.filename;
var fileExtension = file.filename.split(".").pop().toLowerCase();
var mimeType = fileExtension === "png" ? "image/png" :
(fileExtension === "jpg" || fileExtension === "jpeg") ? "image/jpeg" :
fileExtension === "gif" ? "image/gif" : null;
this.dataURI = createURLFromArray(file.fileData, mimeType);
this.data = file;
};
kthoom.initProgressMeter = function() {
var svgns = "http://www.w3.org/2000/svg";
var pdiv = $("#progress")[0];
var svg = document.createElementNS(svgns, "svg");
svg.style.width = "100%";
svg.style.height = "100%";
var defs = document.createElementNS(svgns, "defs");
var patt = document.createElementNS(svgns, "pattern");
patt.id = "progress_pattern";
patt.setAttribute("width", "30");
patt.setAttribute("height", "20");
patt.setAttribute("patternUnits", "userSpaceOnUse");
var rect = document.createElementNS(svgns, "rect");
rect.setAttribute("width", "100%");
rect.setAttribute("height", "100%");
rect.setAttribute("fill", "#cc2929");
var poly = document.createElementNS(svgns, "polygon");
poly.setAttribute("fill", "yellow");
poly.setAttribute("points", "15,0 30,0 15,20 0,20");
patt.appendChild(rect);
patt.appendChild(poly);
defs.appendChild(patt);
svg.appendChild(defs);
var g = document.createElementNS(svgns, "g");
var outline = document.createElementNS(svgns, "rect");
outline.setAttribute("y", "1");
outline.setAttribute("width", "100%");
outline.setAttribute("height", "15");
outline.setAttribute("fill", "#777");
outline.setAttribute("stroke", "white");
outline.setAttribute("rx", "5");
outline.setAttribute("ry", "5");
g.appendChild(outline);
var title = document.createElementNS(svgns, "text");
title.id = "progress_title";
title.appendChild(document.createTextNode("0%"));
title.setAttribute("y", "13");
title.setAttribute("x", "99.5%");
title.setAttribute("fill", "white");
title.setAttribute("font-size", "12px");
title.setAttribute("text-anchor", "end");
g.appendChild(title);
var meter = document.createElementNS(svgns, "rect");
meter.id = "meter";
meter.setAttribute("width", "0%");
meter.setAttribute("height", "17");
meter.setAttribute("fill", "url(#progress_pattern)");
meter.setAttribute("rx", "5");
meter.setAttribute("ry", "5");
var meter2 = document.createElementNS(svgns, "rect");
meter2.id = "meter2";
meter2.setAttribute("width", "0%");
meter2.setAttribute("height", "17");
meter2.setAttribute("opacity", "0.8");
meter2.setAttribute("fill", "#007fff");
meter2.setAttribute("rx", "5");
meter2.setAttribute("ry", "5");
g.appendChild(meter);
g.appendChild(meter2);
var page = document.createElementNS(svgns, "text");
page.id = "page";
page.appendChild(document.createTextNode("0/0"));
page.setAttribute("y", "13");
page.setAttribute("x", "0.5%");
page.setAttribute("fill", "white");
page.setAttribute("font-size", "12px");
g.appendChild(page);
svg.appendChild(g);
pdiv.appendChild(svg);
var l;
svg.onclick = function(e) {
for (var x = pdiv, l = 0; x !== document.documentElement; x = x.parentNode) l += x.offsetLeft;
var page = Math.max(1, Math.ceil(((e.clientX - l) / pdiv.offsetWidth) * totalImages)) - 1;
currentImage = page;
updatePage();
};
}
kthoom.setProgressMeter = function(pct, optLabel) {
pct = (pct * 100);
var part = 1 / totalImages;
var remain = ((pct - lastCompletion) / 100) / part;
var fract = Math.min(1, remain);
var smartpct = ((imageFiles.length / totalImages) + (fract * part)) * 100;
if (totalImages === 0) smartpct = pct;
// + Math.min((pct - lastCompletion), 100/totalImages * 0.9 + (pct - lastCompletion - 100/totalImages)/2, 100/totalImages);
var oldval = parseFloat(getElem("meter").getAttribute("width"));
if (isNaN(oldval)) oldval = 0;
var weight = 0.5;
smartpct = ((weight * smartpct) + ((1 - weight) * oldval));
if (pct === 100) smartpct = 100;
if (!isNaN(smartpct)) {
getElem("meter").setAttribute("width", smartpct + "%");
}
var title = getElem("progress_title");
while (title.firstChild) title.removeChild(title.firstChild);
var labelText = pct.toFixed(2) + "% " + imageFiles.length + "/" + totalImages + "";
if (optLabel) {
labelText = optLabel + " " + labelText;
}
title.appendChild(document.createTextNode(labelText));
getElem("meter2").setAttribute("width",
100 * (totalImages === 0 ? 0 : ((currentImage + 1) / totalImages)) + "%");
var titlePage = getElem("page");
while (titlePage.firstChild) titlePage.removeChild(titlePage.firstChild);
titlePage.appendChild(document.createTextNode( (currentImage + 1) + "/" + totalImages ));
if (pct > 0) {
//getElem('nav').className = '';
getElem("progress").className = "";
}
}
function loadFromArrayBuffer(ab) {
var start = (new Date).getTime();
var h = new Uint8Array(ab, 0, 10);
var pathToBitJS = "../../static/js/";
if (h[0] === 0x52 && h[1] === 0x61 && h[2] === 0x72 && h[3] === 0x21) { //Rar!
unarchiver = new bitjs.archive.Unrarrer(ab, pathToBitJS);
} else if (h[0] === 80 && h[1] === 75) { //PK (Zip)
unarchiver = new bitjs.archive.Unzipper(ab, pathToBitJS);
} else { // Try with tar
unarchiver = new bitjs.archive.Untarrer(ab, pathToBitJS);
}
// Listen for UnarchiveEvents.
if (unarchiver) {
unarchiver.addEventListener(bitjs.archive.UnarchiveEvent.Type.PROGRESS,
function(e) {
var percentage = e.currentBytesUnarchived / e.totalUncompressedBytesInArchive;
totalImages = e.totalFilesInArchive;
kthoom.setProgressMeter(percentage, "Unzipping");
// display nav
lastCompletion = percentage * 100;
});
unarchiver.addEventListener(bitjs.archive.UnarchiveEvent.Type.EXTRACT,
function(e) {
// convert DecompressedFile into a bunch of ImageFiles
if (e.unarchivedFile) {
var f = e.unarchivedFile;
// add any new pages based on the filename
if (imageFilenames.indexOf(f.filename) === -1) {
imageFilenames.push(f.filename);
imageFiles.push(new kthoom.ImageFile(f));
}
}
// display first page if we haven't yet
if (imageFiles.length === currentImage + 1) {
updatePage();
}
});
unarchiver.addEventListener(bitjs.archive.UnarchiveEvent.Type.FINISH,
function() {
var diff = ((new Date).getTime() - start) / 1000;
console.log("Unarchiving done in " + diff + "s");
});
unarchiver.start();
} else {
alert("Some error");
}
}
function updatePage() {
var title = getElem("page");
while (title.firstChild) title.removeChild(title.firstChild);
title.appendChild(document.createTextNode( (currentImage + 1 ) + "/" + totalImages ));
getElem("meter2").setAttribute("width",
100 * (totalImages === 0 ? 0 : ((currentImage + 1 ) / totalImages)) + "%");
if (imageFiles[currentImage]) {
setImage(imageFiles[currentImage].dataURI);
} else {
setImage("loading");
}
}
function setImage(url) {
var canvas = $("#mainImage")[0];
var x = $("#mainImage")[0].getContext("2d");
$("#mainText").hide();
if (url === "loading") {
updateScale(true);
canvas.width = innerWidth - 100;
canvas.height = 200;
x.fillStyle = "red";
x.font = "50px sans-serif";
x.strokeStyle = "black";
x.fillText("Loading Page #" + (currentImage + 1), 100, 100);
} else {
if ($("body").css("scrollHeight") / innerHeight > 1) {
$("body").css("overflowY", "scroll");
}
var img = new Image();
img.onerror = function() {
canvas.width = innerWidth - 100;
canvas.height = 300;
updateScale(true);
x.fillStyle = "orange";
x.font = "50px sans-serif";
x.strokeStyle = "black";
x.fillText("Page #" + (currentImage + 1) + " (" +
imageFiles[currentImage].filename + ")", 100, 100);
x.fillStyle = "red";
x.fillText("Is corrupt or not an image", 100, 200);
var xhr = new XMLHttpRequest();
if (/(html|htm)$/.test(imageFiles[currentImage].filename)) {
xhr.open("GET", url, true);
xhr.onload = function() {
//document.getElementById('mainText').style.display = '';
$("#mainText").css("display", "");
$("#mainText").innerHTML("<iframe style=\"width:100%;height:700px;border:0\" src=\"data:text/html," + escape(xhr.responseText) + "\"></iframe>");
}
xhr.send(null);
} else if (!/(jpg|jpeg|png|gif)$/.test(imageFiles[currentImage].filename) && imageFiles[currentImage].data.uncompressedSize < 10 * 1024) {
xhr.open("GET", url, true);
xhr.onload = function() {
$("#mainText").css("display", "");
$("#mainText").innerText(xhr.responseText);
};
xhr.send(null);
}
};
img.onload = function() {
var h = img.height,
w = img.width,
sw = w,
sh = h;
kthoom.rotateTimes = (4 + kthoom.rotateTimes) % 4;
x.save();
if (kthoom.rotateTimes % 2 === 1) {
sh = w;
sw = h;
}
canvas.height = sh;
canvas.width = sw;
x.translate(sw / 2, sh / 2);
x.rotate(Math.PI / 2 * kthoom.rotateTimes);
x.translate(-w / 2, -h / 2);
if (vflip) {
x.scale(1, -1);
x.translate(0, -h);
}
if (hflip) {
x.scale(-1, 1);
x.translate(-w, 0);
}
canvas.style.display = "none";
scrollTo(0, 0);
x.drawImage(img, 0, 0);
updateScale();
canvas.style.display = "";
$("body").css("overflowY", "");
x.restore();
};
img.src = url;
}
}
function showPrevPage() {
currentImage--;
if (currentImage < 0) {
// Freeze on the current page.
currentImage++;
} else {
updatePage();
}
}
function showNextPage() {
currentImage++;
if (currentImage >= Math.max(totalImages, imageFiles.length)) {
// Freeze on the current page.
currentImage--;
} else {
updatePage();
}
}
function updateScale(clear) {
var mainImageStyle = getElem("mainImage").style;
mainImageStyle.width = "";
mainImageStyle.height = "";
mainImageStyle.maxWidth = "";
mainImageStyle.maxHeight = "";
var maxheight = innerHeight - 15;
if (!/main/.test(getElem("titlebar").className)) {
maxheight -= 25;
}
if (clear || fitMode === kthoom.Key.N) {
} else if (fitMode === kthoom.Key.B) {
mainImageStyle.maxWidth = "100%";
mainImageStyle.maxHeight = maxheight + "px";
} else if (fitMode === kthoom.Key.H) {
mainImageStyle.height = maxheight + "px";
} else if (fitMode === kthoom.Key.W) {
mainImageStyle.width = "100%";
}
kthoom.saveSettings();
}
function keyHandler(evt) {
var code = evt.keyCode;
if ($("#progress").css("display") === "none"){
return;
}
canKeyNext = (($("body").css("offsetWidth") + $("body").css("scrollLeft")) / $("body").css("scrollWidth")) >= 1;
canKeyPrev = (scrollX <= 0);
if (evt.ctrlKey || evt.shiftKey || evt.metaKey) return;
switch (code) {
case kthoom.Key.LEFT:
if (canKeyPrev) showPrevPage();
break;
case kthoom.Key.RIGHT:
if (canKeyNext) showNextPage();
break;
case kthoom.Key.L:
kthoom.rotateTimes--;
if (kthoom.rotateTimes < 0) {
kthoom.rotateTimes = 3;
}
updatePage();
break;
case kthoom.Key.R:
kthoom.rotateTimes++;
if (kthoom.rotateTimes > 3) {
kthoom.rotateTimes = 0;
}
updatePage();
break;
case kthoom.Key.F:
if (!hflip && !vflip) {
hflip = true;
} else if (hflip === true) {
vflip = true;
hflip = false;
} else if (vflip === true) {
vflip = false;
}
updatePage();
break;
case kthoom.Key.W:
fitMode = kthoom.Key.W;
updateScale();
break;
case kthoom.Key.H:
fitMode = kthoom.Key.H;
updateScale();
break;
case kthoom.Key.B:
fitMode = kthoom.Key.B;
updateScale();
break;
case kthoom.Key.N:
fitMode = kthoom.Key.N;
updateScale();
break;
default:
//console.log('KeyCode = ' + code);
break;
}
}
function init(filename) {
if (!window.FileReader) {
alert("Sorry, kthoom will not work with your browser because it does not support the File API. Please try kthoom with Chrome 12+ or Firefox 7+");
} else {
var request = new XMLHttpRequest();
request.open("GET", filename);
request.responseType = "arraybuffer";
request.setRequestHeader("X-Test", "test1");
request.setRequestHeader("X-Test", "test2");
request.addEventListener("load", function(event) {
if (request.status >= 200 && request.status < 300) {
loadFromArrayBuffer(request.response);
} else {
console.warn(request.statusText, request.responseText);
}
});
request.send();
kthoom.initProgressMeter();
document.body.className += /AppleWebKit/.test(navigator.userAgent) ? " webkit" : "";
updateScale(true);
kthoom.loadSettings();
$(document).keydown(keyHandler);
$(window).resize(function() {
var f = (screen.width - innerWidth < 4 && screen.height - innerHeight < 4);
getElem("titlebar").className = f ? "main" : "";
updateScale();
});
$("#mainImage").click(function(evt) {
// Firefox does not support offsetX/Y so we have to manually calculate
// where the user clicked in the image.
var mainContentWidth = $("#mainContent").width();
var mainContentHeight = $("#mainContent").height();
var comicWidth = evt.target.clientWidth;
var comicHeight = evt.target.clientHeight;
var offsetX = (mainContentWidth - comicWidth) / 2;
var offsetY = (mainContentHeight - comicHeight) / 2;
var clickX = !!evt.offsetX ? evt.offsetX : (evt.clientX - offsetX);
var clickY = !!evt.offsetY ? evt.offsetY : (evt.clientY - offsetY);
// Determine if the user clicked/tapped the left side or the
// right side of the page.
var clickedPrev = false;
switch (kthoom.rotateTimes) {
case 0:
clickedPrev = clickX < (comicWidth / 2);
break;
case 1:
clickedPrev = clickY < (comicHeight / 2);
break;
case 2:
clickedPrev = clickX > (comicWidth / 2);
break;
case 3:
clickedPrev = clickY > (comicHeight / 2);
break;
}
if (clickedPrev) {
showPrevPage();
} else {
showNextPage();
}
});
}
}

@ -0,0 +1 @@
!function(a){a.fn.datepicker.dates.it={days:["Domenica","Lunedì","Martedì","Mercoledì","Giovedì","Venerdì","Sabato"],daysShort:["Dom","Lun","Mar","Mer","Gio","Ven","Sab"],daysMin:["Do","Lu","Ma","Me","Gi","Ve","Sa"],months:["Gennaio","Febbraio","Marzo","Aprile","Maggio","Giugno","Luglio","Agosto","Settembre","Ottobre","Novembre","Dicembre"],monthsShort:["Gen","Feb","Mar","Apr","Mag","Giu","Lug","Ago","Set","Ott","Nov","Dic"],today:"Oggi",monthsTitle:"Mesi",clear:"Cancella",weekStart:1,format:"dd/mm/yyyy"}}(jQuery);

@ -0,0 +1,43 @@
/* global $, calibre, EPUBJS, ePubReader */
(function() {
"use strict";
EPUBJS.filePath = calibre.filePath;
EPUBJS.cssPath = calibre.cssPath;
var reader = ePubReader(calibre.bookUrl, {
restore: true,
bookmarks: calibre.bookmark ? [calibre.bookmark] : []
});
if (calibre.useBookmarks) {
reader.on("reader:bookmarked", updateBookmark.bind(reader, "add"));
reader.on("reader:unbookmarked", updateBookmark.bind(reader, "remove"));
} else {
$("#bookmark, #show-Bookmarks").remove();
}
/**
* @param {string} action - Add or remove bookmark
* @param {string|int} location - Location or zero
*/
function updateBookmark(action, location) {
// Remove other bookmarks (there can only be one)
if (action === "add") {
this.settings.bookmarks.filter(function (bookmark) {
return bookmark && bookmark !== location;
}).map(function (bookmark) {
this.removeBookmark(bookmark);
}.bind(this));
}
// Save to database
$.ajax(calibre.bookmarkUrl, {
method: "post",
data: { bookmark: location || "" }
}).fail(function (xhr, status, error) {
alert(error);
});
}
})();

@ -0,0 +1,891 @@
/**
* unrar.js
*
* Copyright(c) 2011 Google Inc.
* Copyright(c) 2011 antimatter15
*
* Reference Documentation:
*
* http://kthoom.googlecode.com/hg/docs/unrar.html
*/
/* global bitjs, importScripts */
// This file expects to be invoked as a Worker (see onmessage below).
importScripts("io.js");
importScripts("archive.js");
// Progress variables.
var currentFilename = "";
var currentFileNumber = 0;
var currentBytesUnarchivedInFile = 0;
var currentBytesUnarchived = 0;
var totalUncompressedBytesInArchive = 0;
var totalFilesInArchive = 0;
// Helper functions.
var info = function(str) {
postMessage(new bitjs.archive.UnarchiveInfoEvent(str));
};
var err = function(str) {
postMessage(new bitjs.archive.UnarchiveErrorEvent(str));
};
var postProgress = function() {
postMessage(new bitjs.archive.UnarchiveProgressEvent(
currentFilename,
currentFileNumber,
currentBytesUnarchivedInFile,
currentBytesUnarchived,
totalUncompressedBytesInArchive,
totalFilesInArchive));
};
// shows a byte value as its hex representation
var nibble = "0123456789ABCDEF";
var byteValueToHexString = function(num) {
return nibble[num>>4] + nibble[num & 0xF];
};
var twoByteValueToHexString = function(num) {
return nibble[(num>>12) & 0xF] + nibble[(num>>8) & 0xF] + nibble[(num>>4) & 0xF] + nibble[num & 0xF];
};
// Volume Types
// MARK_HEAD = 0x72;
var MAIN_HEAD = 0x73,
FILE_HEAD = 0x74,
// COMM_HEAD = 0x75,
// AV_HEAD = 0x76,
// SUB_HEAD = 0x77,
// PROTECT_HEAD = 0x78,
// SIGN_HEAD = 0x79,
// NEWSUB_HEAD = 0x7a,
ENDARC_HEAD = 0x7b;
// bstream is a bit stream
var RarVolumeHeader = function(bstream) {
var headPos = bstream.bytePtr;
// byte 1,2
info("Rar Volume Header @" + bstream.bytePtr);
this.crc = bstream.readBits(16);
info(" crc=" + this.crc);
// byte 3
this.headType = bstream.readBits(8);
info(" headType=" + this.headType);
// Get flags
// bytes 4,5
this.flags = {};
this.flags.value = bstream.peekBits(16);
info(" flags=" + twoByteValueToHexString(this.flags.value));
switch (this.headType) {
case MAIN_HEAD:
this.flags.MHD_VOLUME = !!bstream.readBits(1);
this.flags.MHD_COMMENT = !!bstream.readBits(1);
this.flags.MHD_LOCK = !!bstream.readBits(1);
this.flags.MHD_SOLID = !!bstream.readBits(1);
this.flags.MHD_PACK_COMMENT = !!bstream.readBits(1);
this.flags.MHD_NEWNUMBERING = this.flags.MHD_PACK_COMMENT;
this.flags.MHD_AV = !!bstream.readBits(1);
this.flags.MHD_PROTECT = !!bstream.readBits(1);
this.flags.MHD_PASSWORD = !!bstream.readBits(1);
this.flags.MHD_FIRSTVOLUME = !!bstream.readBits(1);
this.flags.MHD_ENCRYPTVER = !!bstream.readBits(1);
bstream.readBits(6); // unused
break;
case FILE_HEAD:
this.flags.LHD_SPLIT_BEFORE = !!bstream.readBits(1); // 0x0001
this.flags.LHD_SPLIT_AFTER = !!bstream.readBits(1); // 0x0002
this.flags.LHD_PASSWORD = !!bstream.readBits(1); // 0x0004
this.flags.LHD_COMMENT = !!bstream.readBits(1); // 0x0008
this.flags.LHD_SOLID = !!bstream.readBits(1); // 0x0010
bstream.readBits(3); // unused
this.flags.LHD_LARGE = !!bstream.readBits(1); // 0x0100
this.flags.LHD_UNICODE = !!bstream.readBits(1); // 0x0200
this.flags.LHD_SALT = !!bstream.readBits(1); // 0x0400
this.flags.LHD_VERSION = !!bstream.readBits(1); // 0x0800
this.flags.LHD_EXTTIME = !!bstream.readBits(1); // 0x1000
this.flags.LHD_EXTFLAGS = !!bstream.readBits(1); // 0x2000
bstream.readBits(2); // unused
info(" LHD_SPLIT_BEFORE = " + this.flags.LHD_SPLIT_BEFORE);
break;
default:
bstream.readBits(16);
}
// byte 6,7
this.headSize = bstream.readBits(16);
info(" headSize=" + this.headSize);
switch (this.headType) {
case MAIN_HEAD:
this.highPosAv = bstream.readBits(16);
this.posAv = bstream.readBits(32);
if (this.flags.MHD_ENCRYPTVER) {
this.encryptVer = bstream.readBits(8);
}
info("Found MAIN_HEAD with highPosAv=" + this.highPosAv + ", posAv=" + this.posAv);
break;
case FILE_HEAD:
this.packSize = bstream.readBits(32);
this.unpackedSize = bstream.readBits(32);
this.hostOS = bstream.readBits(8);
this.fileCRC = bstream.readBits(32);
this.fileTime = bstream.readBits(32);
this.unpVer = bstream.readBits(8);
this.method = bstream.readBits(8);
this.nameSize = bstream.readBits(16);
this.fileAttr = bstream.readBits(32);
if (this.flags.LHD_LARGE) {
info("Warning: Reading in LHD_LARGE 64-bit size values");
this.HighPackSize = bstream.readBits(32);
this.HighUnpSize = bstream.readBits(32);
} else {
this.HighPackSize = 0;
this.HighUnpSize = 0;
if (this.unpackedSize == 0xffffffff) {
this.HighUnpSize = 0x7fffffff;
this.unpackedSize = 0xffffffff;
}
}
this.fullPackSize = 0;
this.fullUnpackSize = 0;
this.fullPackSize |= this.HighPackSize;
this.fullPackSize <<= 32;
this.fullPackSize |= this.packSize;
// read in filename
this.filename = bstream.readBytes(this.nameSize);
for (var _i = 0, _s = ""; _i < this.filename.length ; _i++) {
_s += String.fromCharCode(this.filename[_i]);
}
this.filename = _s;
if (this.flags.LHD_SALT) {
info("Warning: Reading in 64-bit salt value");
this.salt = bstream.readBits(64); // 8 bytes
}
if (this.flags.LHD_EXTTIME) {
// 16-bit flags
var extTimeFlags = bstream.readBits(16);
// this is adapted straight out of arcread.cpp, Archive::ReadHeader()
for (var I = 0; I < 4; ++I) {
var rmode = extTimeFlags >> ((3 - I) * 4);
if ((rmode & 8)==0)
continue;
if (I!=0) {
bstream.readBits(16);
}
var count = (rmode & 3);
for (var J = 0; J < count; ++J) {
bstream.readBits(8);
}
}
}
if (this.flags.LHD_COMMENT) {
info("Found a LHD_COMMENT");
}
while (headPos + this.headSize > bstream.bytePtr) bstream.readBits(1);
info("Found FILE_HEAD with packSize=" + this.packSize + ", unpackedSize= " + this.unpackedSize + ", hostOS=" + this.hostOS + ", unpVer=" + this.unpVer + ", method=" + this.method + ", filename=" + this.filename);
break;
default:
info("Found a header of type 0x" + byteValueToHexString(this.headType));
// skip the rest of the header bytes (for now)
bstream.readBytes( this.headSize - 7 );
break;
}
};
var BLOCK_LZ = 0;
// BLOCK_PPM = 1;
var rLDecode = [0, 1, 2, 3, 4, 5, 6, 7, 8, 10, 12, 14, 16, 20, 24, 28, 32, 40, 48, 56, 64, 80, 96, 112, 128, 160, 192, 224],
rLBits = [0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 2, 2, 2, 2, 3, 3, 3, 3, 4, 4, 4, 4, 5, 5, 5, 5],
rDBitLengthCounts = [4, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 14, 0, 12],
rSDDecode = [0, 4, 8, 16, 32, 64, 128, 192],
rSDBits = [2,2,3, 4, 5, 6, 6, 6];
var rDDecode = [0, 1, 2, 3, 4, 6, 8, 12, 16, 24, 32,
48, 64, 96, 128, 192, 256, 384, 512, 768, 1024, 1536, 2048, 3072,
4096, 6144, 8192, 12288, 16384, 24576, 32768, 49152, 65536, 98304,
131072, 196608, 262144, 327680, 393216, 458752, 524288, 589824,
655360, 720896, 786432, 851968, 917504, 983040];
var rDBits = [0, 0, 0, 0, 1, 1, 2, 2, 3, 3, 4, 4, 5,
5, 6, 6, 7, 7, 8, 8, 9, 9, 10, 10, 11, 11, 12, 12, 13, 13, 14, 14,
15, 15, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16];
var rLOW_DIST_REP_COUNT = 16;
var rNC = 299,
rDC = 60,
rLDC = 17,
rRC = 28,
rBC = 20,
rHUFF_TABLE_SIZE = (rNC + rDC + rRC + rLDC);
var UnpBlockType = BLOCK_LZ;
var UnpOldTable = new Array(rHUFF_TABLE_SIZE);
var BD = { //bitdecode
DecodeLen: new Array(16),
DecodePos: new Array(16),
DecodeNum: new Array(rBC)
};
var LD = { //litdecode
DecodeLen: new Array(16),
DecodePos: new Array(16),
DecodeNum: new Array(rNC)
};
var DD = { //distdecode
DecodeLen: new Array(16),
DecodePos: new Array(16),
DecodeNum: new Array(rDC)
};
var LDD = { //low dist decode
DecodeLen: new Array(16),
DecodePos: new Array(16),
DecodeNum: new Array(rLDC)
};
var RD = { //rep decode
DecodeLen: new Array(16),
DecodePos: new Array(16),
DecodeNum: new Array(rRC)
};
var rBuffer;
// read in Huffman tables for RAR
function RarReadTables(bstream) {
var BitLength = new Array(rBC),
Table = new Array(rHUFF_TABLE_SIZE);
// before we start anything we need to get byte-aligned
bstream.readBits( (8 - bstream.bitPtr) & 0x7 );
if (bstream.readBits(1)) {
info("Error! PPM not implemented yet");
return;
}
if (!bstream.readBits(1)) { //discard old table
for (var i = UnpOldTable.length; i--;) UnpOldTable[i] = 0;
}
// read in bit lengths
for (var I = 0; I < rBC; ++I) {
var Length = bstream.readBits(4);
if (Length == 15) {
var ZeroCount = bstream.readBits(4);
if (ZeroCount == 0) {
BitLength[I] = 15;
}
else {
ZeroCount += 2;
while (ZeroCount-- > 0 && I < rBC)
BitLength[I++] = 0;
--I;
}
}
else {
BitLength[I] = Length;
}
}
// now all 20 bit lengths are obtained, we construct the Huffman Table:
RarMakeDecodeTables(BitLength, 0, BD, rBC);
var TableSize = rHUFF_TABLE_SIZE;
//console.log(DecodeLen, DecodePos, DecodeNum);
for (var i = 0; i < TableSize;) {
var num = RarDecodeNumber(bstream, BD);
if (num < 16) {
Table[i] = (num + UnpOldTable[i]) & 0xf;
i++;
} else if(num < 18) {
var N = (num == 16) ? (bstream.readBits(3) + 3) : (bstream.readBits(7) + 11);
while (N-- > 0 && i < TableSize) {
Table[i] = Table[i - 1];
i++;
}
} else {
var N = (num == 18) ? (bstream.readBits(3) + 3) : (bstream.readBits(7) + 11);
while (N-- > 0 && i < TableSize) {
Table[i++] = 0;
}
}
}
RarMakeDecodeTables(Table, 0, LD, rNC);
RarMakeDecodeTables(Table, rNC, DD, rDC);
RarMakeDecodeTables(Table, rNC + rDC, LDD, rLDC);
RarMakeDecodeTables(Table, rNC + rDC + rLDC, RD, rRC);
for (var i = UnpOldTable.length; i--;) {
UnpOldTable[i] = Table[i];
}
return true;
}
function RarDecodeNumber(bstream, dec) {
var DecodeLen = dec.DecodeLen, DecodePos = dec.DecodePos, DecodeNum = dec.DecodeNum;
var bitField = bstream.getBits() & 0xfffe;
//some sort of rolled out binary search
var bits = ((bitField < DecodeLen[8])?
((bitField < DecodeLen[4])?
((bitField < DecodeLen[2])?
((bitField < DecodeLen[1])?1:2)
:((bitField < DecodeLen[3])?3:4))
:(bitField < DecodeLen[6])?
((bitField < DecodeLen[5])?5:6)
:((bitField < DecodeLen[7])?7:8))
:((bitField < DecodeLen[12])?
((bitField < DecodeLen[10])?
((bitField < DecodeLen[9])?9:10)
:((bitField < DecodeLen[11])?11:12))
:(bitField < DecodeLen[14])?
((bitField < DecodeLen[13])?13:14)
:15));
bstream.readBits(bits);
var N = DecodePos[bits] + ((bitField - DecodeLen[bits -1]) >>> (16 - bits));
return DecodeNum[N];
}
function RarMakeDecodeTables(BitLength, offset, dec, size) {
var DecodeLen = dec.DecodeLen, DecodePos = dec.DecodePos, DecodeNum = dec.DecodeNum;
var LenCount = [0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],
TmpPos = [0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],
N = 0, M = 0;
for (var i = DecodeNum.length; i--;) DecodeNum[i] = 0;
for (var i = 0; i < size; i++) {
LenCount[BitLength[i + offset] & 0xF]++;
}
LenCount[0] = 0;
TmpPos[0] = 0;
DecodePos[0] = 0;
DecodeLen[0] = 0;
for (var I = 1; I < 16; ++I) {
N = 2 * (N+LenCount[I]);
M = (N << (15-I));
if (M > 0xFFFF)
M = 0xFFFF;
DecodeLen[I] = M;
DecodePos[I] = DecodePos[I-1] + LenCount[I-1];
TmpPos[I] = DecodePos[I];
}
for (I = 0; I < size; ++I)
if (BitLength[I + offset] != 0)
DecodeNum[ TmpPos[ BitLength[offset + I] & 0xF ]++] = I;
}
// TODO: implement
function Unpack15(bstream, Solid) {
info("ERROR! RAR 1.5 compression not supported");
}
function Unpack20(bstream, Solid) {
var destUnpSize = rBuffer.data.length;
var oldDistPtr = 0;
RarReadTables20(bstream);
while (destUnpSize > rBuffer.ptr) {
var num = RarDecodeNumber(bstream, LD);
if (num < 256) {
rBuffer.insertByte(num);
continue;
}
if (num > 269) {
var Length = rLDecode[num -= 270] + 3;
if ((Bits = rLBits[num]) > 0) {
Length += bstream.readBits(Bits);
}
var DistNumber = RarDecodeNumber(bstream, DD);
var Distance = rDDecode[DistNumber] + 1;
if ((Bits = rDBits[DistNumber]) > 0) {
Distance += bstream.readBits(Bits);
}
if (Distance >= 0x2000) {
Length++;
if(Distance >= 0x40000) Length++;
}
lastLength = Length;
lastDist = rOldDist[oldDistPtr++ & 3] = Distance;
RarCopyString(Length, Distance);
continue;
}
if (num == 269) {
RarReadTables20(bstream);
RarUpdateProgress()
continue;
}
if (num == 256) {
lastDist = rOldDist[oldDistPtr++ & 3] = lastDist;
RarCopyString(lastLength, lastDist);
continue;
}
if (num < 261) {
var Distance = rOldDist[(oldDistPtr - (num - 256)) & 3];
var LengthNumber = RarDecodeNumber(bstream, RD);
var Length = rLDecode[LengthNumber] +2;
if ((Bits = rLBits[LengthNumber]) > 0) {
Length += bstream.readBits(Bits);
}
if (Distance >= 0x101) {
Length++;
if (Distance >= 0x2000) {
Length++
if (Distance >= 0x40000) Length++;
}
}
lastLength = Length;
lastDist = rOldDist[oldDistPtr++ & 3] = Distance;
RarCopyString(Length, Distance);
continue;
}
if (num < 270) {
var Distance = rSDDecode[num -= 261] + 1;
if ((Bits = rSDBits[num]) > 0) {
Distance += bstream.readBits(Bits);
}
lastLength = 2;
lastDist = rOldDist[oldDistPtr++ & 3] = Distance;
RarCopyString(2, Distance);
continue;
}
}
RarUpdateProgress()
}
function RarUpdateProgress() {
var change = rBuffer.ptr - currentBytesUnarchivedInFile;
currentBytesUnarchivedInFile = rBuffer.ptr;
currentBytesUnarchived += change;
postProgress();
}
var rNC20 = 298,
rDC20 = 48,
rRC20 = 28,
rBC20 = 19,
rMC20 = 257;
var UnpOldTable20 = new Array(rMC20 * 4);
function RarReadTables20(bstream) {
var BitLength = new Array(rBC20);
var Table = new Array(rMC20 * 4);
var TableSize, N, I;
var AudioBlock = bstream.readBits(1);
if (!bstream.readBits(1))
for (var i = UnpOldTable20.length; i--;) UnpOldTable20[i] = 0;
TableSize = rNC20 + rDC20 + rRC20;
for (var I = 0; I < rBC20; I++)
BitLength[I] = bstream.readBits(4);
RarMakeDecodeTables(BitLength, 0, BD, rBC20);
I = 0;
while (I < TableSize) {
var num = RarDecodeNumber(bstream, BD);
if (num < 16) {
Table[I] = num + UnpOldTable20[I] & 0xf;
I++;
} else if(num == 16) {
N = bstream.readBits(2) + 3;
while (N-- > 0 && I < TableSize) {
Table[I] = Table[I - 1];
I++;
}
} else {
if (num == 17) {
N = bstream.readBits(3) + 3;
} else {
N = bstream.readBits(7) + 11;
}
while (N-- > 0 && I < TableSize) {
Table[I++] = 0;
}
}
}
RarMakeDecodeTables(Table, 0, LD, rNC20);
RarMakeDecodeTables(Table, rNC20, DD, rDC20);
RarMakeDecodeTables(Table, rNC20 + rDC20, RD, rRC20);
for (var i = UnpOldTable20.length; i--;) UnpOldTable20[i] = Table[i];
}
var lowDistRepCount = 0, prevLowDist = 0;
var rOldDist = [0,0,0,0];
var lastDist;
var lastLength;
function Unpack29(bstream, Solid) {
// lazy initialize rDDecode and rDBits
var DDecode = new Array(rDC);
var DBits = new Array(rDC);
var Dist=0,BitLength=0,Slot=0;
for (var I = 0; I < rDBitLengthCounts.length; I++,BitLength++) {
for (var J = 0; J < rDBitLengthCounts[I]; J++,Slot++,Dist+=(1<<BitLength)) {
DDecode[Slot]=Dist;
DBits[Slot]=BitLength;
}
}
var Bits;
//tablesRead = false;
rOldDist = [0,0,0,0]
lastDist = 0;
lastLength = 0;
for (var i = UnpOldTable.length; i--;) UnpOldTable[i] = 0;
// read in Huffman tables
RarReadTables(bstream);
while (true) {
var num = RarDecodeNumber(bstream, LD);
if (num < 256) {
rBuffer.insertByte(num);
continue;
}
if (num >= 271) {
var Length = rLDecode[num -= 271] + 3;
if ((Bits = rLBits[num]) > 0) {
Length += bstream.readBits(Bits);
}
var DistNumber = RarDecodeNumber(bstream, DD);
var Distance = DDecode[DistNumber]+1;
if ((Bits = DBits[DistNumber]) > 0) {
if (DistNumber > 9) {
if (Bits > 4) {
Distance += ((bstream.getBits() >>> (20 - Bits)) << 4);
bstream.readBits(Bits - 4);
//todo: check this
}
if (lowDistRepCount > 0) {
lowDistRepCount--;
Distance += prevLowDist;
} else {
var LowDist = RarDecodeNumber(bstream, LDD);
if (LowDist == 16) {
lowDistRepCount = rLOW_DIST_REP_COUNT - 1;
Distance += prevLowDist;
} else {
Distance += LowDist;
prevLowDist = LowDist;
}
}
} else {
Distance += bstream.readBits(Bits);
}
}
if (Distance >= 0x2000) {
Length++;
if (Distance >= 0x40000) {
Length++;
}
}
RarInsertOldDist(Distance);
RarInsertLastMatch(Length, Distance);
RarCopyString(Length, Distance);
continue;
}
if (num == 256) {
if (!RarReadEndOfBlock(bstream)) break;
continue;
}
if (num == 257) {
//console.log("READVMCODE");
if (!RarReadVMCode(bstream)) break;
continue;
}
if (num == 258) {
if (lastLength != 0) {
RarCopyString(lastLength, lastDist);
}
continue;
}
if (num < 263) {
var DistNum = num - 259;
var Distance = rOldDist[DistNum];
for (var I = DistNum; I > 0; I--) {
rOldDist[I] = rOldDist[I-1];
}
rOldDist[0] = Distance;
var LengthNumber = RarDecodeNumber(bstream, RD);
var Length = rLDecode[LengthNumber] + 2;
if ((Bits = rLBits[LengthNumber]) > 0) {
Length += bstream.readBits(Bits);
}
RarInsertLastMatch(Length, Distance);
RarCopyString(Length, Distance);
continue;
}
if (num < 272) {
var Distance = rSDDecode[num -= 263] + 1;
if ((Bits = rSDBits[num]) > 0) {
Distance += bstream.readBits(Bits);
}
RarInsertOldDist(Distance);
RarInsertLastMatch(2, Distance);
RarCopyString(2, Distance);
continue;
}
}
RarUpdateProgress()
}
function RarReadEndOfBlock(bstream) {
RarUpdateProgress()
var NewTable = false, NewFile = false;
if (bstream.readBits(1)) {
NewTable = true;
} else {
NewFile = true;
NewTable = !!bstream.readBits(1);
}
//tablesRead = !NewTable;
return !(NewFile || NewTable && !RarReadTables(bstream));
}
function RarReadVMCode(bstream) {
var FirstByte = bstream.readBits(8);
var Length = (FirstByte & 7) + 1;
if (Length == 7) {
Length = bstream.readBits(8) + 7;
} else if(Length == 8) {
Length = bstream.readBits(16);
}
var vmCode = [];
for(var I = 0; I < Length; I++) {
//do something here with cheking readbuf
vmCode.push(bstream.readBits(8));
}
return RarAddVMCode(FirstByte, vmCode, Length);
}
function RarAddVMCode(firstByte, vmCode, length) {
//console.log(vmCode);
if (vmCode.length > 0) {
info("Error! RarVM not supported yet!");
}
return true;
}
function RarInsertLastMatch(length, distance) {
lastDist = distance;
lastLength = length;
}
function RarInsertOldDist(distance) {
rOldDist.splice(3,1);
rOldDist.splice(0,0,distance);
}
//this is the real function, the other one is for debugging
function RarCopyString(length, distance) {
var destPtr = rBuffer.ptr - distance;
if(destPtr < 0){
var l = rOldBuffers.length;
while(destPtr < 0){
destPtr = rOldBuffers[--l].data.length + destPtr;
}
//TODO: lets hope that it never needs to read beyond file boundaries
while(length--) rBuffer.insertByte(rOldBuffers[l].data[destPtr++]);
}
if (length > distance) {
while(length--) rBuffer.insertByte(rBuffer.data[destPtr++]);
} else {
rBuffer.insertBytes(rBuffer.data.subarray(destPtr, destPtr + length));
}
}
var rOldBuffers = []
// v must be a valid RarVolume
function unpack(v) {
// TODO: implement what happens when unpVer is < 15
var Ver = v.header.unpVer <= 15 ? 15 : v.header.unpVer,
Solid = v.header.LHD_SOLID,
bstream = new bitjs.io.BitStream(v.fileData.buffer, true /* rtl */, v.fileData.byteOffset, v.fileData.byteLength );
rBuffer = new bitjs.io.ByteBuffer(v.header.unpackedSize);
info("Unpacking " + v.filename+" RAR v" + Ver);
switch(Ver) {
case 15: // rar 1.5 compression
Unpack15(bstream, Solid);
break;
case 20: // rar 2.x compression
case 26: // files larger than 2GB
Unpack20(bstream, Solid);
break;
case 29: // rar 3.x compression
case 36: // alternative hash
Unpack29(bstream, Solid);
break;
} // switch(method)
rOldBuffers.push(rBuffer);
//TODO: clear these old buffers when there's over 4MB of history
return rBuffer.data;
}
// bstream is a bit stream
var RarLocalFile = function(bstream) {
this.header = new RarVolumeHeader(bstream);
this.filename = this.header.filename;
if (this.header.headType != FILE_HEAD && this.header.headType != ENDARC_HEAD) {
this.isValid = false;
info("Error! RAR Volume did not include a FILE_HEAD header ");
}
else {
// read in the compressed data
this.fileData = null;
if (this.header.packSize > 0) {
this.fileData = bstream.readBytes(this.header.packSize);
this.isValid = true;
}
}
};
RarLocalFile.prototype.unrar = function() {
if (!this.header.flags.LHD_SPLIT_BEFORE) {
// unstore file
if (this.header.method == 0x30) {
info("Unstore "+this.filename);
this.isValid = true;
currentBytesUnarchivedInFile += this.fileData.length;
currentBytesUnarchived += this.fileData.length;
// Create a new buffer and copy it over.
var len = this.header.packSize;
var newBuffer = new bitjs.io.ByteBuffer(len);
newBuffer.insertBytes(this.fileData);
this.fileData = newBuffer.data;
} else {
this.isValid = true;
this.fileData = unpack(this);
}
}
}
var unrar = function(arrayBuffer) {
currentFilename = "";
currentFileNumber = 0;
currentBytesUnarchivedInFile = 0;
currentBytesUnarchived = 0;
totalUncompressedBytesInArchive = 0;
totalFilesInArchive = 0;
postMessage(new bitjs.archive.UnarchiveStartEvent());
var bstream = new bitjs.io.BitStream(arrayBuffer, false /* rtl */);
var header = new RarVolumeHeader(bstream);
if (header.crc == 0x6152 &&
header.headType == 0x72 &&
header.flags.value == 0x1A21 &&
header.headSize == 7)
{
info("Found RAR signature");
var mhead = new RarVolumeHeader(bstream);
if (mhead.headType != MAIN_HEAD) {
info("Error! RAR did not include a MAIN_HEAD header");
} else {
var localFiles = [],
localFile = null;
do {
try {
localFile = new RarLocalFile(bstream);
info("RAR localFile isValid=" + localFile.isValid + ", volume packSize=" + localFile.header.packSize);
if (localFile && localFile.isValid && localFile.header.packSize > 0) {
totalUncompressedBytesInArchive += localFile.header.unpackedSize;
localFiles.push(localFile);
} else if (localFile.header.packSize == 0 && localFile.header.unpackedSize == 0) {
localFile.isValid = true;
}
} catch(err) {
break;
}
//info("bstream" + bstream.bytePtr+"/"+bstream.bytes.length);
} while( localFile.isValid );
totalFilesInArchive = localFiles.length;
// now we have all information but things are unpacked
// TODO: unpack
localFiles = localFiles.sort(function(a,b) {
var aname = a.filename.toLowerCase();
var bname = b.filename.toLowerCase();
return aname > bname ? 1 : -1;
});
info(localFiles.map(function(a){return a.filename}).join(', '));
for (var i = 0; i < localFiles.length; ++i) {
var localfile = localFiles[i];
// update progress
currentFilename = localfile.header.filename;
currentBytesUnarchivedInFile = 0;
// actually do the unzipping
localfile.unrar();
if (localfile.isValid) {
postMessage(new bitjs.archive.UnarchiveExtractEvent(localfile));
postProgress();
}
}
postProgress();
}
}
else {
err("Invalid RAR file");
}
postMessage(new bitjs.archive.UnarchiveFinishEvent());
};
// event.data.file has the ArrayBuffer.
onmessage = function(event) {
var ab = event.data.file;
unrar(ab, true);
};

@ -0,0 +1,168 @@
/**
* untar.js
*
* Copyright(c) 2011 Google Inc.
*
* Reference Documentation:
*
* TAR format: http://www.gnu.org/software/automake/manual/tar/Standard.html
*/
// This file expects to be invoked as a Worker (see onmessage below).
importScripts('io.js');
importScripts('archive.js');
// Progress variables.
var currentFilename = "";
var currentFileNumber = 0;
var currentBytesUnarchivedInFile = 0;
var currentBytesUnarchived = 0;
var totalUncompressedBytesInArchive = 0;
var totalFilesInArchive = 0;
// Helper functions.
var info = function(str) {
postMessage(new bitjs.archive.UnarchiveInfoEvent(str));
};
var err = function(str) {
postMessage(new bitjs.archive.UnarchiveErrorEvent(str));
};
var postProgress = function() {
postMessage(new bitjs.archive.UnarchiveProgressEvent(
currentFilename,
currentFileNumber,
currentBytesUnarchivedInFile,
currentBytesUnarchived,
totalUncompressedBytesInArchive,
totalFilesInArchive));
};
// Removes all characters from the first zero-byte in the string onwards.
var readCleanString = function(bstr, numBytes) {
var str = bstr.readString(numBytes);
var zIndex = str.indexOf(String.fromCharCode(0));
return zIndex != -1 ? str.substr(0, zIndex) : str;
};
// takes a ByteStream and parses out the local file information
var TarLocalFile = function(bstream) {
this.isValid = false;
// Read in the header block
this.name = readCleanString(bstream, 100);
this.mode = readCleanString(bstream, 8);
this.uid = readCleanString(bstream, 8);
this.gid = readCleanString(bstream, 8);
this.size = parseInt(readCleanString(bstream, 12), 8);
this.mtime = readCleanString(bstream, 12);
this.chksum = readCleanString(bstream, 8);
this.typeflag = readCleanString(bstream, 1);
this.linkname = readCleanString(bstream, 100);
this.maybeMagic = readCleanString(bstream, 6);
if (this.maybeMagic == "ustar") {
this.version = readCleanString(bstream, 2);
this.uname = readCleanString(bstream, 32);
this.gname = readCleanString(bstream, 32);
this.devmajor = readCleanString(bstream, 8);
this.devminor = readCleanString(bstream, 8);
this.prefix = readCleanString(bstream, 155);
if (this.prefix.length) {
this.name = this.prefix + this.name;
}
bstream.readBytes(12); // 512 - 500
} else {
bstream.readBytes(255); // 512 - 257
}
// Done header, now rest of blocks are the file contents.
this.filename = this.name;
this.fileData = null;
info("Untarring file '" + this.filename + "'");
info(" size = " + this.size);
info(" typeflag = " + this.typeflag);
// A regular file.
if (this.typeflag == 0) {
info(" This is a regular file.");
var sizeInBytes = parseInt(this.size);
this.fileData = new Uint8Array(bstream.bytes.buffer, bstream.ptr, this.size);
if (this.name.length > 0 && this.size > 0 && this.fileData && this.fileData.buffer) {
this.isValid = true;
}
bstream.readBytes(this.size);
// Round up to 512-byte blocks.
var remaining = 512 - this.size % 512;
if (remaining > 0 && remaining < 512) {
bstream.readBytes(remaining);
}
} else if (this.typeflag == 5) {
info(" This is a directory.")
}
};
// Takes an ArrayBuffer of a tar file in
// returns null on error
// returns an array of DecompressedFile objects on success
var untar = function(arrayBuffer) {
currentFilename = "";
currentFileNumber = 0;
currentBytesUnarchivedInFile = 0;
currentBytesUnarchived = 0;
totalUncompressedBytesInArchive = 0;
totalFilesInArchive = 0;
postMessage(new bitjs.archive.UnarchiveStartEvent());
var bstream = new bitjs.io.ByteStream(arrayBuffer);
var localFiles = [];
// While we don't encounter an empty block, keep making TarLocalFiles.
while (bstream.peekNumber(4) != 0) {
var oneLocalFile = new TarLocalFile(bstream);
if (oneLocalFile && oneLocalFile.isValid) {
localFiles.push(oneLocalFile);
totalUncompressedBytesInArchive += oneLocalFile.size;
}
}
totalFilesInArchive = localFiles.length;
// got all local files, now sort them
localFiles.sort(function(a,b) {
var aname = a.filename.toLowerCase();
var bname = b.filename.toLowerCase();
return aname > bname ? 1 : -1;
});
// report # files and total length
if (localFiles.length > 0) {
postProgress();
}
// now do the shipping of each file
for (var i = 0; i < localFiles.length; ++i) {
var localfile = localFiles[i];
info("Sending file '" + localfile.filename + "' up");
// update progress
currentFilename = localfile.filename;
currentFileNumber = i;
currentBytesUnarchivedInFile = localfile.size;
currentBytesUnarchived += localfile.size;
postMessage(new bitjs.archive.UnarchiveExtractEvent(localfile));
postProgress();
}
postProgress();
postMessage(new bitjs.archive.UnarchiveFinishEvent());
};
// event.data.file has the ArrayBuffer.
onmessage = function(event) {
var ab = event.data.file;
untar(ab);
};

@ -0,0 +1,621 @@
/**
* unzip.js
*
* Copyright(c) 2011 Google Inc.
* Copyright(c) 2011 antimatter15
*
* Reference Documentation:
*
* ZIP format: http://www.pkware.com/documents/casestudies/APPNOTE.TXT
* DEFLATE format: http://tools.ietf.org/html/rfc1951
*/
/* global bitjs, importScripts, Uint8Array */
// This file expects to be invoked as a Worker (see onmessage below).
importScripts("io.js");
importScripts("archive.js");
// Progress variables.
var currentFilename = "";
var currentFileNumber = 0;
var currentBytesUnarchivedInFile = 0;
var currentBytesUnarchived = 0;
var totalUncompressedBytesInArchive = 0;
var totalFilesInArchive = 0;
// Helper functions.
var info = function(str) {
postMessage(new bitjs.archive.UnarchiveInfoEvent(str));
};
var err = function(str) {
postMessage(new bitjs.archive.UnarchiveErrorEvent(str));
};
var postProgress = function() {
postMessage(new bitjs.archive.UnarchiveProgressEvent(
currentFilename,
currentFileNumber,
currentBytesUnarchivedInFile,
currentBytesUnarchived,
totalUncompressedBytesInArchive,
totalFilesInArchive));
};
var zLocalFileHeaderSignature = 0x04034b50;
var zArchiveExtraDataSignature = 0x08064b50;
var zCentralFileHeaderSignature = 0x02014b50;
var zDigitalSignatureSignature = 0x05054b50;
//var zEndOfCentralDirSignature = 0x06064b50;
//var zEndOfCentralDirLocatorSignature = 0x07064b50;
// takes a ByteStream and parses out the local file information
var ZipLocalFile = function(bstream) {
if (typeof bstream !== typeof {} || !bstream.readNumber || typeof bstream.readNumber != typeof function() {} ) {
return null;
}
bstream.readNumber(4); // swallow signature
this.version = bstream.readNumber(2);
this.generalPurpose = bstream.readNumber(2);
this.compressionMethod = bstream.readNumber(2);
this.lastModFileTime = bstream.readNumber(2);
this.lastModFileDate = bstream.readNumber(2);
this.crc32 = bstream.readNumber(4);
this.compressedSize = bstream.readNumber(4);
this.uncompressedSize = bstream.readNumber(4);
this.fileNameLength = bstream.readNumber(2);
this.extraFieldLength = bstream.readNumber(2);
this.filename = null;
if (this.fileNameLength > 0) {
this.filename = bstream.readString(this.fileNameLength);
}
info("Zip Local File Header:");
info(" version=" + this.version);
info(" general purpose=" + this.generalPurpose);
info(" compression method=" + this.compressionMethod);
info(" last mod file time=" + this.lastModFileTime);
info(" last mod file date=" + this.lastModFileDate);
info(" crc32=" + this.crc32);
info(" compressed size=" + this.compressedSize);
info(" uncompressed size=" + this.uncompressedSize);
info(" file name length=" + this.fileNameLength);
info(" extra field length=" + this.extraFieldLength);
info(" filename = '" + this.filename + "'");
this.extraField = null;
if (this.extraFieldLength > 0) {
this.extraField = bstream.readString(this.extraFieldLength);
info(" extra field=" + this.extraField);
}
// read in the compressed data
this.fileData = null;
if (this.compressedSize > 0) {
this.fileData = new Uint8Array(bstream.bytes.buffer, bstream.ptr, this.compressedSize);
bstream.ptr += this.compressedSize;
}
// TODO: deal with data descriptor if present (we currently assume no data descriptor!)
// "This descriptor exists only if bit 3 of the general purpose bit flag is set"
// But how do you figure out how big the file data is if you don't know the compressedSize
// from the header?!?
if ((this.generalPurpose & bitjs.BIT[3]) !== 0) {
this.crc32 = bstream.readNumber(4);
this.compressedSize = bstream.readNumber(4);
this.uncompressedSize = bstream.readNumber(4);
}
};
// determine what kind of compressed data we have and decompress
ZipLocalFile.prototype.unzip = function() {
// Zip Version 1.0, no compression (store only)
if (this.compressionMethod === 0 ) {
info("ZIP v" + this.version + ", store only: " + this.filename + " (" + this.compressedSize + " bytes)");
currentBytesUnarchivedInFile = this.compressedSize;
currentBytesUnarchived += this.compressedSize;
}
// version == 20, compression method == 8 (DEFLATE)
else if (this.compressionMethod === 8) {
info("ZIP v2.0, DEFLATE: " + this.filename + " (" + this.compressedSize + " bytes)");
this.fileData = inflate(this.fileData, this.uncompressedSize);
}
else {
err("UNSUPPORTED VERSION/FORMAT: ZIP v" + this.version + ", compression method=" + this.compressionMethod + ": " + this.filename + " (" + this.compressedSize + " bytes)");
this.fileData = null;
}
};
// Takes an ArrayBuffer of a zip file in
// returns null on error
// returns an array of DecompressedFile objects on success
var unzip = function(arrayBuffer) {
postMessage(new bitjs.archive.UnarchiveStartEvent());
currentFilename = "";
currentFileNumber = 0;
currentBytesUnarchivedInFile = 0;
currentBytesUnarchived = 0;
totalUncompressedBytesInArchive = 0;
totalFilesInArchive = 0;
currentBytesUnarchived = 0;
var bstream = new bitjs.io.ByteStream(arrayBuffer);
// detect local file header signature or return null
if (bstream.peekNumber(4) === zLocalFileHeaderSignature) {
var localFiles = [];
// loop until we don't see any more local files
while (bstream.peekNumber(4) === zLocalFileHeaderSignature) {
var oneLocalFile = new ZipLocalFile(bstream);
// this should strip out directories/folders
if (oneLocalFile && oneLocalFile.uncompressedSize > 0 && oneLocalFile.fileData) {
localFiles.push(oneLocalFile);
totalUncompressedBytesInArchive += oneLocalFile.uncompressedSize;
}
}
totalFilesInArchive = localFiles.length;
// got all local files, now sort them
localFiles.sort(function(a, b) {
var aname = a.filename.toLowerCase();
var bname = b.filename.toLowerCase();
return aname > bname ? 1 : -1;
});
// archive extra data record
if (bstream.peekNumber(4) === zArchiveExtraDataSignature) {
info(" Found an Archive Extra Data Signature");
// skipping this record for now
bstream.readNumber(4);
var archiveExtraFieldLength = bstream.readNumber(4);
bstream.readString(archiveExtraFieldLength);
}
// central directory structure
// TODO: handle the rest of the structures (Zip64 stuff)
if (bstream.peekNumber(4) === zCentralFileHeaderSignature) {
info(" Found a Central File Header");
// read all file headers
while (bstream.peekNumber(4) === zCentralFileHeaderSignature) {
bstream.readNumber(4); // signature
bstream.readNumber(2); // version made by
bstream.readNumber(2); // version needed to extract
bstream.readNumber(2); // general purpose bit flag
bstream.readNumber(2); // compression method
bstream.readNumber(2); // last mod file time
bstream.readNumber(2); // last mod file date
bstream.readNumber(4); // crc32
bstream.readNumber(4); // compressed size
bstream.readNumber(4); // uncompressed size
var fileNameLength = bstream.readNumber(2); // file name length
var extraFieldLength = bstream.readNumber(2); // extra field length
var fileCommentLength = bstream.readNumber(2); // file comment length
bstream.readNumber(2); // disk number start
bstream.readNumber(2); // internal file attributes
bstream.readNumber(4); // external file attributes
bstream.readNumber(4); // relative offset of local header
bstream.readString(fileNameLength); // file name
bstream.readString(extraFieldLength); // extra field
bstream.readString(fileCommentLength); // file comment
}
}
// digital signature
if (bstream.peekNumber(4) === zDigitalSignatureSignature) {
info(" Found a Digital Signature");
bstream.readNumber(4);
var sizeOfSignature = bstream.readNumber(2);
bstream.readString(sizeOfSignature); // digital signature data
}
// report # files and total length
if (localFiles.length > 0) {
postProgress();
}
// now do the unzipping of each file
for (var i = 0; i < localFiles.length; ++i) {
var localfile = localFiles[i];
// update progress
currentFilename = localfile.filename;
currentFileNumber = i;
currentBytesUnarchivedInFile = 0;
// actually do the unzipping
localfile.unzip();
if (localfile.fileData !== null) {
postMessage(new bitjs.archive.UnarchiveExtractEvent(localfile));
postProgress();
}
}
postProgress();
postMessage(new bitjs.archive.UnarchiveFinishEvent());
}
};
// returns a table of Huffman codes
// each entry's index is its code and its value is a JavaScript object
// containing {length: 6, symbol: X}
function getHuffmanCodes(bitLengths) {
// ensure bitLengths is an array containing at least one element
if (typeof bitLengths !== typeof [] || bitLengths.length < 1) {
err("Error! getHuffmanCodes() called with an invalid array");
return null;
}
// Reference: http://tools.ietf.org/html/rfc1951#page-8
var numLengths = bitLengths.length,
blCount = [],
MAX_BITS = 1;
// Step 1: count up how many codes of each length we have
for (var i = 0; i < numLengths; ++i) {
var len = bitLengths[i];
// test to ensure each bit length is a positive, non-zero number
if (typeof len !== typeof 1 || len < 0) {
err("bitLengths contained an invalid number in getHuffmanCodes(): " + len + " of type " + (typeof len));
return null;
}
// increment the appropriate bitlength count
if (blCount[len] === undefined) blCount[len] = 0;
// a length of zero means this symbol is not participating in the huffman coding
if (len > 0) blCount[len]++;
if (len > MAX_BITS) MAX_BITS = len;
}
// Step 2: Find the numerical value of the smallest code for each code length
var nextCode = [],
code = 0;
for (var bits = 1; bits <= MAX_BITS; ++bits) {
var len = bits-1;
// ensure undefined lengths are zero
if (blCount[len] == undefined) blCount[len] = 0;
code = (code + blCount[bits-1]) << 1;
nextCode[bits] = code;
}
// Step 3: Assign numerical values to all codes
var table = {}, tableLength = 0;
for (var n = 0; n < numLengths; ++n) {
var len = bitLengths[n];
if (len !== 0) {
table[nextCode[len]] = { length: len, symbol: n }; //, bitstring: binaryValueToString(nextCode[len],len) };
tableLength++;
nextCode[len]++;
}
}
table.maxLength = tableLength;
return table;
}
/*
The Huffman codes for the two alphabets are fixed, and are not
represented explicitly in the data. The Huffman code lengths
for the literal/length alphabet are:
Lit Value Bits Codes
--------- ---- -----
0 - 143 8 00110000 through
10111111
144 - 255 9 110010000 through
111111111
256 - 279 7 0000000 through
0010111
280 - 287 8 11000000 through
11000111
*/
// fixed Huffman codes go from 7-9 bits, so we need an array whose index can hold up to 9 bits
var fixedHCtoLiteral = null;
var fixedHCtoDistance = null;
function getFixedLiteralTable() {
// create once
if (!fixedHCtoLiteral) {
var bitlengths = new Array(288);
for (var i = 0; i <= 143; ++i) bitlengths[i] = 8;
for (var i = 144; i <= 255; ++i) bitlengths[i] = 9;
for (var i = 256; i <= 279; ++i) bitlengths[i] = 7;
for (var i = 280; i <= 287; ++i) bitlengths[i] = 8;
// get huffman code table
fixedHCtoLiteral = getHuffmanCodes(bitlengths);
}
return fixedHCtoLiteral;
}
function getFixedDistanceTable() {
// create once
if (!fixedHCtoDistance) {
var bitlengths = new Array(32);
for (var i = 0; i < 32; ++i) {
bitlengths[i] = 5;
}
// get huffman code table
fixedHCtoDistance = getHuffmanCodes(bitlengths);
}
return fixedHCtoDistance;
}
// extract one bit at a time until we find a matching Huffman Code
// then return that symbol
function decodeSymbol(bstream, hcTable) {
var code = 0, len = 0;
// var match = false;
// loop until we match
for (;;) {
// read in next bit
var bit = bstream.readBits(1);
code = (code<<1) | bit;
++len;
// check against Huffman Code table and break if found
if (hcTable.hasOwnProperty(code) && hcTable[code].length == len) {
break;
}
if (len > hcTable.maxLength) {
err("Bit stream out of sync, didn't find a Huffman Code, length was " + len +
" and table only max code length of " + hcTable.maxLength);
break;
}
}
return hcTable[code].symbol;
}
var CodeLengthCodeOrder = [16, 17, 18, 0, 8, 7, 9, 6, 10, 5, 11, 4, 12, 3, 13, 2, 14, 1, 15];
/*
Extra Extra Extra
Code Bits Length(s) Code Bits Lengths Code Bits Length(s)
---- ---- ------ ---- ---- ------- ---- ---- -------
257 0 3 267 1 15,16 277 4 67-82
258 0 4 268 1 17,18 278 4 83-98
259 0 5 269 2 19-22 279 4 99-114
260 0 6 270 2 23-26 280 4 115-130
261 0 7 271 2 27-30 281 5 131-162
262 0 8 272 2 31-34 282 5 163-194
263 0 9 273 3 35-42 283 5 195-226
264 0 10 274 3 43-50 284 5 227-257
265 1 11,12 275 3 51-58 285 0 258
266 1 13,14 276 3 59-66
*/
var LengthLookupTable = [
[0, 3], [0, 4], [0, 5], [0, 6],
[0, 7], [0, 8], [0, 9], [0, 10],
[1, 11], [1, 13], [1, 15], [1, 17],
[2, 19], [2, 23], [2, 27], [2, 31],
[3, 35], [3, 43], [3, 51], [3, 59],
[4, 67], [4, 83], [4, 99], [4, 115],
[5, 131], [5, 163], [5, 195], [5, 227],
[0, 258]
];
/*
Extra Extra Extra
Code Bits Dist Code Bits Dist Code Bits Distance
---- ---- ---- ---- ---- ------ ---- ---- --------
0 0 1 10 4 33-48 20 9 1025-1536
1 0 2 11 4 49-64 21 9 1537-2048
2 0 3 12 5 65-96 22 10 2049-3072
3 0 4 13 5 97-128 23 10 3073-4096
4 1 5,6 14 6 129-192 24 11 4097-6144
5 1 7,8 15 6 193-256 25 11 6145-8192
6 2 9-12 16 7 257-384 26 12 8193-12288
7 2 13-16 17 7 385-512 27 12 12289-16384
8 3 17-24 18 8 513-768 28 13 16385-24576
9 3 25-32 19 8 769-1024 29 13 24577-32768
*/
var DistLookupTable = [
[0, 1], [0, 2], [0, 3], [0, 4],
[1, 5], [1, 7],
[2, 9], [2, 13],
[3, 17], [3, 25],
[4, 33], [4, 49],
[5, 65], [5, 97],
[6, 129], [6, 193],
[7, 257], [7, 385],
[8, 513], [8, 769],
[9, 1025], [9, 1537],
[10, 2049], [10, 3073],
[11, 4097], [11, 6145],
[12, 8193], [12, 12289],
[13, 16385], [13, 24577]
];
function inflateBlockData(bstream, hcLiteralTable, hcDistanceTable, buffer) {
/*
loop (until end of block code recognized)
decode literal/length value from input stream
if value < 256
copy value (literal byte) to output stream
otherwise
if value = end of block (256)
break from loop
otherwise (value = 257..285)
decode distance from input stream
move backwards distance bytes in the output
stream, and copy length bytes from this
position to the output stream.
*/
var numSymbols = 0, blockSize = 0;
for (;;) {
var symbol = decodeSymbol(bstream, hcLiteralTable);
++numSymbols;
if (symbol < 256) {
// copy literal byte to output
buffer.insertByte(symbol);
blockSize++;
}
else {
// end of block reached
if (symbol === 256) {
break;
}
else {
var lengthLookup = LengthLookupTable[symbol-257],
length = lengthLookup[1] + bstream.readBits(lengthLookup[0]),
distLookup = DistLookupTable[decodeSymbol(bstream, hcDistanceTable)],
distance = distLookup[1] + bstream.readBits(distLookup[0]);
// now apply length and distance appropriately and copy to output
// TODO: check that backward distance < data.length?
// http://tools.ietf.org/html/rfc1951#page-11
// "Note also that the referenced string may overlap the current
// position; for example, if the last 2 bytes decoded have values
// X and Y, a string reference with <length = 5, distance = 2>
// adds X,Y,X,Y,X to the output stream."
//
// loop for each character
var ch = buffer.ptr - distance;
blockSize += length;
if(length > distance) {
var data = buffer.data;
while (length--) {
buffer.insertByte(data[ch++]);
}
} else {
buffer.insertBytes(buffer.data.subarray(ch, ch + length))
}
} // length-distance pair
} // length-distance pair or end-of-block
} // loop until we reach end of block
return blockSize;
}
// {Uint8Array} compressedData A Uint8Array of the compressed file data.
// compression method 8
// deflate: http://tools.ietf.org/html/rfc1951
function inflate(compressedData, numDecompressedBytes) {
// Bit stream representing the compressed data.
var bstream = new bitjs.io.BitStream(compressedData.buffer,
false /* rtl */,
compressedData.byteOffset,
compressedData.byteLength);
var buffer = new bitjs.io.ByteBuffer(numDecompressedBytes);
var numBlocks = 0;
var blockSize = 0;
// block format: http://tools.ietf.org/html/rfc1951#page-9
do {
var bFinal = bstream.readBits(1);
var bType = bstream.readBits(2);
blockSize = 0;
++numBlocks;
// no compression
if (bType == 0) {
// skip remaining bits in this byte
while (bstream.bitPtr != 0) bstream.readBits(1);
var len = bstream.readBits(16),
nlen = bstream.readBits(16);
// TODO: check if nlen is the ones-complement of len?
if(len > 0) buffer.insertBytes(bstream.readBytes(len));
blockSize = len;
}
// fixed Huffman codes
else if(bType === 1) {
blockSize = inflateBlockData(bstream, getFixedLiteralTable(), getFixedDistanceTable(), buffer);
}
// dynamic Huffman codes
else if(bType === 2) {
var numLiteralLengthCodes = bstream.readBits(5) + 257;
var numDistanceCodes = bstream.readBits(5) + 1,
numCodeLengthCodes = bstream.readBits(4) + 4;
// populate the array of code length codes (first de-compaction)
var codeLengthsCodeLengths = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0];
for (var i = 0; i < numCodeLengthCodes; ++i) {
codeLengthsCodeLengths[ CodeLengthCodeOrder[i] ] = bstream.readBits(3);
}
// get the Huffman Codes for the code lengths
var codeLengthsCodes = getHuffmanCodes(codeLengthsCodeLengths);
// now follow this mapping
/*
0 - 15: Represent code lengths of 0 - 15
16: Copy the previous code length 3 - 6 times.
The next 2 bits indicate repeat length
(0 = 3, ... , 3 = 6)
Example: Codes 8, 16 (+2 bits 11),
16 (+2 bits 10) will expand to
12 code lengths of 8 (1 + 6 + 5)
17: Repeat a code length of 0 for 3 - 10 times.
(3 bits of length)
18: Repeat a code length of 0 for 11 - 138 times
(7 bits of length)
*/
// to generate the true code lengths of the Huffman Codes for the literal
// and distance tables together
var literalCodeLengths = [];
var prevCodeLength = 0;
while (literalCodeLengths.length < numLiteralLengthCodes + numDistanceCodes) {
var symbol = decodeSymbol(bstream, codeLengthsCodes);
if (symbol <= 15) {
literalCodeLengths.push(symbol);
prevCodeLength = symbol;
}
else if (symbol === 16) {
var repeat = bstream.readBits(2) + 3;
while (repeat--) {
literalCodeLengths.push(prevCodeLength);
}
}
else if (symbol === 17) {
var repeat = bstream.readBits(3) + 3;
while (repeat--) {
literalCodeLengths.push(0);
}
}
else if (symbol === 18) {
var repeat = bstream.readBits(7) + 11;
while (repeat--) {
literalCodeLengths.push(0);
}
}
}
// now split the distance code lengths out of the literal code array
var distanceCodeLengths = literalCodeLengths.splice(numLiteralLengthCodes, numDistanceCodes);
// now generate the true Huffman Code tables using these code lengths
var hcLiteralTable = getHuffmanCodes(literalCodeLengths),
hcDistanceTable = getHuffmanCodes(distanceCodeLengths);
blockSize = inflateBlockData(bstream, hcLiteralTable, hcDistanceTable, buffer);
}
// error
else {
err("Error! Encountered deflate block of type 3");
return null;
}
// update progress
currentBytesUnarchivedInFile += blockSize;
currentBytesUnarchived += blockSize;
postProgress();
} while (bFinal !== 1);
// we are done reading blocks if the bFinal bit was set for this block
// return the buffer data bytes
return buffer.data;
}
// event.data.file has the ArrayBuffer.
onmessage = function(event) {
unzip(event.data.file, true);
};

@ -39,7 +39,7 @@
<p class="title">{{entry.title|shortentitle}}</p>
<p class="author">
{% for author in entry.authors %}
<a href="{{url_for('author', book_id=author.id) }}">{{author.name}}</a>
<a href="{{url_for('author', book_id=author.id) }}">{{author.name.replace('|',',')}}</a>
{% if not loop.last %}
&amp;
{% endif %}
@ -64,9 +64,9 @@
</div>
</div>
{% if other_books is not none %}
{% if other_books %}
<div class="discover">
<h3>{{_("More by")}} {{ author.name|safe }}</h3>
<h3>{{_("More by")}} {{ author.name.replace('|',',')|safe }}</h3>
<div class="row">
{% for entry in other_books %}
<div class="col-sm-3 col-lg-2 col-xs-6 book">
@ -80,7 +80,7 @@
<p class="author">
{% for author in entry.authors %}
<a href="https://www.goodreads.com/author/show/{{ author.gid }}" target="_blank" rel="noopener">
{{author.name}}
{{author.name.replace('|',',')}}
</a>
{% if not loop.last %}
&amp;

@ -1,7 +1,7 @@
{% extends "layout.html" %}
{% block body %}
{% if book %}
<form role="form" action="{{ url_for('edit_book', book_id=book.id) }}" method="post">
<form role="form" action="{{ url_for('edit_book', book_id=book.id) }}" method="post" enctype="multipart/form-data">
<div class="col-sm-3 col-lg-3 col-xs-12">
<div class="cover">
@ -115,7 +115,15 @@
</div>
{% endfor %}
{% endif %}
{% if g.user.role_upload() or g.user.role_admin()%}
{% if g.allow_upload %}
<div role="group" aria-label="Upload new book format">
<label class="btn btn-default btn-file" for="btn-upload-format">{{ _('Upload format') }}</label>
<div class="upload-format-input-text" id="upload-format"></div>
<input id="btn-upload-format" name="btn-upload-format" type="file">
</div>
{% endif %}
{% endif %}
<div class="checkbox">
<label>

@ -22,7 +22,7 @@
</button>
{% for format in entry.data %}
<a href="{{ url_for('get_download_link_ext', book_id=entry.id, book_format=format.format|lower, anyname=entry.id|string+'.'+format.format) }}" id="btnGroupDrop1{{format.format|lower}}" class="btn btn-primary" role="button">
<span class="glyphicon glyphicon-download"></span>{{format.format}}
<span class="glyphicon glyphicon-download"></span>{{format.format}} ({{ format.uncompressed_size|filesizeformat }})
</a>
{% endfor %}
{% else %}
@ -32,7 +32,7 @@
</button>
<ul class="dropdown-menu" aria-labelledby="btnGroupDrop1">
{% for format in entry.data %}
<li><a href="{{ url_for('get_download_link_ext', book_id=entry.id, book_format=format.format|lower, anyname=entry.id|string+'.'+format.format) }}">{{format.format}}</a></li>
<li><a href="{{ url_for('get_download_link_ext', book_id=entry.id, book_format=format.format|lower, anyname=entry.id|string+'.'+format.format) }}">{{format.format}} ({{ format.uncompressed_size|filesizeformat }})</a></li>
{% endfor %}
</ul>
{% endif %}
@ -62,7 +62,7 @@
<h2>{{entry.title}}</h2>
<p class="author">
{% for author in entry.authors %}
<a href="{{url_for('author', book_id=author.id ) }}">{{author.name}}</a>
<a href="{{url_for('author', book_id=author.id ) }}">{{author.name.replace('|',',')}}</a>
{% if not loop.last %}
&amp;
{% endif %}

@ -33,7 +33,7 @@
</div>
<button type="submit" name="submit" value="submit" class="btn btn-default">{{_('Save settings')}}</button>
<button type="submit" name="test" value="test" class="btn btn-default">{{_('Save settings and send Test E-Mail')}}</button>
<a href="{{ url_for('admin') }}" class="btn btn-default">{{_('Back')}}</a>
<a href="{{ url_for('admin') }}" id="back" class="btn btn-default">{{_('Back')}}</a>
</form>
</div>

@ -64,8 +64,7 @@
<entry>
<title>{{entry.name}}</title>
<id>{{ url_for(folder, book_id=entry.id) }}</id>
<link type="application/atom+xml;profile=opds-catalog;type=feed;kind=acquisition" href="{{url_for(folder, book_id=entry.id)}}"/>
<link type="application/atom+xml" href="{{url_for(folder, book_id=entry.id)}}" rel="subsection"/>
<link rel="subsection" type="application/atom+xml;profile=opds-catalog" href="{{url_for(folder, book_id=entry.id)}}"/>
</entry>
{% endfor %}
</feed>

@ -6,7 +6,7 @@
<div class="row">
{% for entry in random %}
<div class="col-sm-3 col-lg-2 col-xs-6 book">
<div class="col-sm-3 col-lg-2 col-xs-6 book" id="books_rand">
<div class="cover">
<a href="{{ url_for('show_book', book_id=entry.id) }}" data-toggle="modal" data-target="#bookDetailsModal" data-remote="false">
{% if entry.has_cover %}
@ -42,7 +42,7 @@
<div class="row">
{% if entries[0] %}
{% for entry in entries %}
<div class="col-sm-3 col-lg-2 col-xs-6 book">
<div class="col-sm-3 col-lg-2 col-xs-6 book" id="books">
<div class="cover">
<a href="{{ url_for('show_book', book_id=entry.id) }}" data-toggle="modal" data-target="#bookDetailsModal" data-remote="false">
{% if entry.has_cover %}
@ -56,7 +56,7 @@
<p class="title">{{entry.title|shortentitle}}</p>
<p class="author">
{% for author in entry.authors %}
<a href="{{url_for('author', book_id=author.id) }}">{{author.name}}</a>
<a href="{{url_for('author', book_id=author.id) }}">{{author.name.replace('|',',')}}</a>
{% if not loop.last %}
&amp;
{% endif %}

@ -12,64 +12,57 @@
</author>
<entry>
<title>{{_('Hot Books')}}</title>
<link type="application/atom+xml;profile=opds-catalog;type=feed;kind=acquisition" href="{{url_for('feed_hot')}}" />
<link type="application/atom+xml" href="{{url_for('feed_hot')}}" rel="http://opds-spec.org/sort/popular"/>
<link rel="http://opds-spec.org/sort/popular" href="{{url_for('feed_hot')}}" type="application/atom+xml;profile=opds-catalog"/>
<id>{{url_for('feed_hot')}}</id>
<content type="text">{{_('Popular publications from this catalog based on Downloads.')}}</content>
</entry>
<entry>
<title>{{_('Best rated Books')}}</title>
<link type="application/atom+xml;profile=opds-catalog;type=feed;kind=acquisition" href="{{url_for('feed_best_rated')}}" />
<link type="application/atom+xml" href="{{url_for('feed_best_rated')}}" rel="http://opds-spec.org/recommended"/>
<link rel="http://opds-spec.org/recommended" href="{{url_for('feed_best_rated')}}" type="application/atom+xml;profile=opds-catalog"/>
<id>{{url_for('feed_best_rated')}}</id>
<content type="text">{{_('Popular publications from this catalog based on Rating.')}}</content>
</entry>
<entry>
<title>{{_('New Books')}}</title>
<link type="application/atom+xml;profile=opds-catalog;type=feed;kind=acquisition" href="{{url_for('feed_new')}}" />
<link rel="http://opds-spec.org/sort/new" href="{{url_for('feed_new')}}" type="application/atom+xml;profile=opds-catalog;type=feed;kind=acquisition"/>
<link rel="http://opds-spec.org/sort/new" href="{{url_for('feed_new')}}" type="application/atom+xml;profile=opds-catalog"/>
<id>{{url_for('feed_new')}}</id>
<content type="text">{{_('The latest Books')}}</content>
</entry>
<entry>
<title>{{_('Random Books')}}</title>
<link type="application/atom+xml;profile=opds-catalog;type=feed;kind=acquisition" href="{{url_for('feed_discover')}}"/>
<link rel="http://opds-spec.org/featured" href="{{url_for('feed_discover')}}" type="application/atom+xml;profile=opds-catalog;type=feed;kind=acquisition"/>
<link rel="http://opds-spec.org/featured" href="{{url_for('feed_discover')}}" type="application/atom+xml;profile=opds-catalog"/>
<id>{{url_for('feed_discover')}}</id>
<content type="text">{{_('Show Random Books')}}</content>
</entry>
{% if not current_user.is_anonymous() %}
<entry>
<title>{{_('Read Books')}}</title>
<link type="application/atom+xml;profile=opds-catalog;type=feed;kind=acquisition" href="{{url_for('feed_read_books')}}" />
<link rel="subsection" href="{{url_for('feed_read_books')}}" type="application/atom+xml;profile=opds-catalog;type=feed;kind=acquisition"/>
<link rel="subsection" href="{{url_for('feed_read_books')}}" type="application/atom+xml;profile=opds-catalog"/>
<id>{{url_for('feed_read_books')}}</id>
<content type="text">{{_('Read Books')}}</content>
</entry>
<entry>
<title>{{_('Unread Books')}}</title>
<link type="application/atom+xml;profile=opds-catalog;type=feed;kind=acquisition" href="{{url_for('feed_unread_books')}}" />
<link rel="subsection" href="{{url_for('feed_unread_books')}}" type="application/atom+xml;profile=opds-catalog;type=feed;kind=acquisition"/>
<link rel="subsection" href="{{url_for('feed_unread_books')}}" type="application/atom+xml;profile=opds-catalog"/>
<id>{{url_for('feed_unread_books')}}</id>
<content type="text">{{_('Unread Books')}}</content>
</entry>
{% endif %}
<entry>
<title>{{_('Authors')}}</title>
<link type="application/atom+xml;profile=opds-catalog;type=feed;kind=acquisition" href="{{url_for('feed_authorindex')}}"/>
<link rel="subsection" href="{{url_for('feed_authorindex')}}" type="application/atom+xml;profile=opds-catalog;type=feed;kind=navigation"/>
<link rel="subsection" href="{{url_for('feed_authorindex')}}" type="application/atom+xml;profile=opds-catalog"/>
<id>{{url_for('feed_authorindex')}}</id>
<content type="text">{{_('Books ordered by Author')}}</content>
</entry>
<entry>
<title>{{_('Category list')}}</title>
<link type="application/atom+xml;profile=opds-catalog;type=feed;kind=acquisition" href="{{url_for('feed_categoryindex')}}"/>
<link rel="subsection" href="{{url_for('feed_categoryindex')}}" type="application/atom+xml;profile=opds-catalog;type=feed;kind=navigation"/>
<link rel="subsection" href="{{url_for('feed_categoryindex')}}" type="application/atom+xml;profile=opds-catalog"/>
<id>{{url_for('feed_categoryindex')}}</id>
<content type="text">{{_('Books ordered by category')}}</content>
</entry>
<entry>
<title>{{_('Series list')}}</title>
<link type="application/atom+xml;profile=opds-catalog;type=feed;kind=acquisition" href="{{url_for('feed_seriesindex')}}"/>
<link rel="subsection" href="{{url_for('feed_seriesindex')}}" type="application/atom+xml;profile=opds-catalog;type=feed;kind=navigation"/>
<link rel="subsection" href="{{url_for('feed_seriesindex')}}" type="application/atom+xml;profile=opds-catalog"/>
<id>{{url_for('feed_seriesindex')}}</id>
<content type="text">{{_('Books ordered by series')}}</content>
</entry>

@ -41,7 +41,7 @@
"rating":{% if entry.ratings.__len__() > 0 %} "{{entry.ratings[0].rating}}.0"{% else %}0.0{% endif %},
"authors": [
{% for author in entry.authors %}
"{{author.name}}"{% if not loop.last %},{% endif %}
"{{author.name.replace('|',',')}}"{% if not loop.last %},{% endif %}
{% endfor %}
],
"other_formats": {

@ -32,9 +32,9 @@
<span class="icon-bar"></span>
<span class="icon-bar"></span>
</button>
<a class="navbar-brand" href="{{url_for('index')}}">Calibre Web</a>
<a class="navbar-brand" href="{{url_for('index')}}">{{instance}}</a>
</div>
{% if g.user.is_authenticated or g.user.is_anonymous() %}
{% if g.user.is_authenticated() or g.user.is_anonymous() %}
<form class="navbar-form navbar-left" role="search" action="{{url_for('search')}}" method="GET">
<div class="form-group input-group input-group-sm">
<label for="query" class="sr-only">{{_('Search')}}</label>
@ -46,13 +46,13 @@
</form>
{% endif %}
<div class="navbar-collapse collapse">
{% if g.user.is_authenticated or g.user.is_anonymous() %}
{% if g.user.is_authenticated() or g.user.is_anonymous() %}
<ul class="nav navbar-nav ">
<li><a href="{{url_for('advanced_search')}}"><span class="glyphicon glyphicon-search"></span><span class="hidden-sm"> {{_('Advanced Search')}}</span></a></li>
</ul>
{% endif %}
<ul class="nav navbar-nav navbar-right" id="main-nav">
{% if g.user.is_authenticated or g.user.is_anonymous() %}
{% if g.user.is_authenticated() or g.user.is_anonymous() %}
{% if g.user.role_upload() or g.user.role_admin()%}
{% if g.allow_upload %}
<li>
@ -72,8 +72,10 @@
<li><a id="logout" href="{{url_for('logout')}}"><span class="glyphicon glyphicon-log-out"></span><span class="hidden-sm"> {{_('Logout')}}</span></a></li>
{% endif %}
{% endif %}
{% if g.allow_registration and not g.user.is_authenticated %}
{% if not g.user.is_authenticated() %}
<li><a id="login" href="{{url_for('login')}}"><span class="glyphicon glyphicon-log-in"></span> {{_('Login')}}</a></li>
{% endif %}
{% if g.allow_registration and not g.user.is_authenticated() %}
<li><a id="register" href="{{url_for('register')}}"><span class="glyphicon glyphicon-user"></span> {{_('Register')}}</a></li>
{% endif %}
</ul>
@ -99,7 +101,7 @@
{% endfor %}
<div class="container-fluid">
<div class="row-fluid">
{% if g.user.is_authenticated or g.user.is_anonymous() %}
{% if g.user.is_authenticated() or g.user.is_anonymous() %}
<div class="col-sm-2">
<nav class="navigation">
<ul class="list-unstyled" id="scnd-nav" intent in-standard-append="nav.navigation" in-mobile-after="#main-nav" in-mobile-class="nav navbar-nav">
@ -140,7 +142,7 @@
{% if g.user.filter_language() == 'all' and g.user.show_language() %}
<li id="nav_lang"><a href="{{url_for('language_overview')}}"><span class="glyphicon glyphicon-flag"></span> {{_('Languages')}} </a></li>
{%endif%}
{% if g.user.is_authenticated or g.user.is_anonymous() %}
{% if g.user.is_authenticated() or g.user.is_anonymous() %}
<li class="nav-head hidden-xs">{{_('Public Shelves')}}</li>
{% for shelf in g.public_shelfes %}
<li><a href="{{url_for('show_shelf', shelf_id=shelf.id)}}"><span class="glyphicon glyphicon-list"></span> {{shelf.name}}</a></li>

@ -11,59 +11,6 @@
<link rel="stylesheet" href="{{ url_for('static', filename='css/libs/normalize.css') }}">
<link rel="stylesheet" href="{{ url_for('static', filename='css/main.css') }}">
<link rel="stylesheet" href="{{ url_for('static', filename='css/popup.css') }}">
<script src="{{ url_for('static', filename='js/libs/jquery.min.js') }}"></script>
<script src="{{ url_for('static', filename='js/libs/zip.min.js') }}"></script>
<!-- Full Screen -->
<script src="{{ url_for('static', filename='js/libs/screenfull.min.js') }}"></script>
<!-- Render -->
<script src="{{ url_for('static', filename='js/libs/epub.min.js') }}"></script>
<!-- Hooks -->
<script src="{{ url_for('static', filename='js/libs/hooks.min.js') }}"></script>
<!-- Reader -->
<script src="{{ url_for('static', filename='js/libs/reader.min.js') }}"></script>
<script>
"use strict";
document.onreadystatechange = function () {
if (document.readyState == "complete") {
EPUBJS.filePath = "{{ url_for('static', filename='js/libs/') }}";
EPUBJS.cssPath = "{{ url_for('static', filename='css/') }}";
window.reader = ePubReader("{{ url_for('static', filename=bookid) }}/");
//keybind
/*$(document).keydown(function(event){
if(event.keyCode == 37){
//window.reader.book.prevPage();
event.preventDefault();
}
if(event.keyCode == 39){
//swindow.reader.book.nextPage();
event.preventDefault();
}
});
//bind mouse
$(window).bind('DOMMouseScroll mousewheel', function(event) {
var delta = 0;
if (event.originalEvent.wheelDelta) {
delta = event.originalEvent.wheelDelta;
}else if (event.originalEvent.detail) {
delta = event.originalEvent.detail*-1;
}
if (delta >= 0) {
window.reader.book.prevPage();
}
else {
window.reader.book.nextPage();
}
});*/
}
};
</script>
</head>
<body>
<div id="sidebar">
@ -73,24 +20,24 @@
<!--a id="show-Search" class="show_view icon-search" data-view="Search">Search</a-->
<a id="show-Toc" class="show_view icon-list-1 active" data-view="Toc">TOC</a>
<a id="show-Bookmarks" class="show_view icon-bookmark" data-view="Bookmarks">Bookmarks</a>
<a id="show-Notes" class="show_view icon-edit" data-view="Notes">Notes</a>
<!--a id="show-Notes" class="show_view icon-edit" data-view="Notes">Notes</a-->
</div>
<div id="tocView" class="view">
</div>
<div id="searchView" class="view">
<!--div id="searchView" class="view">
<ul id="searchResults"></ul>
</div>
</div-->
<div id="bookmarksView" class="view">
<ul id="bookmarks"></ul>
</div>
<div id="notesView" class="view">
<!--div id="notesView" class="view">
<div id="new-note">
<textarea id="note-text"></textarea>
<button id="note-anchor">Anchor</button>
</div>
<ol id="notes"></ol>
</div>
</div-->
</div>
<div id="main">
@ -119,7 +66,7 @@
</div>
<div class="modal md-effect-1" id="settings-modal">
<div class="md-content">
<h3>Settings</h3>
<h3>{{_('Settings')}}</h3>
<div>
<p>
<input type="checkbox" id="sidebarReflow" name="sidebarReflow">{{_('Reflow text when sidebars are open.')}}
@ -129,5 +76,23 @@
</div>
</div>
<div class="overlay"></div>
<script type="text/javascript">
window.calibre = {
filePath: "{{ url_for('static', filename='js/libs/') }}",
cssPath: "{{ url_for('static', filename='css/') }}",
bookUrl: "{{ url_for('static', filename=bookid) }}/",
bookmarkUrl: "{{ url_for('bookmark', book_id=bookid, book_format='EPUB') }}",
bookmark: "{{ bookmark.bookmark_key if bookmark != None }}",
useBookmarks: {{ g.user.is_authenticated() | tojson }}
};
</script>
<script src="{{ url_for('static', filename='js/libs/jquery.min.js') }}"></script>
<script src="{{ url_for('static', filename='js/libs/zip.min.js') }}"></script>
<script src="{{ url_for('static', filename='js/libs/screenfull.min.js') }}"></script>
<script src="{{ url_for('static', filename='js/libs/epub.min.js') }}"></script>
<script src="{{ url_for('static', filename='js/libs/hooks.min.js') }}"></script>
<script src="{{ url_for('static', filename='js/libs/reader.min.js') }}"></script>
<script src="{{ url_for('static', filename='js/reading/epub.js') }}"></script>
</body>
</html>

@ -0,0 +1,44 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1">
<title>Comic Reader</title>
<meta name="description" content="">
<meta name="viewport" content="width=device-width, user-scalable=no">
<meta name="apple-mobile-web-app-capable" content="yes">
<link rel="stylesheet" href="{{ url_for('static', filename='css/kthoom.css') }}" type="text/css"/>
<link href="{{ url_for('static', filename='css/libs/bootstrap.min.css') }}" rel="stylesheet" media="screen">
<script src="{{ url_for('static', filename='js/libs/jquery.min.js') }}"></script>
<script src="{{ url_for('static', filename='js/libs/bootstrap.min.js') }}"></script>
<script src="{{ url_for('static', filename='js/kthoom.js') }}"></script>
<script src="{{ url_for('static', filename='js/archive.js') }}"></script>
<script>
document.onreadystatechange = function () {
if (document.readyState == "complete") {
init("{{ url_for('static', filename=comicfile) }}");
}
};
</script>
</head>
<body>
<div class="main" id="main">
<div id="titlebar">
<div id="progress" class="hide"></div>
<div class="progress2" style="display:none">
<div class="progress-bar" role="progressbar" aria-valuenow="70"
aria-valuemin="0" aria-valuemax="100" style="width:70%">
70%
</div>
</div>
</div>
<div id="mainContent">
<div id="mainText" style="display:none"></div>
<canvas id="mainImage"></canvas>
</div>
<div id="prev" class="arrow" onclick="showPrevPage()"></div>
<div id="next" class="arrow" onclick="showNextPage()"></div>
</div>
</body>
</html>

@ -4,7 +4,7 @@
{% if entries|length < 1 %}
<h2>{{_('No Results for:')}} {{searchterm}}</h2>
<p>{{_('Please try a diffrent Search')}}</p>
<p>{{_('Please try a different search')}}</p>
{% else %}
<h2>{{entries|length}} {{_('Results for:')}} {{searchterm}}</h2>
{%endif%}
@ -24,7 +24,7 @@
<p class="title">{{entry.title|shortentitle}}</p>
<p class="author">
{% for author in entry.authors %}
<a href="{{url_for('author', book_id=author.id ) }}">{{author.name}}</a>
<a href="{{url_for('author', book_id=author.id ) }}">{{author.name.replace('|',',')}}</a>
{% if not loop.last %}
&amp;
{% endif %}

@ -2,7 +2,7 @@
{% block body %}
<div class="discover">
<h2>{{title}}</h2>
{% if g.user.is_authenticated %}
{% if g.user.is_authenticated() %}
{% if (g.user.role_edit_shelfs() and shelf.is_public ) or not shelf.is_public %}
<div data-toggle="modal" data-target="#DeleteShelfDialog" class="btn btn-danger">{{ _('Delete this Shelf') }} </div>
<a href="{{ url_for('edit_shelf', shelf_id=shelf.id) }}" class="btn btn-primary">{{ _('Edit Shelf name') }} </a>
@ -24,7 +24,7 @@
<p class="title">{{entry.title|shortentitle}}</p>
<p class="author">
{% for author in entry.authors %}
<a href="{{url_for('author', book_id=author.id) }}">{{author.name}}</a>
<a href="{{url_for('author', book_id=author.id) }}">{{author.name.replace('|',',')}}</a>
{% if not loop.last %}
&amp;
{% endif %}

@ -1,5 +1,9 @@
{% extends "layout.html" %}
{% block body %}
<h3>{{_('About')}}</h3>
<p>{{instance}} powered by
<a href="https://github.com/janeczku/calibre-web" title="Calibre-Web">Calibre Web</a>.
</p>
<h3>{{_('Calibre library statistics')}}</h3>
<table id="stats" class="table">
<tbody>

@ -41,10 +41,6 @@
</select>
</div>
<div class="col-sm-6">
<div class="form-group">
<input type="checkbox" name="show_mature_content" id="show_mature_content" {% if content.mature_content %}checked{% endif %}>
<label for="show_mature_content">{{_('Show mature content')}}</label>
</div>
<div class="form-group">
<input type="checkbox" name="show_random" id="show_random" {% if content.show_random_books() %}checked{% endif %}>
<label for="show_random">{{_('Show random books')}}</label>
@ -89,6 +85,10 @@
<input type="checkbox" name="admin_role" id="admin_role" {% if content.role_admin() %}checked{% endif %}>
<label for="admin_role">{{_('Admin user')}}</label>
</div>
<div class="form-group">
<input type="checkbox" name="show_mature_content" id="show_mature_content" {% if content.mature_content %}checked{% endif %}>
<label for="show_mature_content">{{_('Show mature content')}}</label>
</div>
{% endif %}
<div class="form-group">
<input type="checkbox" name="download_role" id="download_role" {% if content.role_download() %}checked{% endif %}>

@ -21,7 +21,7 @@ msgid ""
msgstr ""
"Project-Id-Version: Calibre-web\n"
"Report-Msgid-Bugs-To: https://github.com/janeczku/calibre-web\n"
"POT-Creation-Date: 2017-08-12 18:19+0200\n"
"POT-Creation-Date: 2017-09-16 07:48+0200\n"
"PO-Revision-Date: 2016-07-12 19:54+0200\n"
"Last-Translator: Ozzie Isaacs\n"
"Language: de\n"
@ -32,7 +32,7 @@ msgstr ""
"Content-Transfer-Encoding: 8bit\n"
"Generated-By: Babel 2.4.0\n"
#: cps/book_formats.py:118 cps/book_formats.py:122 cps/web.py:1374
#: cps/book_formats.py:118 cps/book_formats.py:122 cps/web.py:1358
msgid "not installed"
msgstr "Nicht installiert"
@ -78,379 +78,379 @@ msgstr ""
"Konnte keine Formate finden welche für das versenden per E-Mail geeignet "
"sind"
#: cps/ub.py:542
#: cps/ub.py:556
msgid "Guest"
msgstr "Gast"
#: cps/web.py:974
#: cps/web.py:953
msgid "Requesting update package"
msgstr "Frage Update Paket an"
#: cps/web.py:975
#: cps/web.py:954
msgid "Downloading update package"
msgstr "Lade Update Paket herunter"
#: cps/web.py:976
#: cps/web.py:955
msgid "Unzipping update package"
msgstr "Entpacke Update Paket"
#: cps/web.py:977
#: cps/web.py:956
msgid "Files are replaced"
msgstr "Ersetze Dateien"
#: cps/web.py:978
#: cps/web.py:957
msgid "Database connections are closed"
msgstr "Schließe Datenbankverbindungen"
#: cps/web.py:979
#: cps/web.py:958
msgid "Server is stopped"
msgstr "Stoppe Server"
#: cps/web.py:980
#: cps/web.py:959
msgid "Update finished, please press okay and reload page"
msgstr "Update abgeschlossen, bitte okay drücken und Seite neu laden"
#: cps/web.py:1054
#: cps/web.py:1033
msgid "Recently Added Books"
msgstr "Kürzlich hinzugefügte Bücher"
#: cps/web.py:1063
#: cps/web.py:1042
msgid "Newest Books"
msgstr "Neueste Bücher"
#: cps/web.py:1072
#: cps/web.py:1051
msgid "Oldest Books"
msgstr "Älteste Bücher"
#: cps/web.py:1081
#: cps/web.py:1060
msgid "Books (A-Z)"
msgstr "Bücher (A-Z)"
#: cps/web.py:1090
#: cps/web.py:1069
msgid "Books (Z-A)"
msgstr "Bücher (Z-A)"
#: cps/web.py:1126
#: cps/web.py:1096
msgid "Hot Books (most downloaded)"
msgstr "Beliebte Bücher (die meisten Downloads)"
#: cps/web.py:1136
#: cps/web.py:1106
msgid "Best rated books"
msgstr "Best bewertete Bücher"
#: cps/templates/index.xml:36 cps/web.py:1145
#: cps/templates/index.xml:35 cps/web.py:1115
msgid "Random Books"
msgstr "Zufällige Bücher"
#: cps/web.py:1161
#: cps/web.py:1124
msgid "Author list"
msgstr "Autorenliste"
#: cps/web.py:1181 cps/web.py:1212 cps/web.py:1351 cps/web.py:1835
#: cps/web.py:1134 cps/web.py:1190 cps/web.py:1315 cps/web.py:1774
msgid "Error opening eBook. File does not exist or file is not accessible:"
msgstr ""
"Buch öffnen fehlgeschlagen. Datei existiert nicht, oder ist nicht "
"zugänglich."
#: cps/templates/index.xml:71 cps/web.py:1198
#: cps/templates/index.xml:70 cps/web.py:1176
msgid "Series list"
msgstr "Liste Serien"
#: cps/web.py:1210
#: cps/web.py:1188
#, python-format
msgid "Series: %(serie)s"
msgstr "Serie: %(serie)s"
#: cps/web.py:1243
#: cps/web.py:1221
msgid "Available languages"
msgstr "Verfügbare Sprachen"
#: cps/web.py:1258
#: cps/web.py:1236
#, python-format
msgid "Language: %(name)s"
msgstr "Sprache: %(name)s"
#: cps/templates/index.xml:64 cps/web.py:1274
#: cps/templates/index.xml:63 cps/web.py:1245
msgid "Category list"
msgstr "Kategorieliste"
#: cps/web.py:1286
#: cps/web.py:1257
#, python-format
msgid "Category: %(name)s"
msgstr "Kategorie: %(name)s"
#: cps/web.py:1385
#: cps/web.py:1369
msgid "Excecution permissions missing"
msgstr "Ausführungsberechtigung nicht vorhanden"
#: cps/web.py:1399
#: cps/web.py:1383
msgid "Statistics"
msgstr "Statistiken"
#: cps/web.py:1563
#: cps/web.py:1547
msgid "Server restarted, please reload page"
msgstr "Server neu gestartet,bitte Seite neu laden"
#: cps/web.py:1565
#: cps/web.py:1549
msgid "Performing shutdown of server, please close window"
msgstr "Server wird runtergefahren, bitte Fenster schließen"
#: cps/web.py:1581
#: cps/web.py:1565
msgid "Update done"
msgstr "Update durchgeführt"
#: cps/web.py:1662 cps/web.py:1675
#: cps/web.py:1640 cps/web.py:1653
msgid "search"
msgstr "Suche"
#: cps/templates/index.xml:43 cps/templates/index.xml:47
#: cps/templates/layout.html:127 cps/web.py:1751
#: cps/templates/index.xml:42 cps/templates/index.xml:46
#: cps/templates/layout.html:127 cps/web.py:1729
msgid "Read Books"
msgstr "Gelesene Bücher"
#: cps/templates/index.xml:50 cps/templates/index.xml:54
#: cps/templates/layout.html:128 cps/web.py:1754
#: cps/templates/index.xml:49 cps/templates/index.xml:53
#: cps/templates/layout.html:128 cps/web.py:1732
msgid "Unread Books"
msgstr "Ungelesene Bücher"
#: cps/web.py:1821 cps/web.py:1823 cps/web.py:1825 cps/web.py:1832
#: cps/web.py:1805 cps/web.py:1807 cps/web.py:1809 cps/web.py:1816
msgid "Read a Book"
msgstr "Lese ein Buch"
#: cps/web.py:1888 cps/web.py:2512
#: cps/web.py:1868 cps/web.py:2493
msgid "Please fill out all fields!"
msgstr "Bitte alle Felder ausfüllen!"
#: cps/web.py:1889 cps/web.py:1905 cps/web.py:1910 cps/web.py:1912
#: cps/web.py:1869 cps/web.py:1885 cps/web.py:1890 cps/web.py:1892
msgid "register"
msgstr "Registieren"
#: cps/web.py:1904
#: cps/web.py:1884
msgid "An unknown error occured. Please try again later."
msgstr "Es ist ein unbekannter Fehler aufgetreten. Bitte später erneut versuchen."
#: cps/web.py:1909
#: cps/web.py:1889
msgid "This username or email address is already in use."
msgstr "Der Benutzername oder die E-Mailadresse ist in bereits in Benutzung."
#: cps/web.py:1928 cps/web.py:2024
#: cps/web.py:1908 cps/web.py:2004
#, python-format
msgid "you are now logged in as: '%(nickname)s'"
msgstr "Du bist nun eingeloggt als '%(nickname)s'"
#: cps/web.py:1933
#: cps/web.py:1913
msgid "Wrong Username or Password"
msgstr "Falscher Benutzername oder Passwort"
#: cps/web.py:1939 cps/web.py:1960
#: cps/web.py:1919 cps/web.py:1940
msgid "login"
msgstr "Login"
#: cps/web.py:1972 cps/web.py:2003
#: cps/web.py:1952 cps/web.py:1983
msgid "Token not found"
msgstr "Token wurde nicht gefunden"
#: cps/web.py:1980 cps/web.py:2011
#: cps/web.py:1960 cps/web.py:1991
msgid "Token has expired"
msgstr "Das Token ist abgelaufen"
#: cps/web.py:1988
#: cps/web.py:1968
msgid "Success! Please return to your device"
msgstr "Erfolg! Bitte zum Gerät zurückkehren"
#: cps/web.py:2038
#: cps/web.py:2018
msgid "Please configure the SMTP mail settings first..."
msgstr "Bitte zuerst die SMTP Mail Einstellung konfigurieren ..."
#: cps/web.py:2042
#: cps/web.py:2022
#, python-format
msgid "Book successfully send to %(kindlemail)s"
msgstr "Buch erfolgreich versandt an %(kindlemail)s"
#: cps/web.py:2046
#: cps/web.py:2026
#, python-format
msgid "There was an error sending this book: %(res)s"
msgstr "Beim Senden des Buchs trat ein Fehler auf: %(res)s"
#: cps/web.py:2048 cps/web.py:2597
#: cps/web.py:2028 cps/web.py:2578
msgid "Please configure your kindle email address first..."
msgstr "Bitte die Kindle E-Mail Adresse zuuerst konfigurieren..."
#: cps/web.py:2092
#: cps/web.py:2072
#, python-format
msgid "Book has been added to shelf: %(sname)s"
msgstr "Das Buch wurde dem Bücherregal: %(sname)s hinzugefügt"
#: cps/web.py:2127
#: cps/web.py:2107
#, python-format
msgid "Book has been removed from shelf: %(sname)s"
msgstr "Das Buch wurde aus dem Bücherregal: %(sname)s entfernt"
#: cps/web.py:2146 cps/web.py:2170
#: cps/web.py:2126 cps/web.py:2150
#, python-format
msgid "A shelf with the name '%(title)s' already exists."
msgstr "Es existiert bereits ein Bücheregal mit dem Titel '%(title)s'"
#: cps/web.py:2151
#: cps/web.py:2131
#, python-format
msgid "Shelf %(title)s created"
msgstr "Bücherregal %(title)s erzeugt"
#: cps/web.py:2153 cps/web.py:2181
#: cps/web.py:2133 cps/web.py:2161
msgid "There was an error"
msgstr "Es trat ein Fehler auf"
#: cps/web.py:2154 cps/web.py:2156
#: cps/web.py:2134 cps/web.py:2136
msgid "create a shelf"
msgstr "Bücherregal erzeugen"
#: cps/web.py:2179
#: cps/web.py:2159
#, python-format
msgid "Shelf %(title)s changed"
msgstr "Bücherregal %(title)s verändert"
#: cps/web.py:2182 cps/web.py:2184
#: cps/web.py:2162 cps/web.py:2164
msgid "Edit a shelf"
msgstr "Bücherregal editieren"
#: cps/web.py:2204
#: cps/web.py:2184
#, python-format
msgid "successfully deleted shelf %(name)s"
msgstr "Bücherregal %(name)s erfolgreich gelöscht"
#: cps/web.py:2226
#: cps/web.py:2206
#, python-format
msgid "Shelf: '%(name)s'"
msgstr "Bücherregal: '%(name)s'"
#: cps/web.py:2229
#: cps/web.py:2209
msgid "Error opening shelf. Shelf does not exist or is not accessible"
msgstr "Fehler beim Öffnen. Bücherregel exisitert nicht oder ist nicht zugänglich"
#: cps/web.py:2261
#: cps/web.py:2241
#, python-format
msgid "Change order of Shelf: '%(name)s'"
msgstr "Reihenfolge in Bücherregal '%(name)s' verändern"
#: cps/web.py:2325
#: cps/web.py:2306
msgid "Found an existing account for this email address."
msgstr "Es existiert ein Benutzerkonto für diese E-Mailadresse"
#: cps/web.py:2327 cps/web.py:2331
#: cps/web.py:2308 cps/web.py:2312
#, python-format
msgid "%(name)s's profile"
msgstr "%(name)s's Profil"
#: cps/web.py:2328
#: cps/web.py:2309
msgid "Profile updated"
msgstr "Profil aktualisiert"
#: cps/web.py:2342
#: cps/web.py:2323
msgid "Admin page"
msgstr "Admin Seite"
#: cps/web.py:2466
#: cps/web.py:2447
msgid "Calibre-web configuration updated"
msgstr "Calibre-web Konfiguration wurde aktualisiert"
#: cps/web.py:2473 cps/web.py:2479 cps/web.py:2493
#: cps/web.py:2454 cps/web.py:2460 cps/web.py:2474
msgid "Basic Configuration"
msgstr "Basis Konfiguration"
#: cps/web.py:2477
#: cps/web.py:2458
msgid "DB location is not valid, please enter correct path"
msgstr "DB Speicherort ist ungültig, bitte Pfad korrigieren"
#: cps/templates/admin.html:34 cps/web.py:2514 cps/web.py:2567
#: cps/templates/admin.html:34 cps/web.py:2495 cps/web.py:2548
msgid "Add new user"
msgstr "Neuen Benutzer hinzufügen"
#: cps/web.py:2559
#: cps/web.py:2540
#, python-format
msgid "User '%(user)s' created"
msgstr "Benutzer '%(user)s' angelegt"
#: cps/web.py:2563
#: cps/web.py:2544
msgid "Found an existing account for this email address or nickname."
msgstr ""
"Es existiert ein Benutzerkonto für diese Emailadresse oder den "
"Benutzernamen."
#: cps/web.py:2585
#: cps/web.py:2566
msgid "Mail settings updated"
msgstr "E-Mail Einstellungen aktualisiert"
#: cps/web.py:2592
#: cps/web.py:2573
#, python-format
msgid "Test E-Mail successfully send to %(kindlemail)s"
msgstr "Test E-Mail erfolgreich an %(kindlemail)s versendet"
#: cps/web.py:2595
#: cps/web.py:2576
#, python-format
msgid "There was an error sending the Test E-Mail: %(res)s"
msgstr "Fehler beim versenden der Test E-Mail: %(res)s"
#: cps/web.py:2599
#: cps/web.py:2580
msgid "E-Mail settings updated"
msgstr "E-Mail Einstellungen wurde aktualisiert"
#: cps/web.py:2600
#: cps/web.py:2581
msgid "Edit mail settings"
msgstr "E-Mail Einstellungen editieren"
#: cps/web.py:2629
#: cps/web.py:2610
#, python-format
msgid "User '%(nick)s' deleted"
msgstr "Benutzer '%(nick)s' gelöscht"
#: cps/web.py:2727
#: cps/web.py:2708
#, python-format
msgid "User '%(nick)s' updated"
msgstr "Benutzer '%(nick)s' aktualisiert"
#: cps/web.py:2730
#: cps/web.py:2711
msgid "An unknown error occured."
msgstr "Es ist ein unbekanter Fehler aufgetreten"
#: cps/web.py:2733
#: cps/web.py:2714
#, python-format
msgid "Edit User %(nick)s"
msgstr "Benutzer %(nick)s bearbeiten"
#: cps/web.py:2755
#: cps/web.py:2730
msgid "Error opening eBook. File does not exist or file is not accessible"
msgstr ""
"Buch öffnen fehlgeschlagen. Datei existiert nicht, oder ist nicht "
"zugänglich"
#: cps/web.py:2770 cps/web.py:2953 cps/web.py:3077
#: cps/web.py:2745 cps/web.py:2917 cps/web.py:3060
msgid "edit metadata"
msgstr "Metadaten editieren"
#: cps/web.py:2782 cps/web.py:2786
#: cps/web.py:2757 cps/web.py:2761
msgid "unknown"
msgstr "Unbekannt"
#: cps/web.py:2971
#: cps/web.py:2954
#, python-format
msgid "File extension \"%s\" is not allowed to be uploaded to this server"
msgstr "Die Dateiendung \"%s\" kann nicht auf diesen Server hochgeladen werden"
#: cps/web.py:2977
#: cps/web.py:2960
msgid "File to be uploaded must have an extension"
msgstr "Datei müssen eine Erweiterung haben, um hochgeladen zu werden"
#: cps/web.py:2996
#: cps/web.py:2979
#, python-format
msgid "Failed to create path %s (Permission denied)."
msgstr "Fehler beim Erzeugen des Pfads %s (Zugriff verweigert)"
#: cps/web.py:3001
#: cps/web.py:2984
#, python-format
msgid "Failed to store file %s (Permission denied)."
msgstr "Fehler beim speichern der Datei %s (Zugriff verweigert)"
#: cps/web.py:3006
#: cps/web.py:2989
#, python-format
msgid "Failed to delete file %s (Permission denied)."
msgstr "Fehler beim Löschen von Datei %s (Zugriff verweigert)"
@ -621,6 +621,18 @@ msgstr "Calibre-web wirklich stoppen"
msgid "Updating, please do not reload page"
msgstr "Updatevorgang, bitte Seite nicht neu laden"
#: cps/templates/author.html:15
msgid "via"
msgstr "via"
#: cps/templates/author.html:23
msgid "In Library"
msgstr "In Bibliothek"
#: cps/templates/author.html:69
msgid "More by"
msgstr "Mehr von"
#: cps/templates/book_edit.html:16
msgid "Delete Book"
msgstr "Buch löschen"
@ -629,12 +641,13 @@ msgstr "Buch löschen"
msgid "Book Title"
msgstr "Buchtitel"
#: cps/templates/book_edit.html:26 cps/templates/book_edit.html:188
#: cps/templates/search_form.html:10
#: cps/templates/book_edit.html:26 cps/templates/book_edit.html:208
#: cps/templates/book_edit.html:226 cps/templates/search_form.html:10
msgid "Author"
msgstr "Autor"
#: cps/templates/book_edit.html:30 cps/templates/book_edit.html:190
#: cps/templates/book_edit.html:30 cps/templates/book_edit.html:213
#: cps/templates/book_edit.html:228
msgid "Description"
msgstr "Beschreibung"
@ -699,7 +712,7 @@ msgstr "Das Buch wird aus der Calibre Datenbank"
#: cps/templates/book_edit.html:144
msgid "and from hard disk"
msgstr "und der Festplatte gelöscht"
msgstr "und von der Festplatte gelöscht"
#: cps/templates/book_edit.html:148
msgid "Delete"
@ -717,35 +730,35 @@ msgstr "Suchbegriff"
msgid "Go!"
msgstr "Los!"
#: cps/templates/book_edit.html:168
#: cps/templates/book_edit.html:171
msgid "Click the cover to load metadata to the form"
msgstr "Klicke auf das Bild um die Metadaten zu übertragen"
#: cps/templates/book_edit.html:172 cps/templates/book_edit.html:185
#: cps/templates/book_edit.html:183 cps/templates/book_edit.html:223
msgid "Loading..."
msgstr "Lade..."
#: cps/templates/book_edit.html:175 cps/templates/layout.html:199
#: cps/templates/book_edit.html:188 cps/templates/layout.html:199
msgid "Close"
msgstr "Schließen"
#: cps/templates/book_edit.html:186
msgid "Search error!"
msgstr "Fehler bei Suche!"
#: cps/templates/book_edit.html:187
msgid "No Result! Please try anonther keyword."
msgstr "Kein Ergebniss! Bitte anderen Begriff versuchen"
#: cps/templates/book_edit.html:189 cps/templates/detail.html:125
#: cps/templates/search_form.html:14
#: cps/templates/book_edit.html:210 cps/templates/book_edit.html:227
#: cps/templates/detail.html:125 cps/templates/search_form.html:14
msgid "Publisher"
msgstr "Herausgeber"
#: cps/templates/book_edit.html:191
#: cps/templates/book_edit.html:215 cps/templates/book_edit.html:229
msgid "Source"
msgstr "Quelle"
#: cps/templates/book_edit.html:224
msgid "Search error!"
msgstr "Fehler bei Suche!"
#: cps/templates/book_edit.html:225
msgid "No Result! Please try anonther keyword."
msgstr "Kein Ergebniss! Bitte anderen Begriff versuchen"
#: cps/templates/config_edit.html:7
msgid "Location of Calibre database"
msgstr "Speicherort der Calibre Datenbank"
@ -829,13 +842,13 @@ msgstr "Öffentlicher Goodreads API Schlüssel"
#: cps/templates/config_edit.html:119
msgid "Goodreads API Secret"
msgstr Geheimer Goodreads API Schlüssel"
msgstr "eheimer Goodreads API Schlüssel"
#: cps/templates/config_edit.html:125
msgid "Default Settings for new users"
msgstr "Default Einstellungen für neue Benutzer"
#: cps/templates/config_edit.html:128 cps/templates/user_edit.html:90
#: cps/templates/config_edit.html:128 cps/templates/user_edit.html:86
msgid "Admin user"
msgstr "Admin Benutzer"
@ -938,6 +951,11 @@ msgstr "Einstellungen speichern und Test E-Mail versenden"
msgid "Next"
msgstr "Nächste"
#: cps/templates/feed.xml:29 cps/templates/index.xml:7
#: cps/templates/layout.html:40 cps/templates/layout.html:41
msgid "Search"
msgstr "Suche"
#: cps/templates/index.html:5
msgid "Discover (Random Books)"
msgstr "Entdecke (Zufälliges Buch)"
@ -946,52 +964,47 @@ msgstr "Entdecke (Zufälliges Buch)"
msgid "Start"
msgstr "Start"
#: cps/templates/index.xml:7 cps/templates/layout.html:40
#: cps/templates/layout.html:41
msgid "Search"
msgstr "Suche"
#: cps/templates/index.xml:15 cps/templates/layout.html:121
#: cps/templates/index.xml:14 cps/templates/layout.html:121
msgid "Hot Books"
msgstr "Beliebte Bücher"
#: cps/templates/index.xml:19
#: cps/templates/index.xml:18
msgid "Popular publications from this catalog based on Downloads."
msgstr "Beliebte Publikationen aus dieser Bibliothek basierend auf Downloadzahlen"
#: cps/templates/index.xml:22 cps/templates/layout.html:124
#: cps/templates/index.xml:21 cps/templates/layout.html:124
msgid "Best rated Books"
msgstr "Best bewertete Bücher"
#: cps/templates/index.xml:26
#: cps/templates/index.xml:25
msgid "Popular publications from this catalog based on Rating."
msgstr "Beliebte Veröffentlichungen dieses Katalogs basierend auf Bewertungen"
#: cps/templates/index.xml:29
#: cps/templates/index.xml:28
msgid "New Books"
msgstr "Neue Bücher"
#: cps/templates/index.xml:33
#: cps/templates/index.xml:32
msgid "The latest Books"
msgstr "Die neuesten Bücher"
#: cps/templates/index.xml:40
#: cps/templates/index.xml:39
msgid "Show Random Books"
msgstr "Zeige zufällige Bücher"
#: cps/templates/index.xml:57 cps/templates/layout.html:139
#: cps/templates/index.xml:56 cps/templates/layout.html:139
msgid "Authors"
msgstr "Autoren"
#: cps/templates/index.xml:61
#: cps/templates/index.xml:60
msgid "Books ordered by Author"
msgstr "Bücher nach Autoren sortiert"
#: cps/templates/index.xml:68
#: cps/templates/index.xml:67
msgid "Books ordered by category"
msgstr "Bücher nach Kategorien sortiert"
#: cps/templates/index.xml:75
#: cps/templates/index.xml:74
msgid "Books ordered by series"
msgstr "Bücher nach Reihen geordnet"
@ -1102,7 +1115,11 @@ msgstr "Einloggen mit magischem Link"
msgid "Calibre Web ebook catalog"
msgstr "Calibre Web Ebook Katalog"
#: cps/templates/read.html:125
#: cps/templates/read.html:69
msgid "Settings"
msgstr "Einstellungen"
#: cps/templates/read.html:72
msgid "Reflow text when sidebars are open."
msgstr "Text umbrechen wenn Seitenleiste geöffnet ist"
@ -1151,7 +1168,7 @@ msgid "No Results for:"
msgstr "Keine Ergebnisse für:"
#: cps/templates/search.html:7
msgid "Please try a diffrent Search"
msgid "Please try a different search"
msgstr "Versuche eine andere Suche"
#: cps/templates/search.html:9
@ -1243,45 +1260,45 @@ msgid "Show all"
msgstr "Zeige alle"
#: cps/templates/user_edit.html:46
msgid "Show mature content"
msgstr "Erwachsenencontent anzeigen"
#: cps/templates/user_edit.html:50
msgid "Show random books"
msgstr "Zeige Zufällige Bücher"
#: cps/templates/user_edit.html:54
#: cps/templates/user_edit.html:50
msgid "Show hot books"
msgstr "Zeige Auswahl Beliebte Bücher"
#: cps/templates/user_edit.html:58
#: cps/templates/user_edit.html:54
msgid "Show best rated books"
msgstr "Zeige am besten bewertete Bücher"
#: cps/templates/user_edit.html:62
#: cps/templates/user_edit.html:58
msgid "Show language selection"
msgstr "Zeige Sprachauswahl"
#: cps/templates/user_edit.html:66
#: cps/templates/user_edit.html:62
msgid "Show series selection"
msgstr "Zeige Serienauswahl"
#: cps/templates/user_edit.html:70
#: cps/templates/user_edit.html:66
msgid "Show category selection"
msgstr "Zeige Kategorienauswahl"
#: cps/templates/user_edit.html:74
#: cps/templates/user_edit.html:70
msgid "Show author selection"
msgstr "Zeige Autorenauswahl"
#: cps/templates/user_edit.html:78
#: cps/templates/user_edit.html:74
msgid "Show read and unread"
msgstr "Zeige Gelesen/Ungelesen Auswahl"
#: cps/templates/user_edit.html:82
#: cps/templates/user_edit.html:78
msgid "Show random books in detail view"
msgstr "Zeige zufällige Bücher in der Detailansicht"
#: cps/templates/user_edit.html:90
msgid "Show mature content"
msgstr "Erwachsenencontent anzeigen"
#: cps/templates/user_edit.html:123
msgid "Delete this user"
msgstr "Benutzer löschen"

@ -14,7 +14,7 @@ msgid ""
msgstr ""
"Project-Id-Version: Calibre-web\n"
"Report-Msgid-Bugs-To: https://github.com/janeczku/calibre-web\n"
"POT-Creation-Date: 2017-08-12 18:55+0200\n"
"POT-Creation-Date: 2017-09-16 07:48+0200\n"
"PO-Revision-Date: 2017-04-04 15:09+0200\n"
"Last-Translator: Juan F. Villa <juan.villa@paisdelconocimiento.org>\n"
"Language: es\n"
@ -25,7 +25,7 @@ msgstr ""
"Content-Transfer-Encoding: 8bit\n"
"Generated-By: Babel 2.4.0\n"
#: cps/book_formats.py:118 cps/book_formats.py:122 cps/web.py:1374
#: cps/book_formats.py:118 cps/book_formats.py:122 cps/web.py:1358
msgid "not installed"
msgstr "No instalado"
@ -69,375 +69,375 @@ msgstr "Enviar a Kindle"
msgid "Could not find any formats suitable for sending by email"
msgstr "Formato no compatible para enviar por correo electronico"
#: cps/ub.py:542
#: cps/ub.py:556
msgid "Guest"
msgstr "Invitado"
#: cps/web.py:974
#: cps/web.py:953
msgid "Requesting update package"
msgstr "Solicitando paquete de actualización"
#: cps/web.py:975
#: cps/web.py:954
msgid "Downloading update package"
msgstr "Descargando paquete de actualización"
#: cps/web.py:976
#: cps/web.py:955
msgid "Unzipping update package"
msgstr "Descomprimiendo paquete de actualización"
#: cps/web.py:977
#: cps/web.py:956
msgid "Files are replaced"
msgstr "Ficheros sustituidos"
#: cps/web.py:978
#: cps/web.py:957
msgid "Database connections are closed"
msgstr "Los conexiones de base datos están cerradas"
#: cps/web.py:979
#: cps/web.py:958
msgid "Server is stopped"
msgstr "El servidor está detenido"
#: cps/web.py:980
#: cps/web.py:959
msgid "Update finished, please press okay and reload page"
msgstr "Actualización finalizada. Por favor, pulse OK y recargue la página"
#: cps/web.py:1054
#: cps/web.py:1033
msgid "Recently Added Books"
msgstr ""
#: cps/web.py:1063
#: cps/web.py:1042
msgid "Newest Books"
msgstr ""
#: cps/web.py:1072
#: cps/web.py:1051
msgid "Oldest Books"
msgstr ""
#: cps/web.py:1081
#: cps/web.py:1060
msgid "Books (A-Z)"
msgstr ""
#: cps/web.py:1090
#: cps/web.py:1069
msgid "Books (Z-A)"
msgstr ""
#: cps/web.py:1126
#: cps/web.py:1096
msgid "Hot Books (most downloaded)"
msgstr "Libros populares (los mas descargados)"
#: cps/web.py:1136
#: cps/web.py:1106
msgid "Best rated books"
msgstr "Libros mejor valorados"
#: cps/templates/index.xml:36 cps/web.py:1145
#: cps/templates/index.xml:35 cps/web.py:1115
msgid "Random Books"
msgstr "Libros al azar"
#: cps/web.py:1161
#: cps/web.py:1124
msgid "Author list"
msgstr "Lista de autores"
#: cps/web.py:1181 cps/web.py:1212 cps/web.py:1351 cps/web.py:1835
#: cps/web.py:1134 cps/web.py:1190 cps/web.py:1315 cps/web.py:1774
msgid "Error opening eBook. File does not exist or file is not accessible:"
msgstr "Error en la apertura del eBook. El archivo no existe o no es accesible:"
#: cps/templates/index.xml:71 cps/web.py:1198
#: cps/templates/index.xml:70 cps/web.py:1176
msgid "Series list"
msgstr "Lista de series"
#: cps/web.py:1210
#: cps/web.py:1188
#, python-format
msgid "Series: %(serie)s"
msgstr "Series : %(serie)s"
#: cps/web.py:1243
#: cps/web.py:1221
msgid "Available languages"
msgstr "Lenguajes disponibles"
#: cps/web.py:1258
#: cps/web.py:1236
#, python-format
msgid "Language: %(name)s"
msgstr "Lenguaje: %(name)s"
#: cps/templates/index.xml:64 cps/web.py:1274
#: cps/templates/index.xml:63 cps/web.py:1245
msgid "Category list"
msgstr "Lista de categorias"
#: cps/web.py:1286
#: cps/web.py:1257
#, python-format
msgid "Category: %(name)s"
msgstr "Categoría : %(name)s"
#: cps/web.py:1385
#: cps/web.py:1369
msgid "Excecution permissions missing"
msgstr ""
#: cps/web.py:1399
#: cps/web.py:1383
msgid "Statistics"
msgstr "Estadisticas"
#: cps/web.py:1563
#: cps/web.py:1547
msgid "Server restarted, please reload page"
msgstr "Servidor reiniciado. Por favor, recargue la página"
#: cps/web.py:1565
#: cps/web.py:1549
msgid "Performing shutdown of server, please close window"
msgstr "Servidor en proceso de apagado. Por favor, cierre la ventana."
#: cps/web.py:1581
#: cps/web.py:1565
msgid "Update done"
msgstr "Actualización realizada"
#: cps/web.py:1662 cps/web.py:1675
#: cps/web.py:1640 cps/web.py:1653
msgid "search"
msgstr "búsqueda"
#: cps/templates/index.xml:43 cps/templates/index.xml:47
#: cps/templates/layout.html:127 cps/web.py:1751
#: cps/templates/index.xml:42 cps/templates/index.xml:46
#: cps/templates/layout.html:127 cps/web.py:1729
msgid "Read Books"
msgstr "Libros leídos"
#: cps/templates/index.xml:50 cps/templates/index.xml:54
#: cps/templates/layout.html:128 cps/web.py:1754
#: cps/templates/index.xml:49 cps/templates/index.xml:53
#: cps/templates/layout.html:128 cps/web.py:1732
msgid "Unread Books"
msgstr "Libros no leídos"
#: cps/web.py:1821 cps/web.py:1823 cps/web.py:1825 cps/web.py:1832
#: cps/web.py:1805 cps/web.py:1807 cps/web.py:1809 cps/web.py:1816
msgid "Read a Book"
msgstr "Leer un libro"
#: cps/web.py:1888 cps/web.py:2513
#: cps/web.py:1868 cps/web.py:2493
msgid "Please fill out all fields!"
msgstr "¡Por favor completar todos los campos!"
#: cps/web.py:1889 cps/web.py:1905 cps/web.py:1910 cps/web.py:1912
#: cps/web.py:1869 cps/web.py:1885 cps/web.py:1890 cps/web.py:1892
msgid "register"
msgstr "registrarse"
#: cps/web.py:1904
#: cps/web.py:1884
msgid "An unknown error occured. Please try again later."
msgstr "Error desconocido. Por favor, inténtelo de nuevo mas tarde."
#: cps/web.py:1909
#: cps/web.py:1889
msgid "This username or email address is already in use."
msgstr "Usuario o dirección de correo en uso."
#: cps/web.py:1928 cps/web.py:2024
#: cps/web.py:1908 cps/web.py:2004
#, python-format
msgid "you are now logged in as: '%(nickname)s'"
msgstr "Sesion iniciada como : '%(nickname)s'"
#: cps/web.py:1933
#: cps/web.py:1913
msgid "Wrong Username or Password"
msgstr "Usuario o contraseña invalido"
#: cps/web.py:1939 cps/web.py:1960
#: cps/web.py:1919 cps/web.py:1940
msgid "login"
msgstr "Iniciar sesión"
#: cps/web.py:1972 cps/web.py:2003
#: cps/web.py:1952 cps/web.py:1983
msgid "Token not found"
msgstr ""
#: cps/web.py:1980 cps/web.py:2011
#: cps/web.py:1960 cps/web.py:1991
msgid "Token has expired"
msgstr ""
#: cps/web.py:1988
#: cps/web.py:1968
msgid "Success! Please return to your device"
msgstr ""
#: cps/web.py:2038
#: cps/web.py:2018
msgid "Please configure the SMTP mail settings first..."
msgstr "Configurar primero los parametros SMTP por favor..."
#: cps/web.py:2042
#: cps/web.py:2022
#, python-format
msgid "Book successfully send to %(kindlemail)s"
msgstr "Envio de Libro a %(kindlemail)s correctamente"
#: cps/web.py:2046
#: cps/web.py:2026
#, python-format
msgid "There was an error sending this book: %(res)s"
msgstr "Ha sucedido un error en el envio del Libro: %(res)s"
#: cps/web.py:2048 cps/web.py:2598
#: cps/web.py:2028 cps/web.py:2578
msgid "Please configure your kindle email address first..."
msgstr "Configurar primero la dirección de correo Kindle por favor..."
#: cps/web.py:2092
#: cps/web.py:2072
#, python-format
msgid "Book has been added to shelf: %(sname)s"
msgstr "El libro fue agregado a el estante: %(sname)s"
#: cps/web.py:2127
#: cps/web.py:2107
#, python-format
msgid "Book has been removed from shelf: %(sname)s"
msgstr "El libro fue removido del estante: %(sname)s"
#: cps/web.py:2146 cps/web.py:2170
#: cps/web.py:2126 cps/web.py:2150
#, python-format
msgid "A shelf with the name '%(title)s' already exists."
msgstr "Une étagère de ce nom '%(title)s' existe déjà."
#: cps/web.py:2151
#: cps/web.py:2131
#, python-format
msgid "Shelf %(title)s created"
msgstr "Estante %(title)s creado"
#: cps/web.py:2153 cps/web.py:2181
#: cps/web.py:2133 cps/web.py:2161
msgid "There was an error"
msgstr "Ha sucedido un error"
#: cps/web.py:2154 cps/web.py:2156
#: cps/web.py:2134 cps/web.py:2136
msgid "create a shelf"
msgstr "crear un estante"
#: cps/web.py:2179
#: cps/web.py:2159
#, python-format
msgid "Shelf %(title)s changed"
msgstr "Estante %(title)s cambiado"
#: cps/web.py:2182 cps/web.py:2184
#: cps/web.py:2162 cps/web.py:2164
msgid "Edit a shelf"
msgstr "Editar un estante"
#: cps/web.py:2204
#: cps/web.py:2184
#, python-format
msgid "successfully deleted shelf %(name)s"
msgstr "Estante %(name)s fue borrado correctamente"
#: cps/web.py:2226
#: cps/web.py:2206
#, python-format
msgid "Shelf: '%(name)s'"
msgstr "Estante: '%(name)s'"
#: cps/web.py:2229
#: cps/web.py:2209
msgid "Error opening shelf. Shelf does not exist or is not accessible"
msgstr ""
#: cps/web.py:2261
#: cps/web.py:2241
#, python-format
msgid "Change order of Shelf: '%(name)s'"
msgstr "Cambiar orden del estante: '%(name)s'"
#: cps/web.py:2326
#: cps/web.py:2306
msgid "Found an existing account for this email address."
msgstr "Existe una cuenta vinculada a esta dirección de correo."
#: cps/web.py:2328 cps/web.py:2332
#: cps/web.py:2308 cps/web.py:2312
#, python-format
msgid "%(name)s's profile"
msgstr "Perfil de %(name)s"
#: cps/web.py:2329
#: cps/web.py:2309
msgid "Profile updated"
msgstr "Perfil actualizado"
#: cps/web.py:2343
#: cps/web.py:2323
msgid "Admin page"
msgstr "Página de administración"
#: cps/web.py:2467
#: cps/web.py:2447
msgid "Calibre-web configuration updated"
msgstr "Configuración de Calibre-web actualizada"
#: cps/web.py:2474 cps/web.py:2480 cps/web.py:2494
#: cps/web.py:2454 cps/web.py:2460 cps/web.py:2474
msgid "Basic Configuration"
msgstr "Configuración básica"
#: cps/web.py:2478
#: cps/web.py:2458
msgid "DB location is not valid, please enter correct path"
msgstr "Localicación de la BD inválida. Por favor, introduzca la ruta correcta."
#: cps/templates/admin.html:34 cps/web.py:2515 cps/web.py:2568
#: cps/templates/admin.html:34 cps/web.py:2495 cps/web.py:2548
msgid "Add new user"
msgstr "Agregar un nuevo usuario"
#: cps/web.py:2560
#: cps/web.py:2540
#, python-format
msgid "User '%(user)s' created"
msgstr "Usuario '%(user)s' creado"
#: cps/web.py:2564
#: cps/web.py:2544
msgid "Found an existing account for this email address or nickname."
msgstr ""
"Se ha encontrado una cuenta vinculada a esta dirección de correo o nombre"
" de usuario."
#: cps/web.py:2586
#: cps/web.py:2566
msgid "Mail settings updated"
msgstr "Parámetros de correo actualizados"
#: cps/web.py:2593
#: cps/web.py:2573
#, python-format
msgid "Test E-Mail successfully send to %(kindlemail)s"
msgstr "Exito al realizar envio de prueba a %(kindlemail)s"
#: cps/web.py:2596
#: cps/web.py:2576
#, python-format
msgid "There was an error sending the Test E-Mail: %(res)s"
msgstr "Error al realizar envio de prueba a E-Mail: %(res)s"
#: cps/web.py:2600
#: cps/web.py:2580
msgid "E-Mail settings updated"
msgstr "Ajustes de correo electrónico actualizados"
#: cps/web.py:2601
#: cps/web.py:2581
msgid "Edit mail settings"
msgstr "Editar parametros de correo"
#: cps/web.py:2630
#: cps/web.py:2610
#, python-format
msgid "User '%(nick)s' deleted"
msgstr "Usuario '%(nick)s' borrado"
#: cps/web.py:2728
#: cps/web.py:2708
#, python-format
msgid "User '%(nick)s' updated"
msgstr "Usuario '%(nick)s' actualizado"
#: cps/web.py:2731
#: cps/web.py:2711
msgid "An unknown error occured."
msgstr "Error inesperado."
#: cps/web.py:2734
#: cps/web.py:2714
#, python-format
msgid "Edit User %(nick)s"
msgstr "Editar Usuario %(nick)s"
#: cps/web.py:2756
#: cps/web.py:2730
msgid "Error opening eBook. File does not exist or file is not accessible"
msgstr ""
#: cps/web.py:2771 cps/web.py:2954 cps/web.py:3078
#: cps/web.py:2745 cps/web.py:2917 cps/web.py:3060
msgid "edit metadata"
msgstr "editar metainformación"
#: cps/web.py:2783 cps/web.py:2787
#: cps/web.py:2757 cps/web.py:2761
msgid "unknown"
msgstr ""
#: cps/web.py:2972
#: cps/web.py:2954
#, python-format
msgid "File extension \"%s\" is not allowed to be uploaded to this server"
msgstr "No se permite subir archivos con la extensión \"%s\" a este servidor"
#: cps/web.py:2978
#: cps/web.py:2960
msgid "File to be uploaded must have an extension"
msgstr "El archivo a subir debe tener una extensión"
#: cps/web.py:2997
#: cps/web.py:2979
#, python-format
msgid "Failed to create path %s (Permission denied)."
msgstr "Fallo al crear la ruta %s (permiso negado)"
#: cps/web.py:3002
#: cps/web.py:2984
#, python-format
msgid "Failed to store file %s (Permission denied)."
msgstr "Fallo al almacenar el archivo %s (permiso negado)"
#: cps/web.py:3007
#: cps/web.py:2989
#, python-format
msgid "Failed to delete file %s (Permission denied)."
msgstr "Fallo al borrar el archivo %s (permiso negado)"
@ -475,6 +475,10 @@ msgstr "Descarga"
msgid "Upload"
msgstr "Subir archivo"
#: cps/templates/detail.html:14
msgid "Upload format"
msgstr "Subir formato"
#: cps/templates/admin.html:15
msgid "Edit"
msgstr "Editar"
@ -608,6 +612,18 @@ msgstr "¿Seguro que quiere detener Calibre-web?"
msgid "Updating, please do not reload page"
msgstr "Actualizando. Por favor, no recargue la página."
#: cps/templates/author.html:15
msgid "via"
msgstr ""
#: cps/templates/author.html:23
msgid "In Library"
msgstr ""
#: cps/templates/author.html:69
msgid "More by"
msgstr ""
#: cps/templates/book_edit.html:16
msgid "Delete Book"
msgstr ""
@ -616,12 +632,13 @@ msgstr ""
msgid "Book Title"
msgstr "Titulo del Libro"
#: cps/templates/book_edit.html:26 cps/templates/book_edit.html:188
#: cps/templates/search_form.html:10
#: cps/templates/book_edit.html:26 cps/templates/book_edit.html:208
#: cps/templates/book_edit.html:226 cps/templates/search_form.html:10
msgid "Author"
msgstr "Autor"
#: cps/templates/book_edit.html:30 cps/templates/book_edit.html:190
#: cps/templates/book_edit.html:30 cps/templates/book_edit.html:213
#: cps/templates/book_edit.html:228
msgid "Description"
msgstr "Descripcion"
@ -704,35 +721,35 @@ msgstr "Buscar palabras clave"
msgid "Go!"
msgstr "¡Vamos!"
#: cps/templates/book_edit.html:168
#: cps/templates/book_edit.html:171
msgid "Click the cover to load metadata to the form"
msgstr "Haga clic en la portada para cargar la metainformación en el formulario"
#: cps/templates/book_edit.html:172 cps/templates/book_edit.html:185
#: cps/templates/book_edit.html:183 cps/templates/book_edit.html:223
msgid "Loading..."
msgstr "Cargando..."
#: cps/templates/book_edit.html:175 cps/templates/layout.html:199
#: cps/templates/book_edit.html:188 cps/templates/layout.html:199
msgid "Close"
msgstr "Cerrar"
#: cps/templates/book_edit.html:186
msgid "Search error!"
msgstr "¡Error en la búsqueda!"
#: cps/templates/book_edit.html:187
msgid "No Result! Please try anonther keyword."
msgstr "¡Sin resultados! Por favor, pruebe otra palabra clave."
#: cps/templates/book_edit.html:189 cps/templates/detail.html:125
#: cps/templates/search_form.html:14
#: cps/templates/book_edit.html:210 cps/templates/book_edit.html:227
#: cps/templates/detail.html:125 cps/templates/search_form.html:14
msgid "Publisher"
msgstr "Editor"
#: cps/templates/book_edit.html:191
#: cps/templates/book_edit.html:215 cps/templates/book_edit.html:229
msgid "Source"
msgstr "Origen"
#: cps/templates/book_edit.html:224
msgid "Search error!"
msgstr "¡Error en la búsqueda!"
#: cps/templates/book_edit.html:225
msgid "No Result! Please try anonther keyword."
msgstr "¡Sin resultados! Por favor, pruebe otra palabra clave."
#: cps/templates/config_edit.html:7
msgid "Location of Calibre database"
msgstr "Ubicación de la base de datos Calibre"
@ -822,7 +839,7 @@ msgstr ""
msgid "Default Settings for new users"
msgstr "Ajustes por defecto para nuevos usuarios"
#: cps/templates/config_edit.html:128 cps/templates/user_edit.html:90
#: cps/templates/config_edit.html:128 cps/templates/user_edit.html:86
msgid "Admin user"
msgstr "Usuario Administrador"
@ -925,6 +942,11 @@ msgstr "Guardar cambios y enviar un correo de prueba"
msgid "Next"
msgstr "Siguiente"
#: cps/templates/feed.xml:29 cps/templates/index.xml:7
#: cps/templates/layout.html:40 cps/templates/layout.html:41
msgid "Search"
msgstr "Buscar"
#: cps/templates/index.html:5
msgid "Discover (Random Books)"
msgstr "Descubrir (Libros al azar)"
@ -933,52 +955,47 @@ msgstr "Descubrir (Libros al azar)"
msgid "Start"
msgstr "Iniciar"
#: cps/templates/index.xml:7 cps/templates/layout.html:40
#: cps/templates/layout.html:41
msgid "Search"
msgstr "Buscar"
#: cps/templates/index.xml:15 cps/templates/layout.html:121
#: cps/templates/index.xml:14 cps/templates/layout.html:121
msgid "Hot Books"
msgstr "Libros Populares"
#: cps/templates/index.xml:19
#: cps/templates/index.xml:18
msgid "Popular publications from this catalog based on Downloads."
msgstr "Publicaciones mas populares para este catálogo basadas en las descargas."
#: cps/templates/index.xml:22 cps/templates/layout.html:124
#: cps/templates/index.xml:21 cps/templates/layout.html:124
msgid "Best rated Books"
msgstr "Libros mejor valorados"
#: cps/templates/index.xml:26
#: cps/templates/index.xml:25
msgid "Popular publications from this catalog based on Rating."
msgstr "Publicaciones populares del catalogo basados en el puntaje."
#: cps/templates/index.xml:29
#: cps/templates/index.xml:28
msgid "New Books"
msgstr "Nuevos libros"
#: cps/templates/index.xml:33
#: cps/templates/index.xml:32
msgid "The latest Books"
msgstr "Libros recientes"
#: cps/templates/index.xml:40
#: cps/templates/index.xml:39
msgid "Show Random Books"
msgstr "Mostrar libros al azar"
#: cps/templates/index.xml:57 cps/templates/layout.html:139
#: cps/templates/index.xml:56 cps/templates/layout.html:139
msgid "Authors"
msgstr "Autores"
#: cps/templates/index.xml:61
#: cps/templates/index.xml:60
msgid "Books ordered by Author"
msgstr "Libros ordenados por Autor"
#: cps/templates/index.xml:68
#: cps/templates/index.xml:67
msgid "Books ordered by category"
msgstr "Libros ordenados por Categorias"
#: cps/templates/index.xml:75
#: cps/templates/index.xml:74
msgid "Books ordered by series"
msgstr "Libros ordenados por Series"
@ -1089,7 +1106,11 @@ msgstr ""
msgid "Calibre Web ebook catalog"
msgstr "Catálogo de libros electrónicos Calibre Web"
#: cps/templates/read.html:125
#: cps/templates/read.html:69
msgid "Settings"
msgstr ""
#: cps/templates/read.html:72
msgid "Reflow text when sidebars are open."
msgstr "Redimensionar el texto cuando las barras laterales estan abiertas"
@ -1138,7 +1159,7 @@ msgid "No Results for:"
msgstr "Sin resultados para:"
#: cps/templates/search.html:7
msgid "Please try a diffrent Search"
msgid "Please try a different search"
msgstr "Intente una busqueda diferente"
#: cps/templates/search.html:9
@ -1230,45 +1251,45 @@ msgid "Show all"
msgstr "Mostrar Todo"
#: cps/templates/user_edit.html:46
msgid "Show mature content"
msgstr ""
#: cps/templates/user_edit.html:50
msgid "Show random books"
msgstr "Mostrar libros al azar"
#: cps/templates/user_edit.html:54
#: cps/templates/user_edit.html:50
msgid "Show hot books"
msgstr "Mostrar libros populares"
#: cps/templates/user_edit.html:58
#: cps/templates/user_edit.html:54
msgid "Show best rated books"
msgstr "Mostrar libros mejor valorados"
#: cps/templates/user_edit.html:62
#: cps/templates/user_edit.html:58
msgid "Show language selection"
msgstr "Mostrar lenguaje seleccionado"
#: cps/templates/user_edit.html:66
#: cps/templates/user_edit.html:62
msgid "Show series selection"
msgstr "Mostrar series seleccionadas"
#: cps/templates/user_edit.html:70
#: cps/templates/user_edit.html:66
msgid "Show category selection"
msgstr "Mostrar categorias elegidas"
#: cps/templates/user_edit.html:74
#: cps/templates/user_edit.html:70
msgid "Show author selection"
msgstr "Mostrar selección de autores"
#: cps/templates/user_edit.html:78
#: cps/templates/user_edit.html:74
msgid "Show read and unread"
msgstr "Mostrar leídos y no leídos"
#: cps/templates/user_edit.html:82
#: cps/templates/user_edit.html:78
msgid "Show random books in detail view"
msgstr "Mostrar libro aleatorios con vista detallada"
#: cps/templates/user_edit.html:90
msgid "Show mature content"
msgstr ""
#: cps/templates/user_edit.html:123
msgid "Delete this user"
msgstr "Borrar este usuario"

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

@ -22,7 +22,7 @@ msgid ""
msgstr ""
"Project-Id-Version: Calibre-web dutch translation by Ed Driesen (GPL V3)\n"
"Report-Msgid-Bugs-To: EMAIL@ADDRESS\n"
"POT-Creation-Date: 2017-08-12 18:55+0200\n"
"POT-Creation-Date: 2017-09-16 07:48+0200\n"
"PO-Revision-Date: 2017-06-21 20:15+0200\n"
"Last-Translator: \n"
"Language: nl\n"
@ -33,7 +33,7 @@ msgstr ""
"Content-Transfer-Encoding: 8bit\n"
"Generated-By: Babel 2.4.0\n"
#: cps/book_formats.py:118 cps/book_formats.py:122 cps/web.py:1374
#: cps/book_formats.py:118 cps/book_formats.py:122 cps/web.py:1358
msgid "not installed"
msgstr "niet geïnstalleerd"
@ -77,382 +77,384 @@ msgstr "Stuur naar Kindle:"
msgid "Could not find any formats suitable for sending by email"
msgstr "Kon geen geschikte formaten vinden om te verzenden per email"
#: cps/ub.py:542
#: cps/ub.py:556
msgid "Guest"
msgstr "Gast"
#: cps/web.py:974
#: cps/web.py:953
msgid "Requesting update package"
msgstr "Update pakket wordt aangevraagd"
#: cps/web.py:975
#: cps/web.py:954
msgid "Downloading update package"
msgstr "Update pakket wordt gedownload"
#: cps/web.py:976
#: cps/web.py:955
msgid "Unzipping update package"
msgstr "Update pakket wordt uitgepakt"
#: cps/web.py:977
#: cps/web.py:956
msgid "Files are replaced"
msgstr "Bestanden zijn vervangen"
#: cps/web.py:978
#: cps/web.py:957
msgid "Database connections are closed"
msgstr "Database verbindingen zijn gesloten"
#: cps/web.py:979
#: cps/web.py:958
msgid "Server is stopped"
msgstr "Server is gestopt"
#: cps/web.py:980
#: cps/web.py:959
msgid "Update finished, please press okay and reload page"
msgstr "Update voltooid, klik op ok en herlaad de pagina"
#: cps/web.py:1054
#: cps/web.py:1033
msgid "Recently Added Books"
msgstr ""
msgstr "Recent toegevoegde boeken"
#: cps/web.py:1063
#: cps/web.py:1042
msgid "Newest Books"
msgstr ""
msgstr "Nieuwste boeken"
#: cps/web.py:1072
#: cps/web.py:1051
msgid "Oldest Books"
msgstr ""
msgstr "Oudste boeken"
#: cps/web.py:1081
#: cps/web.py:1060
msgid "Books (A-Z)"
msgstr ""
msgstr "Boeken (A-Z)"
#: cps/web.py:1090
#: cps/web.py:1069
msgid "Books (Z-A)"
msgstr ""
msgstr "Boeken (A-Z)"
#: cps/web.py:1126
#: cps/web.py:1096
msgid "Hot Books (most downloaded)"
msgstr "Populaire boeken (meeste downloads)"
#: cps/web.py:1136
#: cps/web.py:1106
msgid "Best rated books"
msgstr "Best beoordeelde boeken"
#: cps/templates/index.xml:36 cps/web.py:1145
#: cps/templates/index.xml:35 cps/web.py:1115
msgid "Random Books"
msgstr "Willekeurige boeken"
#: cps/web.py:1161
#: cps/web.py:1124
msgid "Author list"
msgstr "Auteur lijst"
#: cps/web.py:1181 cps/web.py:1212 cps/web.py:1351 cps/web.py:1835
#: cps/web.py:1134 cps/web.py:1190 cps/web.py:1315 cps/web.py:1774
msgid "Error opening eBook. File does not exist or file is not accessible:"
msgstr ""
"Fout bij openen van het boek. Bestand bestaat niet of is niet "
"toegankelijk:"
#: cps/templates/index.xml:71 cps/web.py:1198
#: cps/templates/index.xml:70 cps/web.py:1176
msgid "Series list"
msgstr "Series lijst"
msgstr "Serie lijst"
#: cps/web.py:1210
#: cps/web.py:1188
#, python-format
msgid "Series: %(serie)s"
msgstr "Series: %(serie)s"
msgstr "Serie: %(serie)s"
#: cps/web.py:1243
#: cps/web.py:1221
msgid "Available languages"
msgstr "Beschikbare talen"
#: cps/web.py:1258
#: cps/web.py:1236
#, python-format
msgid "Language: %(name)s"
msgstr "Taal: %(name)s"
#: cps/templates/index.xml:64 cps/web.py:1274
#: cps/templates/index.xml:63 cps/web.py:1245
msgid "Category list"
msgstr "Categorie lijst"
#: cps/web.py:1286
#: cps/web.py:1257
#, python-format
msgid "Category: %(name)s"
msgstr "Categorie: %(name)s"
#: cps/web.py:1385
#: cps/web.py:1369
msgid "Excecution permissions missing"
msgstr ""
#: cps/web.py:1399
#: cps/web.py:1383
msgid "Statistics"
msgstr "Statistieken"
#: cps/web.py:1563
#: cps/web.py:1547
msgid "Server restarted, please reload page"
msgstr "Server herstart, gelieve de pagina herladen"
#: cps/web.py:1565
#: cps/web.py:1549
msgid "Performing shutdown of server, please close window"
msgstr "Bezig met het stoppen vande server, gelieve venster afsluiten"
msgstr "Bezig met het stoppen van de server, gelieve venster te sluiten"
#: cps/web.py:1581
#: cps/web.py:1565
msgid "Update done"
msgstr "Update voltooid"
#: cps/web.py:1662 cps/web.py:1675
#: cps/web.py:1640 cps/web.py:1653
msgid "search"
msgstr "zoek"
#: cps/templates/index.xml:43 cps/templates/index.xml:47
#: cps/templates/layout.html:127 cps/web.py:1751
#: cps/templates/index.xml:42 cps/templates/index.xml:46
#: cps/templates/layout.html:127 cps/web.py:1729
msgid "Read Books"
msgstr "Lees Boeken"
msgstr "Gelezen Boeken"
#: cps/templates/index.xml:50 cps/templates/index.xml:54
#: cps/templates/layout.html:128 cps/web.py:1754
#: cps/templates/index.xml:49 cps/templates/index.xml:53
#: cps/templates/layout.html:128 cps/web.py:1732
msgid "Unread Books"
msgstr "Ongelezen Boeken"
#: cps/web.py:1821 cps/web.py:1823 cps/web.py:1825 cps/web.py:1832
#: cps/web.py:1805 cps/web.py:1807 cps/web.py:1809 cps/web.py:1816
msgid "Read a Book"
msgstr "Lees een boek"
#: cps/web.py:1888 cps/web.py:2513
#: cps/web.py:1868 cps/web.py:2493
msgid "Please fill out all fields!"
msgstr "Gelieve alle velden in te vullen!"
#: cps/web.py:1889 cps/web.py:1905 cps/web.py:1910 cps/web.py:1912
#: cps/web.py:1869 cps/web.py:1885 cps/web.py:1890 cps/web.py:1892
msgid "register"
msgstr "registreer"
#: cps/web.py:1904
#: cps/web.py:1884
msgid "An unknown error occured. Please try again later."
msgstr "Een onbekende fout deed zich voor. Gelieve later nog eens te proberen."
#: cps/web.py:1909
#: cps/web.py:1889
msgid "This username or email address is already in use."
msgstr "Deze gebruikersnaam of email adres is reeds in gebruik."
msgstr "Deze gebruikersnaam of dit emailadres is reeds in gebruik."
#: cps/web.py:1928 cps/web.py:2024
#: cps/web.py:1908 cps/web.py:2004
#, python-format
msgid "you are now logged in as: '%(nickname)s'"
msgstr "je bent nu ingelogd als: '%(nickname)s'"
#: cps/web.py:1933
#: cps/web.py:1913
msgid "Wrong Username or Password"
msgstr "Verkeerde gebruikersnaam of Wachtwoord"
#: cps/web.py:1939 cps/web.py:1960
#: cps/web.py:1919 cps/web.py:1940
msgid "login"
msgstr "login"
#: cps/web.py:1972 cps/web.py:2003
#: cps/web.py:1952 cps/web.py:1983
msgid "Token not found"
msgstr ""
msgstr "Token niet gevonden"
#: cps/web.py:1980 cps/web.py:2011
#: cps/web.py:1960 cps/web.py:1991
msgid "Token has expired"
msgstr ""
msgstr "Token is verlopen"
#: cps/web.py:1988
#: cps/web.py:1968
msgid "Success! Please return to your device"
msgstr ""
msgstr "Gelukt! Ga terug naar je apparaat"
#: cps/web.py:2038
#: cps/web.py:2018
msgid "Please configure the SMTP mail settings first..."
msgstr "Gelieve de SMTP mail instellingen eerstte configureren..."
msgstr "Gelieve de SMTP mail instellingen eerst te configureren..."
#: cps/web.py:2042
#: cps/web.py:2022
#, python-format
msgid "Book successfully send to %(kindlemail)s"
msgstr "Boek met succes verstuurd naar %(kindlemail)s"
#: cps/web.py:2046
#: cps/web.py:2026
#, python-format
msgid "There was an error sending this book: %(res)s"
msgstr "Er trad een fout op bij het versturen van dit boek: %(res)s"
#: cps/web.py:2048 cps/web.py:2598
#: cps/web.py:2028 cps/web.py:2578
msgid "Please configure your kindle email address first..."
msgstr "Gelieve eerst je kindle email adres te configureren..."
#: cps/web.py:2092
#: cps/web.py:2072
#, python-format
msgid "Book has been added to shelf: %(sname)s"
msgstr "Boek werd toegevoegd aan boekenplank: %(sname)s"
#: cps/web.py:2127
#: cps/web.py:2107
#, python-format
msgid "Book has been removed from shelf: %(sname)s"
msgstr "Boek werd verwijderd van boekenplank: %(sname)s"
#: cps/web.py:2146 cps/web.py:2170
#: cps/web.py:2126 cps/web.py:2150
#, python-format
msgid "A shelf with the name '%(title)s' already exists."
msgstr "Een boekenplank met de naam '%(title)s' bestaat reeds."
#: cps/web.py:2151
#: cps/web.py:2131
#, python-format
msgid "Shelf %(title)s created"
msgstr "Boekenplank %(title)s aangemaakt"
#: cps/web.py:2153 cps/web.py:2181
#: cps/web.py:2133 cps/web.py:2161
msgid "There was an error"
msgstr "Er deed zich een fout voor"
#: cps/web.py:2154 cps/web.py:2156
#: cps/web.py:2134 cps/web.py:2136
msgid "create a shelf"
msgstr "maak een boekenplank"
#: cps/web.py:2179
#: cps/web.py:2159
#, python-format
msgid "Shelf %(title)s changed"
msgstr "Boekenplank %(title)s gewijzigd"
#: cps/web.py:2182 cps/web.py:2184
#: cps/web.py:2162 cps/web.py:2164
msgid "Edit a shelf"
msgstr "Bewerk een boekenplank"
#: cps/web.py:2204
#: cps/web.py:2184
#, python-format
msgid "successfully deleted shelf %(name)s"
msgstr "Succesvol boekenplank %(name)s gewist"
msgstr "Boekenplank %(name)s succesvol gewist"
#: cps/web.py:2226
#: cps/web.py:2206
#, python-format
msgid "Shelf: '%(name)s'"
msgstr "Boekenplank: '%(name)s'"
#: cps/web.py:2229
#: cps/web.py:2209
msgid "Error opening shelf. Shelf does not exist or is not accessible"
msgstr ""
"Fout bij openen boekenplank. Boekenplank bestaat niet of is niet "
"toegankelijk"
#: cps/web.py:2261
#: cps/web.py:2241
#, python-format
msgid "Change order of Shelf: '%(name)s'"
msgstr "Verander volgorde van Boekenplank: '%(name)s'"
#: cps/web.py:2326
#: cps/web.py:2306
msgid "Found an existing account for this email address."
msgstr "Een bestaand gebruiker gevonden voor dit email adres."
#: cps/web.py:2328 cps/web.py:2332
#: cps/web.py:2308 cps/web.py:2312
#, python-format
msgid "%(name)s's profile"
msgstr "%(name)s's profiel"
#: cps/web.py:2329
#: cps/web.py:2309
msgid "Profile updated"
msgstr "Profiel aangepast"
#: cps/web.py:2343
#: cps/web.py:2323
msgid "Admin page"
msgstr "Administratie pagina"
#: cps/web.py:2467
#: cps/web.py:2447
msgid "Calibre-web configuration updated"
msgstr "Calibre-web configuratie aangepast"
#: cps/web.py:2474 cps/web.py:2480 cps/web.py:2494
#: cps/web.py:2454 cps/web.py:2460 cps/web.py:2474
msgid "Basic Configuration"
msgstr "Basis configuratie"
#: cps/web.py:2478
#: cps/web.py:2458
msgid "DB location is not valid, please enter correct path"
msgstr "DB locatie is niet geldig, gelieve het correcte pad in te geven"
#: cps/templates/admin.html:34 cps/web.py:2515 cps/web.py:2568
#: cps/templates/admin.html:34 cps/web.py:2495 cps/web.py:2548
msgid "Add new user"
msgstr "Voeg nieuwe gebruiker toe"
#: cps/web.py:2560
#: cps/web.py:2540
#, python-format
msgid "User '%(user)s' created"
msgstr "Gebruiker '%(user)s' aangemaakt"
#: cps/web.py:2564
#: cps/web.py:2544
msgid "Found an existing account for this email address or nickname."
msgstr "Een bestaand gebruiker gevonden voor dit email adres of gebruikersnaam."
msgstr "Een bestaande gebruiker gevonden voor dit emailadres of gebruikersnaam."
#: cps/web.py:2586
#: cps/web.py:2566
msgid "Mail settings updated"
msgstr "Mail instellingen aangepast"
#: cps/web.py:2593
#: cps/web.py:2573
#, python-format
msgid "Test E-Mail successfully send to %(kindlemail)s"
msgstr "Test email met succes verstuurd naar %(kindlemail)s"
#: cps/web.py:2596
#: cps/web.py:2576
#, python-format
msgid "There was an error sending the Test E-Mail: %(res)s"
msgstr "Er trad een fout op met het versturen van de test email: %(res)s"
#: cps/web.py:2600
#: cps/web.py:2580
msgid "E-Mail settings updated"
msgstr "Email instellingen aangepast"
#: cps/web.py:2601
#: cps/web.py:2581
msgid "Edit mail settings"
msgstr "Bewerk mail instellingen"
#: cps/web.py:2630
#: cps/web.py:2610
#, python-format
msgid "User '%(nick)s' deleted"
msgstr "Gebruiker '%(nick)s' verwijderd"
#: cps/web.py:2728
#: cps/web.py:2708
#, python-format
msgid "User '%(nick)s' updated"
msgstr "Gebruiker '%(nick)s' aangepast"
#: cps/web.py:2731
#: cps/web.py:2711
msgid "An unknown error occured."
msgstr "Een onbekende fout deed zich voor."
#: cps/web.py:2734
#: cps/web.py:2714
#, python-format
msgid "Edit User %(nick)s"
msgstr "Bewerk gebruiker '%(nick)s'"
#: cps/web.py:2756
#: cps/web.py:2730
msgid "Error opening eBook. File does not exist or file is not accessible"
msgstr ""
msgstr "Fout bij openen eBook. Het bestand bestaat niet of is niet toegankelijk"
#: cps/web.py:2771 cps/web.py:2954 cps/web.py:3078
#: cps/web.py:2745 cps/web.py:2917 cps/web.py:3060
msgid "edit metadata"
msgstr "Bewerk metadata"
#: cps/web.py:2783 cps/web.py:2787
#: cps/web.py:2757 cps/web.py:2761
msgid "unknown"
msgstr "onbekend"
#: cps/web.py:2972
#: cps/web.py:2954
#, python-format
msgid "File extension \"%s\" is not allowed to be uploaded to this server"
msgstr "Het uploaden van bestandsextensie \"%s\" is niet toegestaan op deze server"
#: cps/web.py:2978
#: cps/web.py:2960
msgid "File to be uploaded must have an extension"
msgstr "Up te loaden bestanden dienen een extentie te hebben"
msgstr "Up te loaden bestanden dienen een extensie te hebben"
#: cps/web.py:2997
#: cps/web.py:2979
#, python-format
msgid "Failed to create path %s (Permission denied)."
msgstr "Het pad %s aanmaken gefaald (Geen toestemming)."
msgstr "Het pad %s aanmaken mislukt (Geen toestemming)."
#: cps/web.py:3002
#: cps/web.py:2984
#, python-format
msgid "Failed to store file %s (Permission denied)."
msgstr "Bestand %s opslaan gefaald (Geen toestemming)."
msgstr "Bestand %s opslaan mislukt (Geen toestemming)."
#: cps/web.py:3007
#: cps/web.py:2989
#, python-format
msgid "Failed to delete file %s (Permission denied)."
msgstr "Bestand %s wissen gefaald (Geen toestemming)."
msgstr "Bestand %s wissen mislukt (Geen toestemming)."
#: cps/templates/admin.html:4
msgid "User list"
msgstr "Gebruikers lijst"
msgstr "Gebruikerslijst"
#: cps/templates/admin.html:8
msgid "Nickname"
@ -557,7 +559,7 @@ msgstr "Anoniem verkennen"
#: cps/templates/admin.html:67 cps/templates/remote_login.html:4
msgid "Remote Login"
msgstr ""
msgstr "Login op afstand"
#: cps/templates/admin.html:80
msgid "Administration"
@ -616,6 +618,18 @@ msgstr "Wil je Calibre-web echt stoppen?"
msgid "Updating, please do not reload page"
msgstr "Aan het updaten, gelieve de pagina niet te herladen"
#: cps/templates/author.html:15
msgid "via"
msgstr ""
#: cps/templates/author.html:23
msgid "In Library"
msgstr ""
#: cps/templates/author.html:69
msgid "More by"
msgstr ""
#: cps/templates/book_edit.html:16
msgid "Delete Book"
msgstr "Wis boek"
@ -624,12 +638,13 @@ msgstr "Wis boek"
msgid "Book Title"
msgstr "Boek titel"
#: cps/templates/book_edit.html:26 cps/templates/book_edit.html:188
#: cps/templates/search_form.html:10
#: cps/templates/book_edit.html:26 cps/templates/book_edit.html:208
#: cps/templates/book_edit.html:226 cps/templates/search_form.html:10
msgid "Author"
msgstr "Auteur"
#: cps/templates/book_edit.html:30 cps/templates/book_edit.html:190
#: cps/templates/book_edit.html:30 cps/templates/book_edit.html:213
#: cps/templates/book_edit.html:228
msgid "Description"
msgstr "Omschrijving"
@ -712,35 +727,35 @@ msgstr "Zoek voor zoekwoord"
msgid "Go!"
msgstr "Start!"
#: cps/templates/book_edit.html:168
#: cps/templates/book_edit.html:171
msgid "Click the cover to load metadata to the form"
msgstr "Klik op de omslag om de metatadata in het formulier te laden"
#: cps/templates/book_edit.html:172 cps/templates/book_edit.html:185
#: cps/templates/book_edit.html:183 cps/templates/book_edit.html:223
msgid "Loading..."
msgstr "Aan het laden..."
#: cps/templates/book_edit.html:175 cps/templates/layout.html:199
#: cps/templates/book_edit.html:188 cps/templates/layout.html:199
msgid "Close"
msgstr "Sluit"
#: cps/templates/book_edit.html:186
msgid "Search error!"
msgstr "Zoek fout!"
#: cps/templates/book_edit.html:187
msgid "No Result! Please try anonther keyword."
msgstr "Geen resultaat! Gelieve een ander zoekwoord proberen"
#: cps/templates/book_edit.html:189 cps/templates/detail.html:125
#: cps/templates/search_form.html:14
#: cps/templates/book_edit.html:210 cps/templates/book_edit.html:227
#: cps/templates/detail.html:125 cps/templates/search_form.html:14
msgid "Publisher"
msgstr "Uitgever"
#: cps/templates/book_edit.html:191
#: cps/templates/book_edit.html:215 cps/templates/book_edit.html:229
msgid "Source"
msgstr "Bron"
#: cps/templates/book_edit.html:224
msgid "Search error!"
msgstr "Zoek fout!"
#: cps/templates/book_edit.html:225
msgid "No Result! Please try anonther keyword."
msgstr "Geen resultaat! Gelieve een ander zoekwoord proberen"
#: cps/templates/config_edit.html:7
msgid "Location of Calibre database"
msgstr "Locatie van de Calibre database"
@ -830,7 +845,7 @@ msgstr ""
msgid "Default Settings for new users"
msgstr "Standaard instellingen voor nieuwe gebruikers"
#: cps/templates/config_edit.html:128 cps/templates/user_edit.html:90
#: cps/templates/config_edit.html:128 cps/templates/user_edit.html:86
msgid "Admin user"
msgstr "Administratie gebruiker"
@ -933,6 +948,11 @@ msgstr "Bewaar instellingen en stuur test email"
msgid "Next"
msgstr "Volgende"
#: cps/templates/feed.xml:29 cps/templates/index.xml:7
#: cps/templates/layout.html:40 cps/templates/layout.html:41
msgid "Search"
msgstr "Zoek"
#: cps/templates/index.html:5
msgid "Discover (Random Books)"
msgstr "Ontdek (Willekeurige Boeken)"
@ -941,52 +961,47 @@ msgstr "Ontdek (Willekeurige Boeken)"
msgid "Start"
msgstr "Start"
#: cps/templates/index.xml:7 cps/templates/layout.html:40
#: cps/templates/layout.html:41
msgid "Search"
msgstr "Zoek"
#: cps/templates/index.xml:15 cps/templates/layout.html:121
#: cps/templates/index.xml:14 cps/templates/layout.html:121
msgid "Hot Books"
msgstr "Populaire Boeken"
#: cps/templates/index.xml:19
#: cps/templates/index.xml:18
msgid "Popular publications from this catalog based on Downloads."
msgstr "Populaire publicaties van deze cataloog gebaseerd op Downloads."
#: cps/templates/index.xml:22 cps/templates/layout.html:124
#: cps/templates/index.xml:21 cps/templates/layout.html:124
msgid "Best rated Books"
msgstr "Best beoordeeld"
#: cps/templates/index.xml:26
#: cps/templates/index.xml:25
msgid "Popular publications from this catalog based on Rating."
msgstr "Populaire publicaties van deze cataloog gebaseerd op Beoordeling."
#: cps/templates/index.xml:29
#: cps/templates/index.xml:28
msgid "New Books"
msgstr "Nieuwe Boeken"
#: cps/templates/index.xml:33
#: cps/templates/index.xml:32
msgid "The latest Books"
msgstr "Recentste boeken"
#: cps/templates/index.xml:40
#: cps/templates/index.xml:39
msgid "Show Random Books"
msgstr "Toon Willekeurige Boeken"
#: cps/templates/index.xml:57 cps/templates/layout.html:139
#: cps/templates/index.xml:56 cps/templates/layout.html:139
msgid "Authors"
msgstr "Auteurs"
#: cps/templates/index.xml:61
#: cps/templates/index.xml:60
msgid "Books ordered by Author"
msgstr "Boeken gesorteerd op Auteur"
#: cps/templates/index.xml:68
#: cps/templates/index.xml:67
msgid "Books ordered by category"
msgstr "Boeken gesorteerd op Categorie"
#: cps/templates/index.xml:75
#: cps/templates/index.xml:74
msgid "Books ordered by series"
msgstr "Boeken gesorteerd op Serie"
@ -1097,7 +1112,11 @@ msgstr ""
msgid "Calibre Web ebook catalog"
msgstr "Calible web ebook cataloog"
#: cps/templates/read.html:125
#: cps/templates/read.html:69
msgid "Settings"
msgstr ""
#: cps/templates/read.html:72
msgid "Reflow text when sidebars are open."
msgstr "Herschuif tekst waneer het zijpaneel open staat."
@ -1146,7 +1165,7 @@ msgid "No Results for:"
msgstr "Geen resultaat voor:"
#: cps/templates/search.html:7
msgid "Please try a diffrent Search"
msgid "Please try a different search"
msgstr "Gelieve een ander zoekwoord proberen"
#: cps/templates/search.html:9
@ -1238,45 +1257,45 @@ msgid "Show all"
msgstr "Toon alles"
#: cps/templates/user_edit.html:46
msgid "Show mature content"
msgstr ""
#: cps/templates/user_edit.html:50
msgid "Show random books"
msgstr "Toon willekeurige boeken"
#: cps/templates/user_edit.html:54
#: cps/templates/user_edit.html:50
msgid "Show hot books"
msgstr "Toon populaire boeken"
#: cps/templates/user_edit.html:58
#: cps/templates/user_edit.html:54
msgid "Show best rated books"
msgstr "Toon best beoordeelde boeken"
#: cps/templates/user_edit.html:62
#: cps/templates/user_edit.html:58
msgid "Show language selection"
msgstr "Toon taal selectie"
#: cps/templates/user_edit.html:66
#: cps/templates/user_edit.html:62
msgid "Show series selection"
msgstr "Toon serie selectie"
#: cps/templates/user_edit.html:70
#: cps/templates/user_edit.html:66
msgid "Show category selection"
msgstr "Toon categorie selectie"
#: cps/templates/user_edit.html:74
#: cps/templates/user_edit.html:70
msgid "Show author selection"
msgstr "Toon auteur selectie"
#: cps/templates/user_edit.html:78
#: cps/templates/user_edit.html:74
msgid "Show read and unread"
msgstr "Toon gelezen en ongelezen"
#: cps/templates/user_edit.html:82
#: cps/templates/user_edit.html:78
msgid "Show random books in detail view"
msgstr "Toon willekeurige boeken in gedetailleerd zicht"
#: cps/templates/user_edit.html:90
msgid "Show mature content"
msgstr ""
#: cps/templates/user_edit.html:123
msgid "Delete this user"
msgstr "Wis deze gebruiker"

@ -12,7 +12,7 @@ msgid ""
msgstr ""
"Project-Id-Version: Calibre Web - polski (POT: 2017-04-11 22:51)\n"
"Report-Msgid-Bugs-To: EMAIL@ADDRESS\n"
"POT-Creation-Date: 2017-08-12 18:55+0200\n"
"POT-Creation-Date: 2017-09-16 07:48+0200\n"
"PO-Revision-Date: 2017-04-11 22:51+0200\n"
"Last-Translator: Radosław Kierznowski <radek.kierznowski@outlook.com>\n"
"Language: pl\n"
@ -24,7 +24,7 @@ msgstr ""
"Content-Transfer-Encoding: 8bit\n"
"Generated-By: Babel 2.4.0\n"
#: cps/book_formats.py:118 cps/book_formats.py:122 cps/web.py:1374
#: cps/book_formats.py:118 cps/book_formats.py:122 cps/web.py:1358
msgid "not installed"
msgstr "nie zainstalowane"
@ -70,373 +70,373 @@ msgstr ""
"Nie można znaleźć żadnych formatów przystosowane do wysyłania pocztą "
"e-mail"
#: cps/ub.py:542
#: cps/ub.py:556
msgid "Guest"
msgstr "Gość"
#: cps/web.py:974
#: cps/web.py:953
msgid "Requesting update package"
msgstr "Żądanie o pakiet aktualizacji"
#: cps/web.py:975
#: cps/web.py:954
msgid "Downloading update package"
msgstr "Pobieranie pakietu aktualizacji"
#: cps/web.py:976
#: cps/web.py:955
msgid "Unzipping update package"
msgstr "Rozpakowywanie pakietu aktualizacji"
#: cps/web.py:977
#: cps/web.py:956
msgid "Files are replaced"
msgstr "Pliki zostały zastąpione"
#: cps/web.py:978
#: cps/web.py:957
msgid "Database connections are closed"
msgstr "Połączenia z bazą danych zostały zakończone"
#: cps/web.py:979
#: cps/web.py:958
msgid "Server is stopped"
msgstr "Serwer jest zatrzymany"
#: cps/web.py:980
#: cps/web.py:959
msgid "Update finished, please press okay and reload page"
msgstr "Aktualizacja zakończona, proszę nacisnąć OK i odświeżyć stronę"
#: cps/web.py:1054
#: cps/web.py:1033
msgid "Recently Added Books"
msgstr ""
#: cps/web.py:1063
#: cps/web.py:1042
msgid "Newest Books"
msgstr ""
#: cps/web.py:1072
#: cps/web.py:1051
msgid "Oldest Books"
msgstr ""
#: cps/web.py:1081
#: cps/web.py:1060
msgid "Books (A-Z)"
msgstr ""
#: cps/web.py:1090
#: cps/web.py:1069
msgid "Books (Z-A)"
msgstr ""
#: cps/web.py:1126
#: cps/web.py:1096
msgid "Hot Books (most downloaded)"
msgstr "Najpopularniejsze książki (najczęściej pobierane)"
#: cps/web.py:1136
#: cps/web.py:1106
msgid "Best rated books"
msgstr "Najlepiej oceniane książki"
#: cps/templates/index.xml:36 cps/web.py:1145
#: cps/templates/index.xml:35 cps/web.py:1115
msgid "Random Books"
msgstr "Losowe książki"
#: cps/web.py:1161
#: cps/web.py:1124
msgid "Author list"
msgstr "Lista autorów"
#: cps/web.py:1181 cps/web.py:1212 cps/web.py:1351 cps/web.py:1835
#: cps/web.py:1134 cps/web.py:1190 cps/web.py:1315 cps/web.py:1774
msgid "Error opening eBook. File does not exist or file is not accessible:"
msgstr "Błąd otwierania e-booka. Plik nie istnieje lub plik nie jest dostępny:"
#: cps/templates/index.xml:71 cps/web.py:1198
#: cps/templates/index.xml:70 cps/web.py:1176
msgid "Series list"
msgstr "Lista serii"
#: cps/web.py:1210
#: cps/web.py:1188
#, python-format
msgid "Series: %(serie)s"
msgstr "Seria: %(serie)s"
#: cps/web.py:1243
#: cps/web.py:1221
msgid "Available languages"
msgstr "Dostępne języki"
#: cps/web.py:1258
#: cps/web.py:1236
#, python-format
msgid "Language: %(name)s"
msgstr "Język: %(name)s"
#: cps/templates/index.xml:64 cps/web.py:1274
#: cps/templates/index.xml:63 cps/web.py:1245
msgid "Category list"
msgstr "Lista kategorii"
#: cps/web.py:1286
#: cps/web.py:1257
#, python-format
msgid "Category: %(name)s"
msgstr "Kategoria: %(name)s"
#: cps/web.py:1385
#: cps/web.py:1369
msgid "Excecution permissions missing"
msgstr ""
#: cps/web.py:1399
#: cps/web.py:1383
msgid "Statistics"
msgstr "Statystyki"
#: cps/web.py:1563
#: cps/web.py:1547
msgid "Server restarted, please reload page"
msgstr "Serwer uruchomiony ponownie, proszę odświeżyć stronę"
#: cps/web.py:1565
#: cps/web.py:1549
msgid "Performing shutdown of server, please close window"
msgstr "Wykonano wyłączenie serwera, proszę zamknąć okno"
#: cps/web.py:1581
#: cps/web.py:1565
msgid "Update done"
msgstr "Aktualizacja zakończona"
#: cps/web.py:1662 cps/web.py:1675
#: cps/web.py:1640 cps/web.py:1653
msgid "search"
msgstr "szukaj"
#: cps/templates/index.xml:43 cps/templates/index.xml:47
#: cps/templates/layout.html:127 cps/web.py:1751
#: cps/templates/index.xml:42 cps/templates/index.xml:46
#: cps/templates/layout.html:127 cps/web.py:1729
msgid "Read Books"
msgstr "Przeczytane książki"
#: cps/templates/index.xml:50 cps/templates/index.xml:54
#: cps/templates/layout.html:128 cps/web.py:1754
#: cps/templates/index.xml:49 cps/templates/index.xml:53
#: cps/templates/layout.html:128 cps/web.py:1732
msgid "Unread Books"
msgstr "Nieprzeczytane książki"
#: cps/web.py:1821 cps/web.py:1823 cps/web.py:1825 cps/web.py:1832
#: cps/web.py:1805 cps/web.py:1807 cps/web.py:1809 cps/web.py:1816
msgid "Read a Book"
msgstr "Czytaj książkę"
#: cps/web.py:1888 cps/web.py:2513
#: cps/web.py:1868 cps/web.py:2493
msgid "Please fill out all fields!"
msgstr "Proszę wypełnić wszystkie pola!"
#: cps/web.py:1889 cps/web.py:1905 cps/web.py:1910 cps/web.py:1912
#: cps/web.py:1869 cps/web.py:1885 cps/web.py:1890 cps/web.py:1892
msgid "register"
msgstr "rejestracja"
#: cps/web.py:1904
#: cps/web.py:1884
msgid "An unknown error occured. Please try again later."
msgstr "Wystąpił nieznany błąd. Spróbuj ponownie później."
#: cps/web.py:1909
#: cps/web.py:1889
msgid "This username or email address is already in use."
msgstr "Nazwa użytkownika lub adres e-mail jest już w użyciu."
#: cps/web.py:1928 cps/web.py:2024
#: cps/web.py:1908 cps/web.py:2004
#, python-format
msgid "you are now logged in as: '%(nickname)s'"
msgstr "Zalogowałeś się jako: '%(nickname)s'"
#: cps/web.py:1933
#: cps/web.py:1913
msgid "Wrong Username or Password"
msgstr "Błędna nazwa użytkownika lub hasło"
#: cps/web.py:1939 cps/web.py:1960
#: cps/web.py:1919 cps/web.py:1940
msgid "login"
msgstr "logowanie"
#: cps/web.py:1972 cps/web.py:2003
#: cps/web.py:1952 cps/web.py:1983
msgid "Token not found"
msgstr ""
#: cps/web.py:1980 cps/web.py:2011
#: cps/web.py:1960 cps/web.py:1991
msgid "Token has expired"
msgstr ""
#: cps/web.py:1988
#: cps/web.py:1968
msgid "Success! Please return to your device"
msgstr ""
#: cps/web.py:2038
#: cps/web.py:2018
msgid "Please configure the SMTP mail settings first..."
msgstr "Proszę najpierw skonfigurować ustawienia SMTP poczty e-mail..."
#: cps/web.py:2042
#: cps/web.py:2022
#, python-format
msgid "Book successfully send to %(kindlemail)s"
msgstr "Książka została pomyślnie wysłana do %(kindlemail)s"
#: cps/web.py:2046
#: cps/web.py:2026
#, python-format
msgid "There was an error sending this book: %(res)s"
msgstr "Wystąpił błąd podczas wysyłania tej książki: %(res)s"
#: cps/web.py:2048 cps/web.py:2598
#: cps/web.py:2028 cps/web.py:2578
msgid "Please configure your kindle email address first..."
msgstr "Proszę najpierw skonfigurować adres e-mail swojego kindla..."
#: cps/web.py:2092
#: cps/web.py:2072
#, python-format
msgid "Book has been added to shelf: %(sname)s"
msgstr "Książka została dodana do półki: %(sname)s"
#: cps/web.py:2127
#: cps/web.py:2107
#, python-format
msgid "Book has been removed from shelf: %(sname)s"
msgstr "Książka została usunięta z półki: %(sname)s"
#: cps/web.py:2146 cps/web.py:2170
#: cps/web.py:2126 cps/web.py:2150
#, python-format
msgid "A shelf with the name '%(title)s' already exists."
msgstr "Półka o nazwie '%(title)s' już istnieje."
#: cps/web.py:2151
#: cps/web.py:2131
#, python-format
msgid "Shelf %(title)s created"
msgstr "Półka %(title)s została utworzona"
#: cps/web.py:2153 cps/web.py:2181
#: cps/web.py:2133 cps/web.py:2161
msgid "There was an error"
msgstr "Wystąpił błąd"
#: cps/web.py:2154 cps/web.py:2156
#: cps/web.py:2134 cps/web.py:2136
msgid "create a shelf"
msgstr "utwórz półkę"
#: cps/web.py:2179
#: cps/web.py:2159
#, python-format
msgid "Shelf %(title)s changed"
msgstr "Półka %(title)s została zmieniona"
#: cps/web.py:2182 cps/web.py:2184
#: cps/web.py:2162 cps/web.py:2164
msgid "Edit a shelf"
msgstr "Edytuj półkę"
#: cps/web.py:2204
#: cps/web.py:2184
#, python-format
msgid "successfully deleted shelf %(name)s"
msgstr "pomyślnie usunięto półkę %(name)s"
#: cps/web.py:2226
#: cps/web.py:2206
#, python-format
msgid "Shelf: '%(name)s'"
msgstr "Półka: '%(name)s'"
#: cps/web.py:2229
#: cps/web.py:2209
msgid "Error opening shelf. Shelf does not exist or is not accessible"
msgstr ""
#: cps/web.py:2261
#: cps/web.py:2241
#, python-format
msgid "Change order of Shelf: '%(name)s'"
msgstr "Zmieniono kolejność półki: '%(name)s'"
#: cps/web.py:2326
#: cps/web.py:2306
msgid "Found an existing account for this email address."
msgstr "Znaleziono istniejące konto dla tego adresu e-mail."
#: cps/web.py:2328 cps/web.py:2332
#: cps/web.py:2308 cps/web.py:2312
#, python-format
msgid "%(name)s's profile"
msgstr "Profil użytkownika %(name)s"
#: cps/web.py:2329
#: cps/web.py:2309
msgid "Profile updated"
msgstr "Zaktualizowano profil"
#: cps/web.py:2343
#: cps/web.py:2323
msgid "Admin page"
msgstr "Portal administracyjny"
#: cps/web.py:2467
#: cps/web.py:2447
msgid "Calibre-web configuration updated"
msgstr "Konfiguracja Calibre-web została zaktualizowana"
#: cps/web.py:2474 cps/web.py:2480 cps/web.py:2494
#: cps/web.py:2454 cps/web.py:2460 cps/web.py:2474
msgid "Basic Configuration"
msgstr "Podstawowa konfiguracja"
#: cps/web.py:2478
#: cps/web.py:2458
msgid "DB location is not valid, please enter correct path"
msgstr "Lokalizacja bazy danych jest nieprawidłowa, wpisz poprawną ścieżkę"
#: cps/templates/admin.html:34 cps/web.py:2515 cps/web.py:2568
#: cps/templates/admin.html:34 cps/web.py:2495 cps/web.py:2548
msgid "Add new user"
msgstr "Dodaj nowego użytkownika"
#: cps/web.py:2560
#: cps/web.py:2540
#, python-format
msgid "User '%(user)s' created"
msgstr "Użytkownik '%(user)s' został utworzony"
#: cps/web.py:2564
#: cps/web.py:2544
msgid "Found an existing account for this email address or nickname."
msgstr "Znaleziono istniejące konto dla tego adresu e-mail lub nazwy użytkownika."
#: cps/web.py:2586
#: cps/web.py:2566
msgid "Mail settings updated"
msgstr "Zaktualizowano ustawienia poczty e-mail"
#: cps/web.py:2593
#: cps/web.py:2573
#, python-format
msgid "Test E-Mail successfully send to %(kindlemail)s"
msgstr "Testowy e-mail został pomyślnie wysłany do %(kindlemail)s"
#: cps/web.py:2596
#: cps/web.py:2576
#, python-format
msgid "There was an error sending the Test E-Mail: %(res)s"
msgstr "Wystąpił błąd podczas wysyłania testowej wiadomości e-mail: %(res)s"
#: cps/web.py:2600
#: cps/web.py:2580
msgid "E-Mail settings updated"
msgstr "Zaktualizowano ustawienia e-mail"
#: cps/web.py:2601
#: cps/web.py:2581
msgid "Edit mail settings"
msgstr "Edytuj ustawienia poczty e-mail"
#: cps/web.py:2630
#: cps/web.py:2610
#, python-format
msgid "User '%(nick)s' deleted"
msgstr "Użytkownik '%(nick)s' został usunięty"
#: cps/web.py:2728
#: cps/web.py:2708
#, python-format
msgid "User '%(nick)s' updated"
msgstr "Użytkownik '%(nick)s' został zaktualizowany"
#: cps/web.py:2731
#: cps/web.py:2711
msgid "An unknown error occured."
msgstr "Wystąpił nieznany błąd."
#: cps/web.py:2734
#: cps/web.py:2714
#, python-format
msgid "Edit User %(nick)s"
msgstr "Edytuj użytkownika %(nick)s"
#: cps/web.py:2756
#: cps/web.py:2730
msgid "Error opening eBook. File does not exist or file is not accessible"
msgstr ""
#: cps/web.py:2771 cps/web.py:2954 cps/web.py:3078
#: cps/web.py:2745 cps/web.py:2917 cps/web.py:3060
msgid "edit metadata"
msgstr "edytuj metadane"
#: cps/web.py:2783 cps/web.py:2787
#: cps/web.py:2757 cps/web.py:2761
msgid "unknown"
msgstr ""
#: cps/web.py:2972
#: cps/web.py:2954
#, python-format
msgid "File extension \"%s\" is not allowed to be uploaded to this server"
msgstr "Rozszerzenie pliku \"%s\" nie jest dozwolone do przesłania na ten serwer"
#: cps/web.py:2978
#: cps/web.py:2960
msgid "File to be uploaded must have an extension"
msgstr "Plik do przesłania musi mieć rozszerzenie"
#: cps/web.py:2997
#: cps/web.py:2979
#, python-format
msgid "Failed to create path %s (Permission denied)."
msgstr "Nie udało się utworzyć łącza %s (Odmowa dostępu)."
#: cps/web.py:3002
#: cps/web.py:2984
#, python-format
msgid "Failed to store file %s (Permission denied)."
msgstr "Nie można przechowywać pliku %s (Odmowa dostępu)."
#: cps/web.py:3007
#: cps/web.py:2989
#, python-format
msgid "Failed to delete file %s (Permission denied)."
msgstr "Nie udało się usunąć pliku %s (Odmowa dostępu)."
@ -607,6 +607,18 @@ msgstr "Na pewno chcesz zatrzymać Calibre Web?"
msgid "Updating, please do not reload page"
msgstr "Aktualizowanie, proszę nie odświeżać strony"
#: cps/templates/author.html:15
msgid "via"
msgstr ""
#: cps/templates/author.html:23
msgid "In Library"
msgstr ""
#: cps/templates/author.html:69
msgid "More by"
msgstr ""
#: cps/templates/book_edit.html:16
msgid "Delete Book"
msgstr ""
@ -615,12 +627,13 @@ msgstr ""
msgid "Book Title"
msgstr "Tytuł książki"
#: cps/templates/book_edit.html:26 cps/templates/book_edit.html:188
#: cps/templates/search_form.html:10
#: cps/templates/book_edit.html:26 cps/templates/book_edit.html:208
#: cps/templates/book_edit.html:226 cps/templates/search_form.html:10
msgid "Author"
msgstr "Autor"
#: cps/templates/book_edit.html:30 cps/templates/book_edit.html:190
#: cps/templates/book_edit.html:30 cps/templates/book_edit.html:213
#: cps/templates/book_edit.html:228
msgid "Description"
msgstr "Opis"
@ -703,35 +716,35 @@ msgstr " Szukaj słowa kluczowego "
msgid "Go!"
msgstr "Idź!"
#: cps/templates/book_edit.html:168
#: cps/templates/book_edit.html:171
msgid "Click the cover to load metadata to the form"
msgstr "Kliknij okładkę, aby załadować metadane do formularza"
#: cps/templates/book_edit.html:172 cps/templates/book_edit.html:185
#: cps/templates/book_edit.html:183 cps/templates/book_edit.html:223
msgid "Loading..."
msgstr "Ładowanie..."
#: cps/templates/book_edit.html:175 cps/templates/layout.html:199
#: cps/templates/book_edit.html:188 cps/templates/layout.html:199
msgid "Close"
msgstr "Zamknij"
#: cps/templates/book_edit.html:186
msgid "Search error!"
msgstr "Błąd wyszukiwania!"
#: cps/templates/book_edit.html:187
msgid "No Result! Please try anonther keyword."
msgstr "Brak wyników! Spróbuj innego słowa kluczowego."
#: cps/templates/book_edit.html:189 cps/templates/detail.html:125
#: cps/templates/search_form.html:14
#: cps/templates/book_edit.html:210 cps/templates/book_edit.html:227
#: cps/templates/detail.html:125 cps/templates/search_form.html:14
msgid "Publisher"
msgstr "Wydawca"
#: cps/templates/book_edit.html:191
#: cps/templates/book_edit.html:215 cps/templates/book_edit.html:229
msgid "Source"
msgstr "Źródło"
#: cps/templates/book_edit.html:224
msgid "Search error!"
msgstr "Błąd wyszukiwania!"
#: cps/templates/book_edit.html:225
msgid "No Result! Please try anonther keyword."
msgstr "Brak wyników! Spróbuj innego słowa kluczowego."
#: cps/templates/config_edit.html:7
msgid "Location of Calibre database"
msgstr "Lokalizacja bazy danych Calibre"
@ -822,7 +835,7 @@ msgstr ""
msgid "Default Settings for new users"
msgstr "Domyślne ustawienia dla nowych użytkowników"
#: cps/templates/config_edit.html:128 cps/templates/user_edit.html:90
#: cps/templates/config_edit.html:128 cps/templates/user_edit.html:86
msgid "Admin user"
msgstr "Użytkownik z uprawnieniami administratora"
@ -925,6 +938,11 @@ msgstr "Zapisz ustawienia i wyślij testową wiadomość e-mail"
msgid "Next"
msgstr "Następne"
#: cps/templates/feed.xml:29 cps/templates/index.xml:7
#: cps/templates/layout.html:40 cps/templates/layout.html:41
msgid "Search"
msgstr "Szukaj"
#: cps/templates/index.html:5
msgid "Discover (Random Books)"
msgstr "Odkrywaj (losowe książki)"
@ -933,52 +951,47 @@ msgstr "Odkrywaj (losowe książki)"
msgid "Start"
msgstr "Rozpocznij"
#: cps/templates/index.xml:7 cps/templates/layout.html:40
#: cps/templates/layout.html:41
msgid "Search"
msgstr "Szukaj"
#: cps/templates/index.xml:15 cps/templates/layout.html:121
#: cps/templates/index.xml:14 cps/templates/layout.html:121
msgid "Hot Books"
msgstr "Najpopularniejsze książki"
#: cps/templates/index.xml:19
#: cps/templates/index.xml:18
msgid "Popular publications from this catalog based on Downloads."
msgstr "Popularne publikacje z tego katalogu bazujące na pobranych."
#: cps/templates/index.xml:22 cps/templates/layout.html:124
#: cps/templates/index.xml:21 cps/templates/layout.html:124
msgid "Best rated Books"
msgstr "Najlepiej ocenione książki"
#: cps/templates/index.xml:26
#: cps/templates/index.xml:25
msgid "Popular publications from this catalog based on Rating."
msgstr "Popularne publikacje z tego katalogu bazujące na ocenach."
#: cps/templates/index.xml:29
#: cps/templates/index.xml:28
msgid "New Books"
msgstr "Nowe książki"
#: cps/templates/index.xml:33
#: cps/templates/index.xml:32
msgid "The latest Books"
msgstr "Ostatnie książki"
#: cps/templates/index.xml:40
#: cps/templates/index.xml:39
msgid "Show Random Books"
msgstr "Pokazuj losowe książki"
#: cps/templates/index.xml:57 cps/templates/layout.html:139
#: cps/templates/index.xml:56 cps/templates/layout.html:139
msgid "Authors"
msgstr "Autorzy"
#: cps/templates/index.xml:61
#: cps/templates/index.xml:60
msgid "Books ordered by Author"
msgstr "Książki sortowane według autorów"
#: cps/templates/index.xml:68
#: cps/templates/index.xml:67
msgid "Books ordered by category"
msgstr "Książki sortowane według kategorii"
#: cps/templates/index.xml:75
#: cps/templates/index.xml:74
msgid "Books ordered by series"
msgstr "Książki sortowane według serii"
@ -1089,7 +1102,11 @@ msgstr ""
msgid "Calibre Web ebook catalog"
msgstr "Calibre Web katalog ebooków"
#: cps/templates/read.html:125
#: cps/templates/read.html:69
msgid "Settings"
msgstr ""
#: cps/templates/read.html:72
#, fuzzy
msgid "Reflow text when sidebars are open."
msgstr "Tekst pływający, gdy paski boczne są otwarte."
@ -1139,7 +1156,7 @@ msgid "No Results for:"
msgstr "Brak wyników dla:"
#: cps/templates/search.html:7
msgid "Please try a diffrent Search"
msgid "Please try a different search"
msgstr "Proszę wypróbować podobne wyszukiwanie"
#: cps/templates/search.html:9
@ -1231,45 +1248,45 @@ msgid "Show all"
msgstr "Pokaż wszystko"
#: cps/templates/user_edit.html:46
msgid "Show mature content"
msgstr ""
#: cps/templates/user_edit.html:50
msgid "Show random books"
msgstr "Pokaż losowe książki"
#: cps/templates/user_edit.html:54
#: cps/templates/user_edit.html:50
msgid "Show hot books"
msgstr "Pokaż najpopularniejsze książki"
#: cps/templates/user_edit.html:58
#: cps/templates/user_edit.html:54
msgid "Show best rated books"
msgstr "Pokaż najlepiej ocenione książki"
#: cps/templates/user_edit.html:62
#: cps/templates/user_edit.html:58
msgid "Show language selection"
msgstr "Pokaż wybór języka"
#: cps/templates/user_edit.html:66
#: cps/templates/user_edit.html:62
msgid "Show series selection"
msgstr "Pokaż wybór serii"
#: cps/templates/user_edit.html:70
#: cps/templates/user_edit.html:66
msgid "Show category selection"
msgstr "Pokaż wybór kategorii"
#: cps/templates/user_edit.html:74
#: cps/templates/user_edit.html:70
msgid "Show author selection"
msgstr "Pokaż wybór autora"
#: cps/templates/user_edit.html:78
#: cps/templates/user_edit.html:74
msgid "Show read and unread"
msgstr "Pokaż przeczytane i nieprzeczytane"
#: cps/templates/user_edit.html:82
#: cps/templates/user_edit.html:78
msgid "Show random books in detail view"
msgstr "Pokaz losowe książki w widoku szczegółowym"
#: cps/templates/user_edit.html:90
msgid "Show mature content"
msgstr ""
#: cps/templates/user_edit.html:123
msgid "Delete this user"
msgstr "Usuń tego użytkownika"

@ -14,7 +14,7 @@ msgid ""
msgstr ""
"Project-Id-Version: Calibre-web\n"
"Report-Msgid-Bugs-To: https://github.com/janeczku/calibre-web\n"
"POT-Creation-Date: 2017-08-12 18:55+0200\n"
"POT-Creation-Date: 2017-09-16 07:48+0200\n"
"PO-Revision-Date: 2017-04-30 00:47+0300\n"
"Last-Translator: Pavel Korovin <p@tristero.se>\n"
"Language: ru\n"
@ -26,7 +26,7 @@ msgstr ""
"Content-Transfer-Encoding: 8bit\n"
"Generated-By: Babel 2.4.0\n"
#: cps/book_formats.py:118 cps/book_formats.py:122 cps/web.py:1374
#: cps/book_formats.py:118 cps/book_formats.py:122 cps/web.py:1358
msgid "not installed"
msgstr "Отсутствует"
@ -70,373 +70,373 @@ msgstr "Отправить на Kindle"
msgid "Could not find any formats suitable for sending by email"
msgstr "Невозоможно найти формат, подходящий для отправки по email"
#: cps/ub.py:542
#: cps/ub.py:556
msgid "Guest"
msgstr "Гость"
#: cps/web.py:974
#: cps/web.py:953
msgid "Requesting update package"
msgstr "Проверка обновлений"
#: cps/web.py:975
#: cps/web.py:954
msgid "Downloading update package"
msgstr "Загрузка обновлений"
#: cps/web.py:976
#: cps/web.py:955
msgid "Unzipping update package"
msgstr "Распаковка обновлений"
#: cps/web.py:977
#: cps/web.py:956
msgid "Files are replaced"
msgstr "Файлы заменены"
#: cps/web.py:978
#: cps/web.py:957
msgid "Database connections are closed"
msgstr "Соеднинения с базой данных закрыты"
#: cps/web.py:979
#: cps/web.py:958
msgid "Server is stopped"
msgstr "Сервер остановлен"
#: cps/web.py:980
#: cps/web.py:959
msgid "Update finished, please press okay and reload page"
msgstr "Обновления установлены, нажмите okay и перезагрузите страницу"
#: cps/web.py:1054
#: cps/web.py:1033
msgid "Recently Added Books"
msgstr ""
#: cps/web.py:1063
#: cps/web.py:1042
msgid "Newest Books"
msgstr ""
#: cps/web.py:1072
#: cps/web.py:1051
msgid "Oldest Books"
msgstr ""
#: cps/web.py:1081
#: cps/web.py:1060
msgid "Books (A-Z)"
msgstr ""
#: cps/web.py:1090
#: cps/web.py:1069
msgid "Books (Z-A)"
msgstr ""
#: cps/web.py:1126
#: cps/web.py:1096
msgid "Hot Books (most downloaded)"
msgstr "Популярные книги (часто загружаемые)"
#: cps/web.py:1136
#: cps/web.py:1106
msgid "Best rated books"
msgstr "Книги с наивысшим рейтингом"
#: cps/templates/index.xml:36 cps/web.py:1145
#: cps/templates/index.xml:35 cps/web.py:1115
msgid "Random Books"
msgstr "Случайный выбор"
#: cps/web.py:1161
#: cps/web.py:1124
msgid "Author list"
msgstr "Авторы"
#: cps/web.py:1181 cps/web.py:1212 cps/web.py:1351 cps/web.py:1835
#: cps/web.py:1134 cps/web.py:1190 cps/web.py:1315 cps/web.py:1774
msgid "Error opening eBook. File does not exist or file is not accessible:"
msgstr "Невозможно открыть книгу. Файл не существует или недоступен."
#: cps/templates/index.xml:71 cps/web.py:1198
#: cps/templates/index.xml:70 cps/web.py:1176
msgid "Series list"
msgstr "Серии"
#: cps/web.py:1210
#: cps/web.py:1188
#, python-format
msgid "Series: %(serie)s"
msgstr "Серии: %(serie)s"
#: cps/web.py:1243
#: cps/web.py:1221
msgid "Available languages"
msgstr "Языки"
#: cps/web.py:1258
#: cps/web.py:1236
#, python-format
msgid "Language: %(name)s"
msgstr "Язык: %(name)s"
#: cps/templates/index.xml:64 cps/web.py:1274
#: cps/templates/index.xml:63 cps/web.py:1245
msgid "Category list"
msgstr "Категории"
#: cps/web.py:1286
#: cps/web.py:1257
#, python-format
msgid "Category: %(name)s"
msgstr "Категория: %(name)s"
#: cps/web.py:1385
#: cps/web.py:1369
msgid "Excecution permissions missing"
msgstr ""
#: cps/web.py:1399
#: cps/web.py:1383
msgid "Statistics"
msgstr "Статистика"
#: cps/web.py:1563
#: cps/web.py:1547
msgid "Server restarted, please reload page"
msgstr "Сервер перезагружен, пожалуйста, перезагрузите страницу"
#: cps/web.py:1565
#: cps/web.py:1549
msgid "Performing shutdown of server, please close window"
msgstr "Производится остановка сервера, пожалуйста, закройте окно"
#: cps/web.py:1581
#: cps/web.py:1565
msgid "Update done"
msgstr "Обновление закончено"
#: cps/web.py:1662 cps/web.py:1675
#: cps/web.py:1640 cps/web.py:1653
msgid "search"
msgstr "поиск"
#: cps/templates/index.xml:43 cps/templates/index.xml:47
#: cps/templates/layout.html:127 cps/web.py:1751
#: cps/templates/index.xml:42 cps/templates/index.xml:46
#: cps/templates/layout.html:127 cps/web.py:1729
msgid "Read Books"
msgstr "Прочитанные"
#: cps/templates/index.xml:50 cps/templates/index.xml:54
#: cps/templates/layout.html:128 cps/web.py:1754
#: cps/templates/index.xml:49 cps/templates/index.xml:53
#: cps/templates/layout.html:128 cps/web.py:1732
msgid "Unread Books"
msgstr "Непрочитанные"
#: cps/web.py:1821 cps/web.py:1823 cps/web.py:1825 cps/web.py:1832
#: cps/web.py:1805 cps/web.py:1807 cps/web.py:1809 cps/web.py:1816
msgid "Read a Book"
msgstr "Читать книгу"
#: cps/web.py:1888 cps/web.py:2513
#: cps/web.py:1868 cps/web.py:2493
msgid "Please fill out all fields!"
msgstr "Пожалуйста, заполните все поля!"
#: cps/web.py:1889 cps/web.py:1905 cps/web.py:1910 cps/web.py:1912
#: cps/web.py:1869 cps/web.py:1885 cps/web.py:1890 cps/web.py:1892
msgid "register"
msgstr "зарегистрироваться"
#: cps/web.py:1904
#: cps/web.py:1884
msgid "An unknown error occured. Please try again later."
msgstr "Неизвестная ошибка. Пожалуйста, попробуйте позже."
#: cps/web.py:1909
#: cps/web.py:1889
msgid "This username or email address is already in use."
msgstr "Имя пользователя или адрес эл. почты уже используется"
#: cps/web.py:1928 cps/web.py:2024
#: cps/web.py:1908 cps/web.py:2004
#, python-format
msgid "you are now logged in as: '%(nickname)s'"
msgstr "Вы вошли как пользователь '%(nickname)s'"
#: cps/web.py:1933
#: cps/web.py:1913
msgid "Wrong Username or Password"
msgstr "Ошибка в имени пользователя или пароле"
#: cps/web.py:1939 cps/web.py:1960
#: cps/web.py:1919 cps/web.py:1940
msgid "login"
msgstr "войти"
#: cps/web.py:1972 cps/web.py:2003
#: cps/web.py:1952 cps/web.py:1983
msgid "Token not found"
msgstr ""
#: cps/web.py:1980 cps/web.py:2011
#: cps/web.py:1960 cps/web.py:1991
msgid "Token has expired"
msgstr ""
#: cps/web.py:1988
#: cps/web.py:1968
msgid "Success! Please return to your device"
msgstr ""
#: cps/web.py:2038
#: cps/web.py:2018
msgid "Please configure the SMTP mail settings first..."
msgstr "Пожалуйста, сначала сконфигурируйте параметры SMTP"
#: cps/web.py:2042
#: cps/web.py:2022
#, python-format
msgid "Book successfully send to %(kindlemail)s"
msgstr "Книга успешно отправлена на %(kindlemail)s"
#: cps/web.py:2046
#: cps/web.py:2026
#, python-format
msgid "There was an error sending this book: %(res)s"
msgstr "Ошибка при отправке книги: %(res)s"
#: cps/web.py:2048 cps/web.py:2598
#: cps/web.py:2028 cps/web.py:2578
msgid "Please configure your kindle email address first..."
msgstr "Пожалуйста, сначала укажите ваш kindle email..."
#: cps/web.py:2092
#: cps/web.py:2072
#, python-format
msgid "Book has been added to shelf: %(sname)s"
msgstr "Книга добавлена на книжную полку: %(sname)s"
#: cps/web.py:2127
#: cps/web.py:2107
#, python-format
msgid "Book has been removed from shelf: %(sname)s"
msgstr "Книга удалена с книжной полки: %(sname)s"
#: cps/web.py:2146 cps/web.py:2170
#: cps/web.py:2126 cps/web.py:2150
#, python-format
msgid "A shelf with the name '%(title)s' already exists."
msgstr "Книжкная полка с названием '%(title)s' уже существует."
#: cps/web.py:2151
#: cps/web.py:2131
#, python-format
msgid "Shelf %(title)s created"
msgstr "Создана книжная полка %(title)s"
#: cps/web.py:2153 cps/web.py:2181
#: cps/web.py:2133 cps/web.py:2161
msgid "There was an error"
msgstr "Произошла ошибка"
#: cps/web.py:2154 cps/web.py:2156
#: cps/web.py:2134 cps/web.py:2136
msgid "create a shelf"
msgstr "создать книжную полку"
#: cps/web.py:2179
#: cps/web.py:2159
#, python-format
msgid "Shelf %(title)s changed"
msgstr "Книжная полка %(title)s изменена"
#: cps/web.py:2182 cps/web.py:2184
#: cps/web.py:2162 cps/web.py:2164
msgid "Edit a shelf"
msgstr "Изменить книжную полку"
#: cps/web.py:2204
#: cps/web.py:2184
#, python-format
msgid "successfully deleted shelf %(name)s"
msgstr "Книжная полка %(name)s удалена"
#: cps/web.py:2226
#: cps/web.py:2206
#, python-format
msgid "Shelf: '%(name)s'"
msgstr "Книжная полка: '%(name)s'"
#: cps/web.py:2229
#: cps/web.py:2209
msgid "Error opening shelf. Shelf does not exist or is not accessible"
msgstr ""
#: cps/web.py:2261
#: cps/web.py:2241
#, python-format
msgid "Change order of Shelf: '%(name)s'"
msgstr "Изменить расположение книжной полки '%(name)s'"
#: cps/web.py:2326
#: cps/web.py:2306
msgid "Found an existing account for this email address."
msgstr "Найдена учётная запись для для данного адреса email."
#: cps/web.py:2328 cps/web.py:2332
#: cps/web.py:2308 cps/web.py:2312
#, python-format
msgid "%(name)s's profile"
msgstr "Профиль %(name)s"
#: cps/web.py:2329
#: cps/web.py:2309
msgid "Profile updated"
msgstr "Профиль обновлён"
#: cps/web.py:2343
#: cps/web.py:2323
msgid "Admin page"
msgstr "Администрирование"
#: cps/web.py:2467
#: cps/web.py:2447
msgid "Calibre-web configuration updated"
msgstr "Конфигурация Calibre-web обновлена"
#: cps/web.py:2474 cps/web.py:2480 cps/web.py:2494
#: cps/web.py:2454 cps/web.py:2460 cps/web.py:2474
msgid "Basic Configuration"
msgstr "Настройки сервера"
#: cps/web.py:2478
#: cps/web.py:2458
msgid "DB location is not valid, please enter correct path"
msgstr "Неверный путь к фалу БД, пожалуйста, укажите правильное расположение БД"
#: cps/templates/admin.html:34 cps/web.py:2515 cps/web.py:2568
#: cps/templates/admin.html:34 cps/web.py:2495 cps/web.py:2548
msgid "Add new user"
msgstr "Добавить пользователя"
#: cps/web.py:2560
#: cps/web.py:2540
#, python-format
msgid "User '%(user)s' created"
msgstr "Пользователь '%(user)s' добавлен"
#: cps/web.py:2564
#: cps/web.py:2544
msgid "Found an existing account for this email address or nickname."
msgstr "Для указанного адреса или имени найдена существующая учётная запись."
#: cps/web.py:2586
#: cps/web.py:2566
msgid "Mail settings updated"
msgstr "Настройки почты изменены"
#: cps/web.py:2593
#: cps/web.py:2573
#, python-format
msgid "Test E-Mail successfully send to %(kindlemail)s"
msgstr "Тестовое сообщение успешно отправлено на адрес %(kindlemail)s"
#: cps/web.py:2596
#: cps/web.py:2576
#, python-format
msgid "There was an error sending the Test E-Mail: %(res)s"
msgstr "Ошибка отправки тестового сообщения: %(res)s"
#: cps/web.py:2600
#: cps/web.py:2580
msgid "E-Mail settings updated"
msgstr "Обновлены настройки e-mail"
#: cps/web.py:2601
#: cps/web.py:2581
msgid "Edit mail settings"
msgstr "Изменить почтовые настройки"
#: cps/web.py:2630
#: cps/web.py:2610
#, python-format
msgid "User '%(nick)s' deleted"
msgstr "Пользователь '%(nick)s' удалён"
#: cps/web.py:2728
#: cps/web.py:2708
#, python-format
msgid "User '%(nick)s' updated"
msgstr "Пользователь '%(nick)s' обновлён"
#: cps/web.py:2731
#: cps/web.py:2711
msgid "An unknown error occured."
msgstr "Произошла неизвестная ошибка."
#: cps/web.py:2734
#: cps/web.py:2714
#, python-format
msgid "Edit User %(nick)s"
msgstr "Изменить пользователя %(nick)s"
#: cps/web.py:2756
#: cps/web.py:2730
msgid "Error opening eBook. File does not exist or file is not accessible"
msgstr ""
#: cps/web.py:2771 cps/web.py:2954 cps/web.py:3078
#: cps/web.py:2745 cps/web.py:2917 cps/web.py:3060
msgid "edit metadata"
msgstr "изменить метаданные"
#: cps/web.py:2783 cps/web.py:2787
#: cps/web.py:2757 cps/web.py:2761
msgid "unknown"
msgstr "неизвестно"
#: cps/web.py:2972
#: cps/web.py:2954
#, python-format
msgid "File extension \"%s\" is not allowed to be uploaded to this server"
msgstr "Запрещена загрузка файлов с расширением \"%s\""
#: cps/web.py:2978
#: cps/web.py:2960
msgid "File to be uploaded must have an extension"
msgstr "Загружаемый файл должен иметь расширение"
#: cps/web.py:2997
#: cps/web.py:2979
#, python-format
msgid "Failed to create path %s (Permission denied)."
msgstr "Ошибка при создании пути %s (доступ запрещён)"
#: cps/web.py:3002
#: cps/web.py:2984
#, python-format
msgid "Failed to store file %s (Permission denied)."
msgstr "Ошибка записи файоа %s (доступ запрещён)"
#: cps/web.py:3007
#: cps/web.py:2989
#, python-format
msgid "Failed to delete file %s (Permission denied)."
msgstr "Ошибка удаления файла %s (доступ запрещён)"
@ -607,6 +607,18 @@ msgstr "Вы действительно хотите остановить Calibr
msgid "Updating, please do not reload page"
msgstr "Установка обновлений, пожалуйста, не обновляйте страницу."
#: cps/templates/author.html:15
msgid "via"
msgstr ""
#: cps/templates/author.html:23
msgid "In Library"
msgstr ""
#: cps/templates/author.html:69
msgid "More by"
msgstr ""
#: cps/templates/book_edit.html:16
msgid "Delete Book"
msgstr "Удалить книгу"
@ -615,12 +627,13 @@ msgstr "Удалить книгу"
msgid "Book Title"
msgstr "Название"
#: cps/templates/book_edit.html:26 cps/templates/book_edit.html:188
#: cps/templates/search_form.html:10
#: cps/templates/book_edit.html:26 cps/templates/book_edit.html:208
#: cps/templates/book_edit.html:226 cps/templates/search_form.html:10
msgid "Author"
msgstr "Автор"
#: cps/templates/book_edit.html:30 cps/templates/book_edit.html:190
#: cps/templates/book_edit.html:30 cps/templates/book_edit.html:213
#: cps/templates/book_edit.html:228
msgid "Description"
msgstr "Описание"
@ -703,35 +716,35 @@ msgstr " Поиск по ключевому слову"
msgid "Go!"
msgstr "Искать"
#: cps/templates/book_edit.html:168
#: cps/templates/book_edit.html:171
msgid "Click the cover to load metadata to the form"
msgstr "Нажмите на обложку, чтобы получить метаданные"
#: cps/templates/book_edit.html:172 cps/templates/book_edit.html:185
#: cps/templates/book_edit.html:183 cps/templates/book_edit.html:223
msgid "Loading..."
msgstr "Загрузка..."
#: cps/templates/book_edit.html:175 cps/templates/layout.html:199
#: cps/templates/book_edit.html:188 cps/templates/layout.html:199
msgid "Close"
msgstr "Закрыть"
#: cps/templates/book_edit.html:186
msgid "Search error!"
msgstr "Ошибка поиска!"
#: cps/templates/book_edit.html:187
msgid "No Result! Please try anonther keyword."
msgstr "Нет результатов. Пожалуйста, попробуйте другое ключевое слово"
#: cps/templates/book_edit.html:189 cps/templates/detail.html:125
#: cps/templates/search_form.html:14
#: cps/templates/book_edit.html:210 cps/templates/book_edit.html:227
#: cps/templates/detail.html:125 cps/templates/search_form.html:14
msgid "Publisher"
msgstr "Издатель"
#: cps/templates/book_edit.html:191
#: cps/templates/book_edit.html:215 cps/templates/book_edit.html:229
msgid "Source"
msgstr "Источник"
#: cps/templates/book_edit.html:224
msgid "Search error!"
msgstr "Ошибка поиска!"
#: cps/templates/book_edit.html:225
msgid "No Result! Please try anonther keyword."
msgstr "Нет результатов. Пожалуйста, попробуйте другое ключевое слово"
#: cps/templates/config_edit.html:7
msgid "Location of Calibre database"
msgstr "Расположение БД Calibre"
@ -821,7 +834,7 @@ msgstr ""
msgid "Default Settings for new users"
msgstr "Настройки по умолчанию для новых пользователей"
#: cps/templates/config_edit.html:128 cps/templates/user_edit.html:90
#: cps/templates/config_edit.html:128 cps/templates/user_edit.html:86
msgid "Admin user"
msgstr "Управление сервером"
@ -922,6 +935,11 @@ msgstr "Сохранить настройки и отправить тестов
msgid "Next"
msgstr "Дальше"
#: cps/templates/feed.xml:29 cps/templates/index.xml:7
#: cps/templates/layout.html:40 cps/templates/layout.html:41
msgid "Search"
msgstr "Поиск"
#: cps/templates/index.html:5
msgid "Discover (Random Books)"
msgstr "Обзор (случайные книги)"
@ -930,52 +948,47 @@ msgstr "Обзор (случайные книги)"
msgid "Start"
msgstr "Старт"
#: cps/templates/index.xml:7 cps/templates/layout.html:40
#: cps/templates/layout.html:41
msgid "Search"
msgstr "Поиск"
#: cps/templates/index.xml:15 cps/templates/layout.html:121
#: cps/templates/index.xml:14 cps/templates/layout.html:121
msgid "Hot Books"
msgstr "Популярные книги"
#: cps/templates/index.xml:19
#: cps/templates/index.xml:18
msgid "Popular publications from this catalog based on Downloads."
msgstr "Популярные книги в этом каталоге, на основе количества скачиваний"
#: cps/templates/index.xml:22 cps/templates/layout.html:124
#: cps/templates/index.xml:21 cps/templates/layout.html:124
msgid "Best rated Books"
msgstr "Книги с наилучшим рейтингом"
#: cps/templates/index.xml:26
#: cps/templates/index.xml:25
msgid "Popular publications from this catalog based on Rating."
msgstr "Популярные книги из этого каталога на основании рейтинга"
#: cps/templates/index.xml:29
#: cps/templates/index.xml:28
msgid "New Books"
msgstr "Новые"
#: cps/templates/index.xml:33
#: cps/templates/index.xml:32
msgid "The latest Books"
msgstr "Последние поступления"
#: cps/templates/index.xml:40
#: cps/templates/index.xml:39
msgid "Show Random Books"
msgstr "Показывать случайные книги"
#: cps/templates/index.xml:57 cps/templates/layout.html:139
#: cps/templates/index.xml:56 cps/templates/layout.html:139
msgid "Authors"
msgstr "Авторы"
#: cps/templates/index.xml:61
#: cps/templates/index.xml:60
msgid "Books ordered by Author"
msgstr "Книги, отсортированные по автору"
#: cps/templates/index.xml:68
#: cps/templates/index.xml:67
msgid "Books ordered by category"
msgstr "Книги, отсортированные по категории"
#: cps/templates/index.xml:75
#: cps/templates/index.xml:74
msgid "Books ordered by series"
msgstr "Книги, отсортированные по серии"
@ -1086,7 +1099,11 @@ msgstr ""
msgid "Calibre Web ebook catalog"
msgstr "Каталог электронных книг Calibre Web"
#: cps/templates/read.html:125
#: cps/templates/read.html:69
msgid "Settings"
msgstr ""
#: cps/templates/read.html:72
msgid "Reflow text when sidebars are open."
msgstr "Обновить размещение текста при открытии боковой панели"
@ -1135,8 +1152,8 @@ msgid "No Results for:"
msgstr "Ничего не найдено по запросу:"
#: cps/templates/search.html:7
msgid "Please try a diffrent Search"
msgstr "Попробуйте изменить критерии поиска"
msgid "Please try a different search"
msgstr "Попробуйте изменить критерии поиск"
#: cps/templates/search.html:9
msgid "Results for:"
@ -1227,45 +1244,45 @@ msgid "Show all"
msgstr "Всех"
#: cps/templates/user_edit.html:46
msgid "Show mature content"
msgstr ""
#: cps/templates/user_edit.html:50
msgid "Show random books"
msgstr "Показывать случайные книги"
#: cps/templates/user_edit.html:54
#: cps/templates/user_edit.html:50
msgid "Show hot books"
msgstr "Показывать популярные книги"
#: cps/templates/user_edit.html:58
#: cps/templates/user_edit.html:54
msgid "Show best rated books"
msgstr "Показывать книги с наивысшим рейтингом"
#: cps/templates/user_edit.html:62
#: cps/templates/user_edit.html:58
msgid "Show language selection"
msgstr "Показывать выбор языка"
#: cps/templates/user_edit.html:66
#: cps/templates/user_edit.html:62
msgid "Show series selection"
msgstr "Показывать выбор серии"
#: cps/templates/user_edit.html:70
#: cps/templates/user_edit.html:66
msgid "Show category selection"
msgstr "Показывать выбор категории"
#: cps/templates/user_edit.html:74
#: cps/templates/user_edit.html:70
msgid "Show author selection"
msgstr "Показывать выбор автора"
#: cps/templates/user_edit.html:78
#: cps/templates/user_edit.html:74
msgid "Show read and unread"
msgstr "Показывать прочитанные и непрочитанные"
#: cps/templates/user_edit.html:82
#: cps/templates/user_edit.html:78
msgid "Show random books in detail view"
msgstr "Показывать случайные книги при просмотре деталей"
#: cps/templates/user_edit.html:90
msgid "Show mature content"
msgstr ""
#: cps/templates/user_edit.html:123
msgid "Delete this user"
msgstr "Удалить этого пользователя"

@ -15,7 +15,7 @@ msgid ""
msgstr ""
"Project-Id-Version: Calibre-web\n"
"Report-Msgid-Bugs-To: https://github.com/janeczku/calibre-web\n"
"POT-Creation-Date: 2017-08-12 18:55+0200\n"
"POT-Creation-Date: 2017-09-16 07:48+0200\n"
"PO-Revision-Date: 2017-01-06 17:00+0000\n"
"Last-Translator: dalin <dalin.lin@gmail.com>\n"
"Language: zh_Hans_CN\n"
@ -26,7 +26,7 @@ msgstr ""
"Content-Transfer-Encoding: 8bit\n"
"Generated-By: Babel 2.4.0\n"
#: cps/book_formats.py:118 cps/book_formats.py:122 cps/web.py:1374
#: cps/book_formats.py:118 cps/book_formats.py:122 cps/web.py:1358
msgid "not installed"
msgstr "未安装"
@ -70,373 +70,373 @@ msgstr "发送到Kindle"
msgid "Could not find any formats suitable for sending by email"
msgstr "无法找到适合邮件发送的格式"
#: cps/ub.py:542
#: cps/ub.py:556
msgid "Guest"
msgstr "游客"
#: cps/web.py:974
#: cps/web.py:953
msgid "Requesting update package"
msgstr "正在请求更新包"
#: cps/web.py:975
#: cps/web.py:954
msgid "Downloading update package"
msgstr "正在下载更新包"
#: cps/web.py:976
#: cps/web.py:955
msgid "Unzipping update package"
msgstr "正在解压更新包"
#: cps/web.py:977
#: cps/web.py:956
msgid "Files are replaced"
msgstr "文件已替换"
#: cps/web.py:978
#: cps/web.py:957
msgid "Database connections are closed"
msgstr "数据库连接已关闭"
#: cps/web.py:979
#: cps/web.py:958
msgid "Server is stopped"
msgstr "服务器已停止"
#: cps/web.py:980
#: cps/web.py:959
msgid "Update finished, please press okay and reload page"
msgstr "更新完成,请按确定并刷新页面"
#: cps/web.py:1054
#: cps/web.py:1033
msgid "Recently Added Books"
msgstr "最近添加的书籍"
#: cps/web.py:1063
#: cps/web.py:1042
msgid "Newest Books"
msgstr "最新书籍"
#: cps/web.py:1072
#: cps/web.py:1051
msgid "Oldest Books"
msgstr "最旧书籍"
#: cps/web.py:1081
#: cps/web.py:1060
msgid "Books (A-Z)"
msgstr "书籍 (A-Z)"
#: cps/web.py:1090
#: cps/web.py:1069
msgid "Books (Z-A)"
msgstr "书籍 (Z-A)"
#: cps/web.py:1126
#: cps/web.py:1096
msgid "Hot Books (most downloaded)"
msgstr "热门书籍(最多下载)"
#: cps/web.py:1136
#: cps/web.py:1106
msgid "Best rated books"
msgstr "最高评分书籍"
#: cps/templates/index.xml:36 cps/web.py:1145
#: cps/templates/index.xml:35 cps/web.py:1115
msgid "Random Books"
msgstr "随机书籍"
#: cps/web.py:1161
#: cps/web.py:1124
msgid "Author list"
msgstr "作者列表"
#: cps/web.py:1181 cps/web.py:1212 cps/web.py:1351 cps/web.py:1835
#: cps/web.py:1134 cps/web.py:1190 cps/web.py:1315 cps/web.py:1774
msgid "Error opening eBook. File does not exist or file is not accessible:"
msgstr "无法打开电子书。 文件不存在或者文件不可访问:"
#: cps/templates/index.xml:71 cps/web.py:1198
#: cps/templates/index.xml:70 cps/web.py:1176
msgid "Series list"
msgstr "丛书列表"
#: cps/web.py:1210
#: cps/web.py:1188
#, python-format
msgid "Series: %(serie)s"
msgstr "丛书: %(serie)s"
#: cps/web.py:1243
#: cps/web.py:1221
msgid "Available languages"
msgstr "可用语言"
#: cps/web.py:1258
#: cps/web.py:1236
#, python-format
msgid "Language: %(name)s"
msgstr "语言: %(name)s"
#: cps/templates/index.xml:64 cps/web.py:1274
#: cps/templates/index.xml:63 cps/web.py:1245
msgid "Category list"
msgstr "分类列表"
#: cps/web.py:1286
#: cps/web.py:1257
#, python-format
msgid "Category: %(name)s"
msgstr "分类: %(name)s"
#: cps/web.py:1385
#: cps/web.py:1369
msgid "Excecution permissions missing"
msgstr "可执行权限缺失"
#: cps/web.py:1399
#: cps/web.py:1383
msgid "Statistics"
msgstr "统计"
#: cps/web.py:1563
#: cps/web.py:1547
msgid "Server restarted, please reload page"
msgstr "服务器已重启,请刷新页面"
#: cps/web.py:1565
#: cps/web.py:1549
msgid "Performing shutdown of server, please close window"
msgstr "正在关闭服务器,请关闭窗口"
#: cps/web.py:1581
#: cps/web.py:1565
msgid "Update done"
msgstr "更新完成"
#: cps/web.py:1662 cps/web.py:1675
#: cps/web.py:1640 cps/web.py:1653
msgid "search"
msgstr "搜索"
#: cps/templates/index.xml:43 cps/templates/index.xml:47
#: cps/templates/layout.html:127 cps/web.py:1751
#: cps/templates/index.xml:42 cps/templates/index.xml:46
#: cps/templates/layout.html:127 cps/web.py:1729
msgid "Read Books"
msgstr "已读书籍"
#: cps/templates/index.xml:50 cps/templates/index.xml:54
#: cps/templates/layout.html:128 cps/web.py:1754
#: cps/templates/index.xml:49 cps/templates/index.xml:53
#: cps/templates/layout.html:128 cps/web.py:1732
msgid "Unread Books"
msgstr "未读书籍"
#: cps/web.py:1821 cps/web.py:1823 cps/web.py:1825 cps/web.py:1832
#: cps/web.py:1805 cps/web.py:1807 cps/web.py:1809 cps/web.py:1816
msgid "Read a Book"
msgstr "阅读一本书"
#: cps/web.py:1888 cps/web.py:2513
#: cps/web.py:1868 cps/web.py:2493
msgid "Please fill out all fields!"
msgstr "请填写所有字段"
#: cps/web.py:1889 cps/web.py:1905 cps/web.py:1910 cps/web.py:1912
#: cps/web.py:1869 cps/web.py:1885 cps/web.py:1890 cps/web.py:1892
msgid "register"
msgstr "注册"
#: cps/web.py:1904
#: cps/web.py:1884
msgid "An unknown error occured. Please try again later."
msgstr "发生一个未知错误。请稍后再试。"
#: cps/web.py:1909
#: cps/web.py:1889
msgid "This username or email address is already in use."
msgstr "此用户名或邮箱已被使用。"
#: cps/web.py:1928 cps/web.py:2024
#: cps/web.py:1908 cps/web.py:2004
#, python-format
msgid "you are now logged in as: '%(nickname)s'"
msgstr "您现在已以'%(nickname)s'身份登录"
#: cps/web.py:1933
#: cps/web.py:1913
msgid "Wrong Username or Password"
msgstr "用户名或密码错误"
#: cps/web.py:1939 cps/web.py:1960
#: cps/web.py:1919 cps/web.py:1940
msgid "login"
msgstr "登录"
#: cps/web.py:1972 cps/web.py:2003
#: cps/web.py:1952 cps/web.py:1983
msgid "Token not found"
msgstr "找不到Token"
#: cps/web.py:1980 cps/web.py:2011
#: cps/web.py:1960 cps/web.py:1991
msgid "Token has expired"
msgstr "Token已过期"
#: cps/web.py:1988
#: cps/web.py:1968
msgid "Success! Please return to your device"
msgstr "成功!请返回您的设备"
#: cps/web.py:2038
#: cps/web.py:2018
msgid "Please configure the SMTP mail settings first..."
msgstr "请先配置SMTP邮箱..."
#: cps/web.py:2042
#: cps/web.py:2022
#, python-format
msgid "Book successfully send to %(kindlemail)s"
msgstr "此书已被成功发给 %(kindlemail)s"
#: cps/web.py:2046
#: cps/web.py:2026
#, python-format
msgid "There was an error sending this book: %(res)s"
msgstr "发送这本书的时候出现错误: %(res)s"
#: cps/web.py:2048 cps/web.py:2598
#: cps/web.py:2028 cps/web.py:2578
msgid "Please configure your kindle email address first..."
msgstr "请先配置您的kindle电子邮箱地址..."
#: cps/web.py:2092
#: cps/web.py:2072
#, python-format
msgid "Book has been added to shelf: %(sname)s"
msgstr "此书已被添加到书架: %(sname)s"
#: cps/web.py:2127
#: cps/web.py:2107
#, python-format
msgid "Book has been removed from shelf: %(sname)s"
msgstr "此书已从书架 %(sname)s 中删除"
#: cps/web.py:2146 cps/web.py:2170
#: cps/web.py:2126 cps/web.py:2150
#, python-format
msgid "A shelf with the name '%(title)s' already exists."
msgstr "已存在书架 '%(title)s'。"
#: cps/web.py:2151
#: cps/web.py:2131
#, python-format
msgid "Shelf %(title)s created"
msgstr "书架 %(title)s 已被创建"
#: cps/web.py:2153 cps/web.py:2181
#: cps/web.py:2133 cps/web.py:2161
msgid "There was an error"
msgstr "发生错误"
#: cps/web.py:2154 cps/web.py:2156
#: cps/web.py:2134 cps/web.py:2136
msgid "create a shelf"
msgstr "创建书架"
#: cps/web.py:2179
#: cps/web.py:2159
#, python-format
msgid "Shelf %(title)s changed"
msgstr "书架 %(title)s 已被修改"
#: cps/web.py:2182 cps/web.py:2184
#: cps/web.py:2162 cps/web.py:2164
msgid "Edit a shelf"
msgstr "编辑书架"
#: cps/web.py:2204
#: cps/web.py:2184
#, python-format
msgid "successfully deleted shelf %(name)s"
msgstr "成功删除书架 %(name)s"
#: cps/web.py:2226
#: cps/web.py:2206
#, python-format
msgid "Shelf: '%(name)s'"
msgstr "书架: '%(name)s'"
#: cps/web.py:2229
#: cps/web.py:2209
msgid "Error opening shelf. Shelf does not exist or is not accessible"
msgstr "打开书架出错。书架不存在或不可访问"
#: cps/web.py:2261
#: cps/web.py:2241
#, python-format
msgid "Change order of Shelf: '%(name)s'"
msgstr "修改书架 '%(name)s' 顺序"
#: cps/web.py:2326
#: cps/web.py:2306
msgid "Found an existing account for this email address."
msgstr "找到已使用此邮箱的账号。"
#: cps/web.py:2328 cps/web.py:2332
#: cps/web.py:2308 cps/web.py:2312
#, python-format
msgid "%(name)s's profile"
msgstr "%(name)s 的资料"
#: cps/web.py:2329
#: cps/web.py:2309
msgid "Profile updated"
msgstr "资料已更新"
#: cps/web.py:2343
#: cps/web.py:2323
msgid "Admin page"
msgstr "管理页"
#: cps/web.py:2467
#: cps/web.py:2447
msgid "Calibre-web configuration updated"
msgstr "Calibre-web配置已更新"
#: cps/web.py:2474 cps/web.py:2480 cps/web.py:2494
#: cps/web.py:2454 cps/web.py:2460 cps/web.py:2474
msgid "Basic Configuration"
msgstr "基本配置"
#: cps/web.py:2478
#: cps/web.py:2458
msgid "DB location is not valid, please enter correct path"
msgstr "DB位置无效请输入正确路径"
#: cps/templates/admin.html:34 cps/web.py:2515 cps/web.py:2568
#: cps/templates/admin.html:34 cps/web.py:2495 cps/web.py:2548
msgid "Add new user"
msgstr "添加新用户"
#: cps/web.py:2560
#: cps/web.py:2540
#, python-format
msgid "User '%(user)s' created"
msgstr "用户 '%(user)s' 已被创建"
#: cps/web.py:2564
#: cps/web.py:2544
msgid "Found an existing account for this email address or nickname."
msgstr "已存在使用此邮箱或昵称的账号。"
#: cps/web.py:2586
#: cps/web.py:2566
msgid "Mail settings updated"
msgstr "邮箱设置已更新"
#: cps/web.py:2593
#: cps/web.py:2573
#, python-format
msgid "Test E-Mail successfully send to %(kindlemail)s"
msgstr "测试邮件已成功发送到 %(kindlemail)s"
#: cps/web.py:2596
#: cps/web.py:2576
#, python-format
msgid "There was an error sending the Test E-Mail: %(res)s"
msgstr "发送测试邮件时发生错误: %(res)s"
#: cps/web.py:2600
#: cps/web.py:2580
msgid "E-Mail settings updated"
msgstr "E-Mail 设置已更新"
#: cps/web.py:2601
#: cps/web.py:2581
msgid "Edit mail settings"
msgstr "编辑邮箱设置"
#: cps/web.py:2630
#: cps/web.py:2610
#, python-format
msgid "User '%(nick)s' deleted"
msgstr "用户 '%(nick)s' 已被删除"
#: cps/web.py:2728
#: cps/web.py:2708
#, python-format
msgid "User '%(nick)s' updated"
msgstr "用户 '%(nick)s' 已被更新"
#: cps/web.py:2731
#: cps/web.py:2711
msgid "An unknown error occured."
msgstr "发生未知错误。"
#: cps/web.py:2734
#: cps/web.py:2714
#, python-format
msgid "Edit User %(nick)s"
msgstr "编辑用户 %(nick)s"
#: cps/web.py:2756
#: cps/web.py:2730
msgid "Error opening eBook. File does not exist or file is not accessible"
msgstr "打开电子书出错。文件不存在或不可访问"
#: cps/web.py:2771 cps/web.py:2954 cps/web.py:3078
#: cps/web.py:2745 cps/web.py:2917 cps/web.py:3060
msgid "edit metadata"
msgstr "编辑元数据"
#: cps/web.py:2783 cps/web.py:2787
#: cps/web.py:2757 cps/web.py:2761
msgid "unknown"
msgstr "未知"
#: cps/web.py:2972
#: cps/web.py:2954
#, python-format
msgid "File extension \"%s\" is not allowed to be uploaded to this server"
msgstr "不能上传后缀为 \"%s\" 的文件到此服务器"
#: cps/web.py:2978
#: cps/web.py:2960
msgid "File to be uploaded must have an extension"
msgstr "要上传的文件必须有一个后缀"
#: cps/web.py:2997
#: cps/web.py:2979
#, python-format
msgid "Failed to create path %s (Permission denied)."
msgstr "创建路径 %s 失败(权限拒绝)。"
#: cps/web.py:3002
#: cps/web.py:2984
#, python-format
msgid "Failed to store file %s (Permission denied)."
msgstr "存储文件 %s 失败(权限拒绝)。"
#: cps/web.py:3007
#: cps/web.py:2989
#, python-format
msgid "Failed to delete file %s (Permission denied)."
msgstr "删除文件 %s 失败(权限拒绝)。"
@ -607,6 +607,18 @@ msgstr "您确定要关闭 Calibre-web 吗?"
msgid "Updating, please do not reload page"
msgstr "正在更新,请不要刷新页面"
#: cps/templates/author.html:15
msgid "via"
msgstr ""
#: cps/templates/author.html:23
msgid "In Library"
msgstr ""
#: cps/templates/author.html:69
msgid "More by"
msgstr ""
#: cps/templates/book_edit.html:16
msgid "Delete Book"
msgstr "删除书籍"
@ -615,12 +627,13 @@ msgstr "删除书籍"
msgid "Book Title"
msgstr "书名"
#: cps/templates/book_edit.html:26 cps/templates/book_edit.html:188
#: cps/templates/search_form.html:10
#: cps/templates/book_edit.html:26 cps/templates/book_edit.html:208
#: cps/templates/book_edit.html:226 cps/templates/search_form.html:10
msgid "Author"
msgstr "作者"
#: cps/templates/book_edit.html:30 cps/templates/book_edit.html:190
#: cps/templates/book_edit.html:30 cps/templates/book_edit.html:213
#: cps/templates/book_edit.html:228
msgid "Description"
msgstr "简介"
@ -703,35 +716,35 @@ msgstr "搜索关键字"
msgid "Go!"
msgstr "走起!"
#: cps/templates/book_edit.html:168
#: cps/templates/book_edit.html:171
msgid "Click the cover to load metadata to the form"
msgstr "点击封面加载元数据到表单"
#: cps/templates/book_edit.html:172 cps/templates/book_edit.html:185
#: cps/templates/book_edit.html:183 cps/templates/book_edit.html:223
msgid "Loading..."
msgstr "加载中..."
#: cps/templates/book_edit.html:175 cps/templates/layout.html:199
#: cps/templates/book_edit.html:188 cps/templates/layout.html:199
msgid "Close"
msgstr "关闭"
#: cps/templates/book_edit.html:186
msgid "Search error!"
msgstr "搜索错误"
#: cps/templates/book_edit.html:187
msgid "No Result! Please try anonther keyword."
msgstr "没有结果!请尝试别的关键字."
#: cps/templates/book_edit.html:189 cps/templates/detail.html:125
#: cps/templates/search_form.html:14
#: cps/templates/book_edit.html:210 cps/templates/book_edit.html:227
#: cps/templates/detail.html:125 cps/templates/search_form.html:14
msgid "Publisher"
msgstr "出版社"
#: cps/templates/book_edit.html:191
#: cps/templates/book_edit.html:215 cps/templates/book_edit.html:229
msgid "Source"
msgstr "来源"
#: cps/templates/book_edit.html:224
msgid "Search error!"
msgstr "搜索错误"
#: cps/templates/book_edit.html:225
msgid "No Result! Please try anonther keyword."
msgstr "没有结果!请尝试别的关键字."
#: cps/templates/config_edit.html:7
msgid "Location of Calibre database"
msgstr "Calibre 数据库位置"
@ -821,7 +834,7 @@ msgstr ""
msgid "Default Settings for new users"
msgstr "新用户默认设置"
#: cps/templates/config_edit.html:128 cps/templates/user_edit.html:90
#: cps/templates/config_edit.html:128 cps/templates/user_edit.html:86
msgid "Admin user"
msgstr "管理用户"
@ -922,6 +935,11 @@ msgstr "保存设置并发送测试邮件"
msgid "Next"
msgstr "下一个"
#: cps/templates/feed.xml:29 cps/templates/index.xml:7
#: cps/templates/layout.html:40 cps/templates/layout.html:41
msgid "Search"
msgstr "搜索"
#: cps/templates/index.html:5
msgid "Discover (Random Books)"
msgstr "发现(随机书籍)"
@ -930,52 +948,47 @@ msgstr "发现(随机书籍)"
msgid "Start"
msgstr "开始"
#: cps/templates/index.xml:7 cps/templates/layout.html:40
#: cps/templates/layout.html:41
msgid "Search"
msgstr "搜索"
#: cps/templates/index.xml:15 cps/templates/layout.html:121
#: cps/templates/index.xml:14 cps/templates/layout.html:121
msgid "Hot Books"
msgstr "热门书籍"
#: cps/templates/index.xml:19
#: cps/templates/index.xml:18
msgid "Popular publications from this catalog based on Downloads."
msgstr "基于下载数的热门书籍"
#: cps/templates/index.xml:22 cps/templates/layout.html:124
#: cps/templates/index.xml:21 cps/templates/layout.html:124
msgid "Best rated Books"
msgstr "最高评分书籍"
#: cps/templates/index.xml:26
#: cps/templates/index.xml:25
msgid "Popular publications from this catalog based on Rating."
msgstr "基于评分的热门书籍"
#: cps/templates/index.xml:29
#: cps/templates/index.xml:28
msgid "New Books"
msgstr "新书"
#: cps/templates/index.xml:33
#: cps/templates/index.xml:32
msgid "The latest Books"
msgstr "最新书籍"
#: cps/templates/index.xml:40
#: cps/templates/index.xml:39
msgid "Show Random Books"
msgstr "显示随机书籍"
#: cps/templates/index.xml:57 cps/templates/layout.html:139
#: cps/templates/index.xml:56 cps/templates/layout.html:139
msgid "Authors"
msgstr "作者"
#: cps/templates/index.xml:61
#: cps/templates/index.xml:60
msgid "Books ordered by Author"
msgstr "书籍按作者排序"
#: cps/templates/index.xml:68
#: cps/templates/index.xml:67
msgid "Books ordered by category"
msgstr "书籍按分类排序"
#: cps/templates/index.xml:75
#: cps/templates/index.xml:74
msgid "Books ordered by series"
msgstr "书籍按丛书排序"
@ -1086,7 +1099,11 @@ msgstr "通过魔法链接登录"
msgid "Calibre Web ebook catalog"
msgstr ""
#: cps/templates/read.html:125
#: cps/templates/read.html:69
msgid "Settings"
msgstr ""
#: cps/templates/read.html:72
msgid "Reflow text when sidebars are open."
msgstr ""
@ -1135,7 +1152,7 @@ msgid "No Results for:"
msgstr "找不到结果:"
#: cps/templates/search.html:7
msgid "Please try a diffrent Search"
msgid "Please try a different search"
msgstr "请尝试别的关键字"
#: cps/templates/search.html:9
@ -1227,45 +1244,45 @@ msgid "Show all"
msgstr "显示全部"
#: cps/templates/user_edit.html:46
msgid "Show mature content"
msgstr ""
#: cps/templates/user_edit.html:50
msgid "Show random books"
msgstr "显示随机书籍"
#: cps/templates/user_edit.html:54
#: cps/templates/user_edit.html:50
msgid "Show hot books"
msgstr "显示热门书籍"
#: cps/templates/user_edit.html:58
#: cps/templates/user_edit.html:54
msgid "Show best rated books"
msgstr "显示最高评分书籍"
#: cps/templates/user_edit.html:62
#: cps/templates/user_edit.html:58
msgid "Show language selection"
msgstr "显示语言选择"
#: cps/templates/user_edit.html:66
#: cps/templates/user_edit.html:62
msgid "Show series selection"
msgstr "显示丛书选择"
#: cps/templates/user_edit.html:70
#: cps/templates/user_edit.html:66
msgid "Show category selection"
msgstr "显示分类选择"
#: cps/templates/user_edit.html:74
#: cps/templates/user_edit.html:70
msgid "Show author selection"
msgstr "显示作者选择"
#: cps/templates/user_edit.html:78
#: cps/templates/user_edit.html:74
msgid "Show read and unread"
msgstr "显示已读和未读"
#: cps/templates/user_edit.html:82
#: cps/templates/user_edit.html:78
msgid "Show random books in detail view"
msgstr "在详情页显示随机书籍"
#: cps/templates/user_edit.html:90
msgid "Show mature content"
msgstr ""
#: cps/templates/user_edit.html:123
msgid "Delete this user"
msgstr "删除此用户"

@ -6,6 +6,7 @@ from sqlalchemy import exc
from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy.orm import *
from flask_login import AnonymousUserMixin
import sys
import os
import logging
from werkzeug.security import generate_password_hash
@ -50,7 +51,7 @@ DEVELOPMENT = False
class UserBase:
@staticmethod
@classmethod
def is_authenticated(self):
return True
@ -171,6 +172,7 @@ class Anonymous(AnonymousUserMixin, UserBase):
settings = session.query(Settings).first()
self.nickname = data.nickname
self.role = data.role
self.id=data.id
self.sidebar_view = data.sidebar_view
self.default_language = data.default_language
self.locale = data.locale
@ -186,6 +188,8 @@ class Anonymous(AnonymousUserMixin, UserBase):
def is_anonymous(self):
return self.anon_browse
def is_authenticated(self):
return False
# Baseclass representing Shelfs in calibre-web inapp.db
class Shelf(Base):
@ -212,15 +216,26 @@ class BookShelf(Base):
def __repr__(self):
return '<Book %r>' % self.id
class ReadBook(Base):
__tablename__ = 'book_read_link'
id=Column(Integer, primary_key=True)
id = Column(Integer, primary_key=True)
book_id = Column(Integer, unique=False)
user_id =Column(Integer, ForeignKey('user.id'), unique=False)
user_id = Column(Integer, ForeignKey('user.id'), unique=False)
is_read = Column(Boolean, unique=False)
class Bookmark(Base):
__tablename__ = 'bookmark'
id = Column(Integer, primary_key=True)
user_id = Column(Integer, ForeignKey('user.id'))
book_id = Column(Integer)
format = Column(String(collation='NOCASE'))
bookmark_key = Column(String)
# Baseclass representing Downloads from calibre-web in app.db
class Downloads(Base):
__tablename__ = 'downloads'
@ -376,7 +391,11 @@ class Config:
(self.config_default_role & ROLE_DELETE_BOOKS == ROLE_DELETE_BOOKS))
def mature_content_tags(self):
return list(map(unicode.lstrip, self.config_mature_content_tags.split(",")))
if (sys.version_info > (3, 0)): #Python3 str, Python2 unicode
lstrip = str.lstrip
else:
lstrip = unicode.lstrip
return list(map(lstrip, self.config_mature_content_tags.split(",")))
def get_Log_Level(self):
ret_value=""
@ -396,7 +415,9 @@ class Config:
# rows with SQL commands
def migrate_Database():
if not engine.dialect.has_table(engine.connect(), "book_read_link"):
ReadBook.__table__.create(bind = engine)
ReadBook.__table__.create(bind=engine)
if not engine.dialect.has_table(engine.connect(), "bookmark"):
Bookmark.__table__.create(bind=engine)
try:
session.query(exists().where(User.locale)).scalar()

@ -13,6 +13,12 @@ try:
except ImportError:
goodreads_support = False
try:
import Levenshtein
levenshtein_support = True
except ImportError:
levenshtein_support = False
try:
from functools import reduce
except ImportError:
@ -73,6 +79,7 @@ import hashlib
from redirect import redirect_back, is_safe_url
from tornado import version as tornadoVersion
from socket import error as SocketError
try:
from urllib.parse import quote
@ -151,7 +158,7 @@ class Gauth:
@Singleton
class Gdrive:
def __init__(self):
self.drive = gdriveutils.getDrive(Gauth.Instance().auth)
self.drive = gdriveutils.getDrive(gauth=Gauth.Instance().auth)
class ReverseProxied(object):
@ -196,6 +203,7 @@ class ReverseProxied(object):
mimetypes.init()
mimetypes.add_type('application/xhtml+xml', '.xhtml')
mimetypes.add_type('application/epub+zip', '.epub')
mimetypes.add_type('application/fb2+zip', '.fb2')
mimetypes.add_type('application/x-mobipocket-ebook', '.mobi')
mimetypes.add_type('application/x-mobipocket-ebook', '.prc')
mimetypes.add_type('application/vnd.amazon.ebook', '.azw')
@ -511,7 +519,7 @@ def common_filters():
if current_user.filter_language() != "all":
lang_filter = db.Books.languages.any(db.Languages.lang_code == current_user.filter_language())
else:
lang_filter = True
lang_filter = true()
content_rating_filter = false() if current_user.mature_content else \
db.Books.tags.any(db.Tags.name.in_(config.mature_content_tags()))
return and_(lang_filter, ~content_rating_filter)
@ -528,7 +536,7 @@ def fill_indexpage(page, database, db_filter, order):
pagination = Pagination(page, config.config_books_per_page,
len(db.session.query(database)
.filter(db_filter).filter(common_filters()).all()))
entries = db.session.query(database).filter(common_filters())\
entries = db.session.query(database).filter(db_filter).filter(common_filters())\
.order_by(order).offset(off).limit(config.config_books_per_page)
return entries, random, pagination
@ -624,7 +632,7 @@ def before_request():
def feed_index():
xml = render_title_template('index.xml')
response = make_response(xml)
response.headers["Content-Type"] = "application/xml"
response.headers["Content-Type"] = "application/atom+xml; charset=utf-8"
return response
@ -633,7 +641,7 @@ def feed_index():
def feed_osd():
xml = render_title_template('osd.xml', lang='de-DE')
response = make_response(xml)
response.headers["Content-Type"] = "application/xml"
response.headers["Content-Type"] = "application/xml; charset=utf-8"
return response
@ -663,7 +671,7 @@ def feed_search(term):
else:
xml = render_title_template('feed.xml', searchterm="")
response = make_response(xml)
response.headers["Content-Type"] = "application/xml"
response.headers["Content-Type"] = "application/atom+xml; charset=utf-8"
return response
@ -677,7 +685,7 @@ def feed_new():
db.Books, True, db.Books.timestamp.desc())
xml = render_title_template('feed.xml', entries=entries, pagination=pagination)
response = make_response(xml)
response.headers["Content-Type"] = "application/xml"
response.headers["Content-Type"] = "application/atom+xml; charset=utf-8"
return response
@ -689,7 +697,7 @@ def feed_discover():
pagination = Pagination(1, config.config_books_per_page, int(config.config_books_per_page))
xml = render_title_template('feed.xml', entries=entries, pagination=pagination)
response = make_response(xml)
response.headers["Content-Type"] = "application/xml"
response.headers["Content-Type"] = "application/atom+xml; charset=utf-8"
return response
@ -703,7 +711,7 @@ def feed_best_rated():
db.Books, db.Books.ratings.any(db.Ratings.rating > 9), db.Books.timestamp.desc())
xml = render_title_template('feed.xml', entries=entries, pagination=pagination)
response = make_response(xml)
response.headers["Content-Type"] = "application/xml"
response.headers["Content-Type"] = "application/atom+xml; charset=utf-8"
return response
@ -731,7 +739,7 @@ def feed_hot():
pagination = Pagination((int(off) / (int(config.config_books_per_page)) + 1), config.config_books_per_page, numBooks)
xml = render_title_template('feed.xml', entries=entries, pagination=pagination)
response = make_response(xml)
response.headers["Content-Type"] = "application/xml"
response.headers["Content-Type"] = "application/atom+xml; charset=utf-8"
return response
@ -747,7 +755,7 @@ def feed_authorindex():
len(db.session.query(db.Authors).all()))
xml = render_title_template('feed.xml', listelements=entries, folder='feed_author', pagination=pagination)
response = make_response(xml)
response.headers["Content-Type"] = "application/xml"
response.headers["Content-Type"] = "application/atom+xml; charset=utf-8"
return response
@ -761,7 +769,7 @@ def feed_author(book_id):
db.Books, db.Books.authors.any(db.Authors.id == book_id), db.Books.timestamp.desc())
xml = render_title_template('feed.xml', entries=entries, pagination=pagination)
response = make_response(xml)
response.headers["Content-Type"] = "application/xml"
response.headers["Content-Type"] = "application/atom+xml; charset=utf-8"
return response
@ -777,7 +785,7 @@ def feed_categoryindex():
len(db.session.query(db.Tags).all()))
xml = render_title_template('feed.xml', listelements=entries, folder='feed_category', pagination=pagination)
response = make_response(xml)
response.headers["Content-Type"] = "application/xml"
response.headers["Content-Type"] = "application/atom+xml; charset=utf-8"
return response
@ -787,11 +795,11 @@ def feed_category(book_id):
off = request.args.get("offset")
if not off:
off = 0
entries, random, pagination = fill_indexpage((int(off) / (int(config.config_books_per_page)) + 1),
entries, __, pagination = fill_indexpage((int(off) / (int(config.config_books_per_page)) + 1),
db.Books, db.Books.tags.any(db.Tags.id == book_id), db.Books.timestamp.desc())
xml = render_title_template('feed.xml', entries=entries, pagination=pagination)
response = make_response(xml)
response.headers["Content-Type"] = "application/xml"
response.headers["Content-Type"] = "application/atom+xml; charset=utf-8"
return response
@ -807,7 +815,7 @@ def feed_seriesindex():
len(db.session.query(db.Series).all()))
xml = render_title_template('feed.xml', listelements=entries, folder='feed_series', pagination=pagination)
response = make_response(xml)
response.headers["Content-Type"] = "application/xml"
response.headers["Content-Type"] = "application/atom+xml; charset=utf-8"
return response
@ -821,7 +829,7 @@ def feed_series(book_id):
db.Books, db.Books.series.any(db.Series.id == book_id),db.Books.series_index)
xml = render_title_template('feed.xml', entries=entries, pagination=pagination)
response = make_response(xml)
response.headers["Content-Type"] = "application/xml"
response.headers["Content-Type"] = "application/atom+xml; charset=utf-8"
return response
@ -858,7 +866,7 @@ def get_opds_download_link(book_id, book_format):
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 current_user.is_authenticated:
if current_user.is_authenticated():
helper.update_download(book_id, int(current_user.id))
file_name = book.title
if len(book.authors) > 0:
@ -866,7 +874,11 @@ def get_opds_download_link(book_id, book_format):
file_name = helper.get_valid_filename(file_name)
headers = Headers()
headers["Content-Disposition"] = "attachment; filename*=UTF-8''%s.%s" % (quote(file_name.encode('utf8')), book_format)
app.logger.info(time.time()-startTime)
try:
headers["Content-Type"] = mimetypes.types_map['.' + book_format]
except KeyError:
headers["Content-Type"] = "application/octet-stream"
app.logger.info(time.time() - startTime)
startTime = time.time()
if config.config_use_google_drive:
app.logger.info(time.time() - startTime)
@ -1078,12 +1090,9 @@ def hot_books(page):
hot_books = all_books.offset(off).limit(config.config_books_per_page)
entries = list()
for book in hot_books:
downloadBook = db.session.query(db.Books).filter(db.Books.id == book.Downloads.book_id).first()
downloadBook = db.session.query(db.Books).filter(common_filters()).filter(db.Books.id == book.Downloads.book_id).first()
if downloadBook:
entries.append(
db.session.query(db.Books).filter(common_filters())
.filter(db.Books.id == book.Downloads.book_id).first()
)
entries.append(downloadBook)
else:
ub.session.query(ub.Downloads).filter(book.Downloads.book_id == ub.Downloads.book_id).delete()
ub.session.commit()
@ -1118,6 +1127,8 @@ def author_list():
entries = db.session.query(db.Authors, func.count('books_authors_link.book').label('count'))\
.join(db.books_authors_link).join(db.Books).filter(common_filters())\
.group_by('books_authors_link.author').order_by(db.Authors.sort).all()
for entry in entries:
entry.Authors.name=entry.Authors.name.replace('|',',')
return render_title_template('list.html', entries=entries, folder='author', title=_(u"Author list"))
@ -1125,28 +1136,43 @@ def author_list():
@app.route("/author/<int:book_id>/<int:page>'")
@login_required_if_no_ano
def author(book_id, page):
entries, random, pagination = fill_indexpage(page, db.Books, db.Books.authors.any(db.Authors.id == book_id),
entries, __, pagination = fill_indexpage(page, db.Books, db.Books.authors.any(db.Authors.id == book_id),
db.Books.timestamp.desc())
if entries is None:
flash(_(u"Error opening eBook. File does not exist or file is not accessible:"), category="error")
return redirect(url_for("index"))
name = db.session.query(db.Authors).filter(db.Authors.id == book_id).first().name
name = (db.session.query(db.Authors).filter(db.Authors.id == book_id).first().name).replace('|',',')
author_info = None
other_books = None
other_books = []
if goodreads_support and config.config_use_goodreads:
gc = GoodreadsClient(config.config_goodreads_api_key, config.config_goodreads_api_secret)
author_info = gc.find_author(author_name=name)
other_books = get_unique_other_books(entries.all(), author_info.books)
return render_title_template('author.html', entries=entries, pagination=pagination,
title=name, author=author_info, other_books=other_books)
def get_unique_other_books(library_books, author_books):
# Get all identifiers (ISBN, Goodreads, etc) and filter author's books by that list so we show fewer duplicates
# Note: Not all images will be shown, even though they're available on Goodreads.com.
# See https://www.goodreads.com/topic/show/18213769-goodreads-book-images
identifiers = reduce(lambda acc, book: acc + map(lambda identifier: identifier.val, book.identifiers), entries.all(), [])
other_books = filter(lambda book: book.isbn not in identifiers and book.gid["#text"] not in identifiers, author_info.books)
identifiers = reduce(lambda acc, book: acc + map(lambda identifier: identifier.val, book.identifiers), library_books, [])
other_books = filter(lambda book: book.isbn not in identifiers and book.gid["#text"] not in identifiers, author_books)
# Fuzzy match book titles
if levenshtein_support:
library_titles = reduce(lambda acc, book: acc + [book.title], library_books, [])
other_books = filter(lambda author_book: not filter(
lambda library_book:
Levenshtein.ratio(re.sub(r"\(.*\)", "", author_book.title), library_book) > 0.7, # Remove items in parentheses before comparing
library_titles
), other_books)
return other_books
return render_title_template('author.html', entries=entries, pagination=pagination,
title=name, author=author_info, other_books=other_books)
@app.route("/series")
@ -1186,13 +1212,12 @@ def language_overview():
lang.name = _(isoLanguages.get(part3=lang.lang_code).name)
else:
try:
langfound = 1
cur_l = LC.parse(current_user.filter_language())
except Exception:
langfound = 0
cur_l = None
languages = db.session.query(db.Languages).filter(
db.Languages.lang_code == current_user.filter_language()).all()
if langfound:
if cur_l:
languages[0].name = cur_l.get_language_name(get_locale())
else:
languages[0].name = _(isoLanguages.get(part3=languages[0].lang_code).name)
@ -1298,6 +1323,26 @@ def show_book(book_id):
return redirect(url_for("index"))
@app.route("/ajax/bookmark/<int:book_id>/<book_format>", methods=['POST'])
@login_required
def bookmark(book_id, book_format):
bookmark_key = request.form["bookmark"]
ub.session.query(ub.Bookmark).filter(ub.and_(ub.Bookmark.user_id == int(current_user.id),
ub.Bookmark.book_id == book_id,
ub.Bookmark.format == book_format)).delete()
if not bookmark_key:
ub.session.commit()
return "", 204
bookmark = ub.Bookmark(user_id=current_user.id,
book_id=book_id,
format=book_format,
bookmark_key=bookmark_key)
ub.session.merge(bookmark)
ub.session.commit()
return "", 201
@app.route("/admin")
@login_required
def admin_forbidden():
@ -1672,6 +1717,7 @@ def feed_get_cover(book_id):
def render_read_books(page, are_read, as_xml=False):
if not current_user.is_anonymous():
readBooks = ub.session.query(ub.ReadBook).filter(ub.ReadBook.user_id == int(current_user.id)).filter(ub.ReadBook.is_read == True).all()
readBookIds = [x.book_id for x in readBooks]
if are_read:
@ -1681,6 +1727,11 @@ def render_read_books(page, are_read, as_xml=False):
entries, random, pagination = fill_indexpage(page, db.Books,
db_filter, db.Books.timestamp.desc())
else:
entries = []
random = False
pagination = Pagination(page, 1, 0)
if as_xml:
xml = render_title_template('feed.xml', entries=entries, pagination=pagination)
response = make_response(xml)
@ -1732,10 +1783,18 @@ def unread_books(page):
@login_required_if_no_ano
def read_book(book_id, book_format):
book = db.session.query(db.Books).filter(db.Books.id == book_id).first()
if book:
if not book:
flash(_(u"Error opening eBook. File does not exist or file is not accessible:"), category="error")
return redirect(url_for("index"))
book_dir = os.path.join(config.get_main_dir, "cps", "static", str(book_id))
if not os.path.exists(book_dir):
os.mkdir(book_dir)
bookmark = None
if current_user.is_authenticated():
bookmark = ub.session.query(ub.Bookmark).filter(ub.and_(ub.Bookmark.user_id == int(current_user.id),
ub.Bookmark.book_id == book_id,
ub.Bookmark.format == book_format.upper())).first()
if book_format.lower() == "epub":
# check if mimetype file is exists
mime_file = str(book_id) + "/mimetype"
@ -1758,23 +1817,21 @@ def read_book(book_id, book_format):
fd.write(zfile.read(name))
fd.close()
zfile.close()
return render_title_template('read.html', bookid=book_id, title=_(u"Read a Book"))
return render_title_template('read.html', bookid=book_id, title=_(u"Read a Book"), bookmark=bookmark)
elif book_format.lower() == "pdf":
return render_title_template('readpdf.html', pdffile=book_id, title=_(u"Read a Book"))
elif book_format.lower() == "txt":
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"
else:
for fileext in ["cbr","cbt","cbz"]:
if book_format.lower() == fileext:
all_name = str(book_id) + "/" + book.data[0].name + "." + fileext
tmp_file = os.path.join(book_dir, book.data[0].name) + "." + fileext
if not os.path.exists(all_name):
cbr_file = os.path.join(config.config_calibre_dir, book.path, book.data[0].name) + ".cbr"
cbr_file = os.path.join(config.config_calibre_dir, book.path, book.data[0].name) + "." + fileext
copyfile(cbr_file, tmp_file)
return render_title_template('readcbr.html', comicfile=all_name, title=_(u"Read a Book"))
else:
flash(_(u"Error opening eBook. File does not exist or file is not accessible:"), category="error")
return redirect(url_for("index"))
@app.route("/download/<int:book_id>/<book_format>")
@login_required_if_no_ano
@ -1785,7 +1842,7 @@ def get_download_link(book_id, book_format):
data = db.session.query(db.Data).filter(db.Data.book == book.id).filter(db.Data.format == book_format.upper()).first()
if data:
# collect downloaded books only for registered user and not for anonymous user
if current_user.is_authenticated:
if current_user.is_authenticated():
helper.update_download(book_id, int(current_user.id))
file_name = book.title
if len(book.authors) > 0:
@ -1819,7 +1876,7 @@ def get_download_link_ext(book_id, book_format, anyname):
def register():
if not config.config_public_reg:
abort(404)
if current_user is not None and current_user.is_authenticated:
if current_user is not None and current_user.is_authenticated():
return redirect(url_for('index'))
if request.method == "POST":
@ -1856,7 +1913,7 @@ def register():
def login():
if not config.db_configured:
return redirect(url_for('basic_configuration'))
if current_user is not None and current_user.is_authenticated:
if current_user is not None and current_user.is_authenticated():
return redirect(url_for('index'))
if request.method == "POST":
form = request.form.to_dict()
@ -1883,7 +1940,7 @@ def login():
@app.route('/logout')
@login_required
def logout():
if current_user is not None and current_user.is_authenticated:
if current_user is not None and current_user.is_authenticated():
logout_user()
return redirect(url_for('login'))
@ -2128,10 +2185,11 @@ def edit_shelf(shelf_id):
@login_required
def delete_shelf(shelf_id):
cur_shelf = ub.session.query(ub.Shelf).filter(ub.Shelf.id == shelf_id).first()
deleted = false
if current_user.role_admin():
deleted = ub.session.query(ub.Shelf).filter(ub.Shelf.id == shelf_id).delete()
else:
if not cur_shelf.is_public and not cur_shelf.user_id == int(current_user.id) \
if (not cur_shelf.is_public and cur_shelf.user_id == int(current_user.id)) \
or (cur_shelf.is_public and current_user.role_edit_shelfs()):
deleted = ub.session.query(ub.Shelf).filter(ub.or_(ub.and_(ub.Shelf.user_id == int(current_user.id),
ub.Shelf.id == shelf_id),
@ -2697,7 +2755,7 @@ def edit_book(book_id):
except Exception:
book.languages[index].language_name = _(isoLanguages.get(part3=book.languages[index].lang_code).name)
for author in book.authors:
author_names.append(author.name)
author_names.append(author.name.replace('|',','))
# Show form
if request.method != 'POST':
@ -2706,12 +2764,41 @@ def edit_book(book_id):
# Update book
edited_books_id = set()
# Check and handle Uploaded file
if 'btn-upload-format' in request.files and '.' in request.files['btn-upload-format'].filename:
requested_file = request.files['btn-upload-format']
file_ext = requested_file.filename.rsplit('.', 1)[-1].lower()
if file_ext not in ALLOWED_EXTENSIONS:
flash(_('File extension "%s" is not allowed to be uploaded to this server' % file_ext), category="error")
return redirect(url_for('index'))
file_name = book.path.rsplit('/', 1)[-1]
filepath = config.config_calibre_dir + os.sep + book.path
filepath = os.path.normpath(filepath)
saved_filename = filepath + os.sep + file_name + '.' + file_ext
try:
requested_file.save(saved_filename)
except OSError:
flash(_(u"Failed to store file %s." % saved_filename), category="error")
return redirect(url_for('index'))
file_size = os.path.getsize(saved_filename)
is_format = db.session.query(db.Data).filter(db.Data.book == book_id).filter(db.Data.format == file_ext.upper()).first()
if is_format:
# Format entry already exists, no need to update the database
pass
else:
db_format = db.Data(book_id, file_ext.upper(), file_size, file_name)
db.session.add(db_format)
to_save = request.form.to_dict()
if book.title != to_save["book_title"]:
book.title = to_save["book_title"]
edited_books_id.add(book.id)
input_authors = to_save["author_name"].split('&')
input_authors = map(lambda it: it.strip(), input_authors)
input_authors = map(lambda it: it.strip().replace(',','|'), input_authors)
# we have all author names now
if input_authors == ['']:
input_authors = [_(u'unknown')] # prevent empty Author
@ -3028,5 +3115,11 @@ def upload():
def start_gevent():
from gevent.wsgi import WSGIServer
global gevent_server
try:
gevent_server = WSGIServer(('', ub.config.config_port), app)
gevent_server.serve_forever()
except SocketError:
app.logger.info('Unable to listen on \'\', trying on IPv4 only...')
gevent_server = WSGIServer(('0.0.0.0', ub.config.config_port), app)
gevent_server.serve_forever()

@ -8,7 +8,7 @@ msgid ""
msgstr ""
"Project-Id-Version: PROJECT VERSION\n"
"Report-Msgid-Bugs-To: EMAIL@ADDRESS\n"
"POT-Creation-Date: 2017-08-12 18:55+0200\n"
"POT-Creation-Date: 2017-09-16 07:48+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"
@ -17,7 +17,7 @@ msgstr ""
"Content-Transfer-Encoding: 8bit\n"
"Generated-By: Babel 2.4.0\n"
#: cps/book_formats.py:118 cps/book_formats.py:122 cps/web.py:1374
#: cps/book_formats.py:118 cps/book_formats.py:122 cps/web.py:1358
msgid "not installed"
msgstr ""
@ -61,373 +61,373 @@ msgstr ""
msgid "Could not find any formats suitable for sending by email"
msgstr ""
#: cps/ub.py:542
#: cps/ub.py:556
msgid "Guest"
msgstr ""
#: cps/web.py:974
#: cps/web.py:953
msgid "Requesting update package"
msgstr ""
#: cps/web.py:975
#: cps/web.py:954
msgid "Downloading update package"
msgstr ""
#: cps/web.py:976
#: cps/web.py:955
msgid "Unzipping update package"
msgstr ""
#: cps/web.py:977
#: cps/web.py:956
msgid "Files are replaced"
msgstr ""
#: cps/web.py:978
#: cps/web.py:957
msgid "Database connections are closed"
msgstr ""
#: cps/web.py:979
#: cps/web.py:958
msgid "Server is stopped"
msgstr ""
#: cps/web.py:980
#: cps/web.py:959
msgid "Update finished, please press okay and reload page"
msgstr ""
#: cps/web.py:1054
#: cps/web.py:1033
msgid "Recently Added Books"
msgstr ""
#: cps/web.py:1063
#: cps/web.py:1042
msgid "Newest Books"
msgstr ""
#: cps/web.py:1072
#: cps/web.py:1051
msgid "Oldest Books"
msgstr ""
#: cps/web.py:1081
#: cps/web.py:1060
msgid "Books (A-Z)"
msgstr ""
#: cps/web.py:1090
#: cps/web.py:1069
msgid "Books (Z-A)"
msgstr ""
#: cps/web.py:1126
#: cps/web.py:1096
msgid "Hot Books (most downloaded)"
msgstr ""
#: cps/web.py:1136
#: cps/web.py:1106
msgid "Best rated books"
msgstr ""
#: cps/templates/index.xml:36 cps/web.py:1145
#: cps/templates/index.xml:35 cps/web.py:1115
msgid "Random Books"
msgstr ""
#: cps/web.py:1161
#: cps/web.py:1124
msgid "Author list"
msgstr ""
#: cps/web.py:1181 cps/web.py:1212 cps/web.py:1351 cps/web.py:1835
#: cps/web.py:1134 cps/web.py:1190 cps/web.py:1315 cps/web.py:1774
msgid "Error opening eBook. File does not exist or file is not accessible:"
msgstr ""
#: cps/templates/index.xml:71 cps/web.py:1198
#: cps/templates/index.xml:70 cps/web.py:1176
msgid "Series list"
msgstr ""
#: cps/web.py:1210
#: cps/web.py:1188
#, python-format
msgid "Series: %(serie)s"
msgstr ""
#: cps/web.py:1243
#: cps/web.py:1221
msgid "Available languages"
msgstr ""
#: cps/web.py:1258
#: cps/web.py:1236
#, python-format
msgid "Language: %(name)s"
msgstr ""
#: cps/templates/index.xml:64 cps/web.py:1274
#: cps/templates/index.xml:63 cps/web.py:1245
msgid "Category list"
msgstr ""
#: cps/web.py:1286
#: cps/web.py:1257
#, python-format
msgid "Category: %(name)s"
msgstr ""
#: cps/web.py:1385
#: cps/web.py:1369
msgid "Excecution permissions missing"
msgstr ""
#: cps/web.py:1399
#: cps/web.py:1383
msgid "Statistics"
msgstr ""
#: cps/web.py:1563
#: cps/web.py:1547
msgid "Server restarted, please reload page"
msgstr ""
#: cps/web.py:1565
#: cps/web.py:1549
msgid "Performing shutdown of server, please close window"
msgstr ""
#: cps/web.py:1581
#: cps/web.py:1565
msgid "Update done"
msgstr ""
#: cps/web.py:1662 cps/web.py:1675
#: cps/web.py:1640 cps/web.py:1653
msgid "search"
msgstr ""
#: cps/templates/index.xml:43 cps/templates/index.xml:47
#: cps/templates/layout.html:127 cps/web.py:1751
#: cps/templates/index.xml:42 cps/templates/index.xml:46
#: cps/templates/layout.html:127 cps/web.py:1729
msgid "Read Books"
msgstr ""
#: cps/templates/index.xml:50 cps/templates/index.xml:54
#: cps/templates/layout.html:128 cps/web.py:1754
#: cps/templates/index.xml:49 cps/templates/index.xml:53
#: cps/templates/layout.html:128 cps/web.py:1732
msgid "Unread Books"
msgstr ""
#: cps/web.py:1821 cps/web.py:1823 cps/web.py:1825 cps/web.py:1832
#: cps/web.py:1805 cps/web.py:1807 cps/web.py:1809 cps/web.py:1816
msgid "Read a Book"
msgstr ""
#: cps/web.py:1888 cps/web.py:2513
#: cps/web.py:1868 cps/web.py:2493
msgid "Please fill out all fields!"
msgstr ""
#: cps/web.py:1889 cps/web.py:1905 cps/web.py:1910 cps/web.py:1912
#: cps/web.py:1869 cps/web.py:1885 cps/web.py:1890 cps/web.py:1892
msgid "register"
msgstr ""
#: cps/web.py:1904
#: cps/web.py:1884
msgid "An unknown error occured. Please try again later."
msgstr ""
#: cps/web.py:1909
#: cps/web.py:1889
msgid "This username or email address is already in use."
msgstr ""
#: cps/web.py:1928 cps/web.py:2024
#: cps/web.py:1908 cps/web.py:2004
#, python-format
msgid "you are now logged in as: '%(nickname)s'"
msgstr ""
#: cps/web.py:1933
#: cps/web.py:1913
msgid "Wrong Username or Password"
msgstr ""
#: cps/web.py:1939 cps/web.py:1960
#: cps/web.py:1919 cps/web.py:1940
msgid "login"
msgstr ""
#: cps/web.py:1972 cps/web.py:2003
#: cps/web.py:1952 cps/web.py:1983
msgid "Token not found"
msgstr ""
#: cps/web.py:1980 cps/web.py:2011
#: cps/web.py:1960 cps/web.py:1991
msgid "Token has expired"
msgstr ""
#: cps/web.py:1988
#: cps/web.py:1968
msgid "Success! Please return to your device"
msgstr ""
#: cps/web.py:2038
#: cps/web.py:2018
msgid "Please configure the SMTP mail settings first..."
msgstr ""
#: cps/web.py:2042
#: cps/web.py:2022
#, python-format
msgid "Book successfully send to %(kindlemail)s"
msgstr ""
#: cps/web.py:2046
#: cps/web.py:2026
#, python-format
msgid "There was an error sending this book: %(res)s"
msgstr ""
#: cps/web.py:2048 cps/web.py:2598
#: cps/web.py:2028 cps/web.py:2578
msgid "Please configure your kindle email address first..."
msgstr ""
#: cps/web.py:2092
#: cps/web.py:2072
#, python-format
msgid "Book has been added to shelf: %(sname)s"
msgstr ""
#: cps/web.py:2127
#: cps/web.py:2107
#, python-format
msgid "Book has been removed from shelf: %(sname)s"
msgstr ""
#: cps/web.py:2146 cps/web.py:2170
#: cps/web.py:2126 cps/web.py:2150
#, python-format
msgid "A shelf with the name '%(title)s' already exists."
msgstr ""
#: cps/web.py:2151
#: cps/web.py:2131
#, python-format
msgid "Shelf %(title)s created"
msgstr ""
#: cps/web.py:2153 cps/web.py:2181
#: cps/web.py:2133 cps/web.py:2161
msgid "There was an error"
msgstr ""
#: cps/web.py:2154 cps/web.py:2156
#: cps/web.py:2134 cps/web.py:2136
msgid "create a shelf"
msgstr ""
#: cps/web.py:2179
#: cps/web.py:2159
#, python-format
msgid "Shelf %(title)s changed"
msgstr ""
#: cps/web.py:2182 cps/web.py:2184
#: cps/web.py:2162 cps/web.py:2164
msgid "Edit a shelf"
msgstr ""
#: cps/web.py:2204
#: cps/web.py:2184
#, python-format
msgid "successfully deleted shelf %(name)s"
msgstr ""
#: cps/web.py:2226
#: cps/web.py:2206
#, python-format
msgid "Shelf: '%(name)s'"
msgstr ""
#: cps/web.py:2229
#: cps/web.py:2209
msgid "Error opening shelf. Shelf does not exist or is not accessible"
msgstr ""
#: cps/web.py:2261
#: cps/web.py:2241
#, python-format
msgid "Change order of Shelf: '%(name)s'"
msgstr ""
#: cps/web.py:2326
#: cps/web.py:2306
msgid "Found an existing account for this email address."
msgstr ""
#: cps/web.py:2328 cps/web.py:2332
#: cps/web.py:2308 cps/web.py:2312
#, python-format
msgid "%(name)s's profile"
msgstr ""
#: cps/web.py:2329
#: cps/web.py:2309
msgid "Profile updated"
msgstr ""
#: cps/web.py:2343
#: cps/web.py:2323
msgid "Admin page"
msgstr ""
#: cps/web.py:2467
#: cps/web.py:2447
msgid "Calibre-web configuration updated"
msgstr ""
#: cps/web.py:2474 cps/web.py:2480 cps/web.py:2494
#: cps/web.py:2454 cps/web.py:2460 cps/web.py:2474
msgid "Basic Configuration"
msgstr ""
#: cps/web.py:2478
#: cps/web.py:2458
msgid "DB location is not valid, please enter correct path"
msgstr ""
#: cps/templates/admin.html:34 cps/web.py:2515 cps/web.py:2568
#: cps/templates/admin.html:34 cps/web.py:2495 cps/web.py:2548
msgid "Add new user"
msgstr ""
#: cps/web.py:2560
#: cps/web.py:2540
#, python-format
msgid "User '%(user)s' created"
msgstr ""
#: cps/web.py:2564
#: cps/web.py:2544
msgid "Found an existing account for this email address or nickname."
msgstr ""
#: cps/web.py:2586
#: cps/web.py:2566
msgid "Mail settings updated"
msgstr ""
#: cps/web.py:2593
#: cps/web.py:2573
#, python-format
msgid "Test E-Mail successfully send to %(kindlemail)s"
msgstr ""
#: cps/web.py:2596
#: cps/web.py:2576
#, python-format
msgid "There was an error sending the Test E-Mail: %(res)s"
msgstr ""
#: cps/web.py:2600
#: cps/web.py:2580
msgid "E-Mail settings updated"
msgstr ""
#: cps/web.py:2601
#: cps/web.py:2581
msgid "Edit mail settings"
msgstr ""
#: cps/web.py:2630
#: cps/web.py:2610
#, python-format
msgid "User '%(nick)s' deleted"
msgstr ""
#: cps/web.py:2728
#: cps/web.py:2708
#, python-format
msgid "User '%(nick)s' updated"
msgstr ""
#: cps/web.py:2731
#: cps/web.py:2711
msgid "An unknown error occured."
msgstr ""
#: cps/web.py:2734
#: cps/web.py:2714
#, python-format
msgid "Edit User %(nick)s"
msgstr ""
#: cps/web.py:2756
#: cps/web.py:2730
msgid "Error opening eBook. File does not exist or file is not accessible"
msgstr ""
#: cps/web.py:2771 cps/web.py:2954 cps/web.py:3078
#: cps/web.py:2745 cps/web.py:2917 cps/web.py:3060
msgid "edit metadata"
msgstr ""
#: cps/web.py:2783 cps/web.py:2787
#: cps/web.py:2757 cps/web.py:2761
msgid "unknown"
msgstr ""
#: cps/web.py:2972
#: cps/web.py:2954
#, python-format
msgid "File extension \"%s\" is not allowed to be uploaded to this server"
msgstr ""
#: cps/web.py:2978
#: cps/web.py:2960
msgid "File to be uploaded must have an extension"
msgstr ""
#: cps/web.py:2997
#: cps/web.py:2979
#, python-format
msgid "Failed to create path %s (Permission denied)."
msgstr ""
#: cps/web.py:3002
#: cps/web.py:2984
#, python-format
msgid "Failed to store file %s (Permission denied)."
msgstr ""
#: cps/web.py:3007
#: cps/web.py:2989
#, python-format
msgid "Failed to delete file %s (Permission denied)."
msgstr ""
@ -598,6 +598,18 @@ msgstr ""
msgid "Updating, please do not reload page"
msgstr ""
#: cps/templates/author.html:15
msgid "via"
msgstr ""
#: cps/templates/author.html:23
msgid "In Library"
msgstr ""
#: cps/templates/author.html:69
msgid "More by"
msgstr ""
#: cps/templates/book_edit.html:16
msgid "Delete Book"
msgstr ""
@ -606,12 +618,13 @@ msgstr ""
msgid "Book Title"
msgstr ""
#: cps/templates/book_edit.html:26 cps/templates/book_edit.html:188
#: cps/templates/search_form.html:10
#: cps/templates/book_edit.html:26 cps/templates/book_edit.html:208
#: cps/templates/book_edit.html:226 cps/templates/search_form.html:10
msgid "Author"
msgstr ""
#: cps/templates/book_edit.html:30 cps/templates/book_edit.html:190
#: cps/templates/book_edit.html:30 cps/templates/book_edit.html:213
#: cps/templates/book_edit.html:228
msgid "Description"
msgstr ""
@ -694,33 +707,33 @@ msgstr ""
msgid "Go!"
msgstr ""
#: cps/templates/book_edit.html:168
#: cps/templates/book_edit.html:171
msgid "Click the cover to load metadata to the form"
msgstr ""
#: cps/templates/book_edit.html:172 cps/templates/book_edit.html:185
#: cps/templates/book_edit.html:183 cps/templates/book_edit.html:223
msgid "Loading..."
msgstr ""
#: cps/templates/book_edit.html:175 cps/templates/layout.html:199
#: cps/templates/book_edit.html:188 cps/templates/layout.html:199
msgid "Close"
msgstr ""
#: cps/templates/book_edit.html:186
msgid "Search error!"
#: cps/templates/book_edit.html:210 cps/templates/book_edit.html:227
#: cps/templates/detail.html:125 cps/templates/search_form.html:14
msgid "Publisher"
msgstr ""
#: cps/templates/book_edit.html:187
msgid "No Result! Please try anonther keyword."
#: cps/templates/book_edit.html:215 cps/templates/book_edit.html:229
msgid "Source"
msgstr ""
#: cps/templates/book_edit.html:189 cps/templates/detail.html:125
#: cps/templates/search_form.html:14
msgid "Publisher"
#: cps/templates/book_edit.html:224
msgid "Search error!"
msgstr ""
#: cps/templates/book_edit.html:191
msgid "Source"
#: cps/templates/book_edit.html:225
msgid "No Result! Please try anonther keyword."
msgstr ""
#: cps/templates/config_edit.html:7
@ -812,7 +825,7 @@ msgstr ""
msgid "Default Settings for new users"
msgstr ""
#: cps/templates/config_edit.html:128 cps/templates/user_edit.html:90
#: cps/templates/config_edit.html:128 cps/templates/user_edit.html:86
msgid "Admin user"
msgstr ""
@ -913,6 +926,11 @@ msgstr ""
msgid "Next"
msgstr ""
#: cps/templates/feed.xml:29 cps/templates/index.xml:7
#: cps/templates/layout.html:40 cps/templates/layout.html:41
msgid "Search"
msgstr ""
#: cps/templates/index.html:5
msgid "Discover (Random Books)"
msgstr ""
@ -921,52 +939,47 @@ msgstr ""
msgid "Start"
msgstr ""
#: cps/templates/index.xml:7 cps/templates/layout.html:40
#: cps/templates/layout.html:41
msgid "Search"
msgstr ""
#: cps/templates/index.xml:15 cps/templates/layout.html:121
#: cps/templates/index.xml:14 cps/templates/layout.html:121
msgid "Hot Books"
msgstr ""
#: cps/templates/index.xml:19
#: cps/templates/index.xml:18
msgid "Popular publications from this catalog based on Downloads."
msgstr ""
#: cps/templates/index.xml:22 cps/templates/layout.html:124
#: cps/templates/index.xml:21 cps/templates/layout.html:124
msgid "Best rated Books"
msgstr ""
#: cps/templates/index.xml:26
#: cps/templates/index.xml:25
msgid "Popular publications from this catalog based on Rating."
msgstr ""
#: cps/templates/index.xml:29
#: cps/templates/index.xml:28
msgid "New Books"
msgstr ""
#: cps/templates/index.xml:33
#: cps/templates/index.xml:32
msgid "The latest Books"
msgstr ""
#: cps/templates/index.xml:40
#: cps/templates/index.xml:39
msgid "Show Random Books"
msgstr ""
#: cps/templates/index.xml:57 cps/templates/layout.html:139
#: cps/templates/index.xml:56 cps/templates/layout.html:139
msgid "Authors"
msgstr ""
#: cps/templates/index.xml:61
#: cps/templates/index.xml:60
msgid "Books ordered by Author"
msgstr ""
#: cps/templates/index.xml:68
#: cps/templates/index.xml:67
msgid "Books ordered by category"
msgstr ""
#: cps/templates/index.xml:75
#: cps/templates/index.xml:74
msgid "Books ordered by series"
msgstr ""
@ -1077,7 +1090,11 @@ msgstr ""
msgid "Calibre Web ebook catalog"
msgstr ""
#: cps/templates/read.html:125
#: cps/templates/read.html:69
msgid "Settings"
msgstr ""
#: cps/templates/read.html:72
msgid "Reflow text when sidebars are open."
msgstr ""
@ -1126,7 +1143,7 @@ msgid "No Results for:"
msgstr ""
#: cps/templates/search.html:7
msgid "Please try a diffrent Search"
msgid "Please try a different search"
msgstr ""
#: cps/templates/search.html:9
@ -1218,43 +1235,43 @@ msgid "Show all"
msgstr ""
#: cps/templates/user_edit.html:46
msgid "Show mature content"
msgid "Show random books"
msgstr ""
#: cps/templates/user_edit.html:50
msgid "Show random books"
msgid "Show hot books"
msgstr ""
#: cps/templates/user_edit.html:54
msgid "Show hot books"
msgid "Show best rated books"
msgstr ""
#: cps/templates/user_edit.html:58
msgid "Show best rated books"
msgid "Show language selection"
msgstr ""
#: cps/templates/user_edit.html:62
msgid "Show language selection"
msgid "Show series selection"
msgstr ""
#: cps/templates/user_edit.html:66
msgid "Show series selection"
msgid "Show category selection"
msgstr ""
#: cps/templates/user_edit.html:70
msgid "Show category selection"
msgid "Show author selection"
msgstr ""
#: cps/templates/user_edit.html:74
msgid "Show author selection"
msgid "Show read and unread"
msgstr ""
#: cps/templates/user_edit.html:78
msgid "Show read and unread"
msgid "Show random books in detail view"
msgstr ""
#: cps/templates/user_edit.html:82
msgid "Show random books in detail view"
#: cps/templates/user_edit.html:90
msgid "Show mature content"
msgstr ""
#: cps/templates/user_edit.html:123

@ -11,4 +11,5 @@ PyYAML==3.12
rsa==3.4.2
six==1.10.0
uritemplate==3.0.0
goodreads==0.3.2
goodreads>=0.3.2
python-Levenshtein>=0.12.0

@ -10,9 +10,9 @@ Calibre Web is a web app providing a clean interface for browsing, reading and d
- Bootstrap 3 HTML5 interface
- full graphical setup
- User management
- User management with fine grained per-user permissions
- Admin interface
- User Interface in dutch, english, french, german, polish, russian, simplified chinese, spanish
- User Interface in dutch, english, french, german, italian, 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)
@ -21,10 +21,10 @@ Calibre Web is a web app providing a clean interface for browsing, reading and d
- Restrict eBook download to logged-in users
- Support for public user registration
- Send eBooks to Kindle devices with the click of a button
- Support for reading eBooks directly in the browser (.txt, .epub, .pdf)
- Support for reading eBooks directly in the browser (.txt, .epub, .pdf, .cbr, .cbt, .cbz)
- Upload new books in PDF, epub, fb2 format
- Support for Calibre custom columns
- Fine grained per-user permissions
- Ability to hide content based on categories for certain users
- Self update capability
- "Magic Link" login to make it easy to log on eReaders

Loading…
Cancel
Save