1
0
mirror of https://github.com/janeczku/calibre-web synced 2024-11-17 07:14:53 +00:00

Merge remote-tracking branch 'refs/remotes/janeczku/master'

This commit is contained in:
Radosław Kierznowski 2017-04-12 14:00:30 +02:00
commit ccf563023b
26 changed files with 807 additions and 858 deletions

2
cps.py
View File

@ -22,7 +22,7 @@ except ImportError:
if __name__ == '__main__':
if web.ub.DEVELOPMENT:
web.app.run(host="0.0.0.0", port=web.ub.config.config_port, debug=True)
web.app.run(port=web.ub.config.config_port, debug=True)
else:
if gevent_present:
web.app.logger.info('Attempting to start gevent')

View File

@ -67,9 +67,9 @@ class Identifiers(Base):
val = Column(String)
book = Column(Integer, ForeignKey('books.id'))
def __init__(self, val, type, book):
def __init__(self, val, id_type, book):
self.val = val
self.type = type
self.type = id_type
self.book = book
def formatType(self):
@ -209,9 +209,9 @@ class Data(Base):
uncompressed_size = Column(Integer)
name = Column(String)
def __init__(self, book, format, uncompressed_size, name):
def __init__(self, book, book_format, uncompressed_size, name):
self.book = book
self.format = format
self.format = book_format
self.uncompressed_size = uncompressed_size
self.name = name
@ -293,7 +293,7 @@ def setup_db():
engine = create_engine('sqlite:///'+ dbpath, echo=False, isolation_level="SERIALIZABLE")
try:
conn = engine.connect()
except Exception as e:
except Exception:
content = ub.session.query(ub.Settings).first()
content.config_calibre_dir = None
content.db_configured = False
@ -333,15 +333,15 @@ def setup_db():
'value': Column(String)}
cc_classes[row.id] = type('Custom_Column_' + str(row.id), (Base,), ccdict)
for id in cc_ids:
if id[1] == 'bool':
setattr(Books, 'custom_column_' + str(id[0]), relationship(cc_classes[id[0]],
for cc_id in cc_ids:
if cc_id[1] == 'bool':
setattr(Books, 'custom_column_' + str(cc_id[0]), relationship(cc_classes[cc_id[0]],
primaryjoin=(
Books.id == cc_classes[id[0]].book),
Books.id == cc_classes[cc_id[0]].book),
backref='books'))
else:
setattr(Books, 'custom_column_' + str(id[0]), relationship(cc_classes[id[0]],
secondary=books_custom_column_links[id[0]],
setattr(Books, 'custom_column_' + str(cc_id[0]), relationship(cc_classes[cc_id[0]],
secondary=books_custom_column_links[cc_id[0]],
backref='books'))
# Base.metadata.create_all(engine)

View File

@ -7,12 +7,13 @@ import os
import uploader
from iso639 import languages as isoLanguages
def extractCover(zip, coverFile, coverpath, tmp_file_name):
def extractCover(zipFile, coverFile, coverpath, tmp_file_name):
if coverFile is None:
return None
else:
zipCoverPath = os.path.join(coverpath , coverFile).replace('\\','/')
cf = zip.read(zipCoverPath)
cf = zipFile.read(zipCoverPath)
prefix = os.path.splitext(tmp_file_name)[0]
tmp_cover_name = prefix + '.' + os.path.basename(zipCoverPath)
image = open(tmp_cover_name, 'wb')
@ -28,12 +29,12 @@ def get_epub_info(tmp_file_path, original_file_name, original_file_extension):
'dc': 'http://purl.org/dc/elements/1.1/'
}
zip = zipfile.ZipFile(tmp_file_path)
epubZip = zipfile.ZipFile(tmp_file_path)
txt = zip.read('META-INF/container.xml')
txt = epubZip.read('META-INF/container.xml')
tree = etree.fromstring(txt)
cfname = tree.xpath('n:rootfiles/n:rootfile/@full-path', namespaces=ns)[0]
cf = zip.read(cfname)
cf = epubZip.read(cfname)
tree = etree.fromstring(cf)
coverpath = os.path.dirname(cfname)
@ -57,7 +58,7 @@ def get_epub_info(tmp_file_path, original_file_name, original_file_extension):
epub_metadata['description'] = ""
if epub_metadata['language'] == "Unknown":
epub_metadata['language'] == ""
epub_metadata['language'] = ""
else:
lang = epub_metadata['language'].split('-', 1)[0].lower()
if len(lang) == 2:
@ -70,7 +71,7 @@ def get_epub_info(tmp_file_path, original_file_name, original_file_extension):
coversection = tree.xpath("/pkg:package/pkg:manifest/pkg:item[@id='cover-image']/@href", namespaces=ns)
coverfile = None
if len(coversection) > 0:
coverfile = extractCover(zip, coversection[0], coverpath, tmp_file_path)
coverfile = extractCover(epubZip, coversection[0], coverpath, tmp_file_path)
else:
meta_cover = tree.xpath("/pkg:package/pkg:metadata/pkg:meta[@name='cover']/@content", namespaces=ns)
if len(meta_cover) > 0:
@ -78,15 +79,15 @@ def get_epub_info(tmp_file_path, original_file_name, original_file_extension):
if len(coversection) > 0:
filetype = coversection[0].rsplit('.', 1)[-1]
if filetype == "xhtml" or filetype == "html": #if cover is (x)html format
markup = zip.read(os.path.join(coverpath,coversection[0]))
markup = epubZip.read(os.path.join(coverpath, coversection[0]))
markupTree = etree.fromstring(markup)
# no matter xhtml or html with no namespace
imgsrc = markupTree.xpath("//*[local-name() = 'img']/@src")
# imgsrc maybe startwith "../"" so fullpath join then relpath to cwd
filename = os.path.relpath(os.path.join(os.path.dirname(os.path.join(coverpath, coversection[0])), imgsrc[0]))
coverfile = extractCover(zip, filename, "", tmp_file_path)
coverfile = extractCover(epubZip, filename, "", tmp_file_path)
else:
coverfile = extractCover(zip, coversection[0], coverpath, tmp_file_path)
coverfile = extractCover(epubZip, coversection[0], coverpath, tmp_file_path)
if epub_metadata['title'] is None:
title = original_file_name

View File

@ -2,12 +2,11 @@
# -*- coding: utf-8 -*-
from lxml import etree
import os
import uploader
try:
from io import StringIO
except ImportError as e:
import StringIO
#try:
# from io import StringIO
#except ImportError:
# import StringIO
def get_fb2_info(tmp_file_path, original_file_extension):

View File

@ -4,12 +4,11 @@ try:
from apiclient import errors
except ImportError:
pass
import os, time
import os
from ub import config
from sqlalchemy import *
from sqlalchemy import exc
from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy.orm import *
@ -165,7 +164,7 @@ def getFileFromEbooksFolder(drive, path, fileName):
if drive.auth.access_token_expired:
drive.auth.Refresh()
if path:
sqlCheckPath=path if path[-1] =='/' else path + '/'
# sqlCheckPath=path if path[-1] =='/' else path + '/'
folderId=getFolderId(path, drive)
else:
folderId=getEbooksFolderId(drive)
@ -195,7 +194,6 @@ def downloadFile(drive, path, filename, output):
f.GetContentFile(output)
def backupCalibreDbAndOptionalDownload(drive, f=None):
pass
if not drive:
drive=getDrive()
if drive.auth.access_token_expired:
@ -209,6 +207,7 @@ def backupCalibreDbAndOptionalDownload(drive, f=None):
if f:
databaseFile.GetContentFile(f)
def copyToDrive(drive, uploadFile, createRoot, replaceFiles,
ignoreFiles=[],
parent=None, prevDir=''):
@ -273,20 +272,19 @@ def watchChange(drive, channel_id, channel_type, channel_address,
drive=getDrive()
if drive.auth.access_token_expired:
drive.auth.Refresh()
"""Watch for all changes to a user's Drive.
Args:
service: Drive API service instance.
channel_id: Unique string that identifies this channel.
channel_type: Type of delivery mechanism used for this channel.
channel_address: Address where notifications are delivered.
channel_token: An arbitrary string delivered to the target address with
each notification delivered over this channel. Optional.
channel_address: Address where notifications are delivered. Optional.
Returns:
The created channel if successful
Raises:
apiclient.errors.HttpError: if http request to create channel fails.
"""
# Watch for all changes to a user's Drive.
# Args:
# service: Drive API service instance.
# channel_id: Unique string that identifies this channel.
# channel_type: Type of delivery mechanism used for this channel.
# channel_address: Address where notifications are delivered.
# channel_token: An arbitrary string delivered to the target address with
# each notification delivered over this channel. Optional.
# channel_address: Address where notifications are delivered. Optional.
# Returns:
# The created channel if successful
# Raises:
# apiclient.errors.HttpError: if http request to create channel fails.
body = {
'id': channel_id,
'type': channel_type,
@ -344,7 +342,7 @@ def stopChannel(drive, channel_id, resource_id):
drive=getDrive()
if drive.auth.access_token_expired:
drive.auth.Refresh()
service=drive.auth.service
# service=drive.auth.service
body = {
'id': channel_id,
'resourceId': resource_id
@ -356,15 +354,14 @@ def getChangeById (drive, change_id):
drive=getDrive()
if drive.auth.access_token_expired:
drive.auth.Refresh()
"""Print a single Change resource information.
Args:
service: Drive API service instance.
change_id: ID of the Change resource to retrieve.
"""
# Print a single Change resource information.
#
# Args:
# service: Drive API service instance.
# change_id: ID of the Change resource to retrieve.
try:
change = drive.auth.service.changes().get(changeId=change_id).execute()
return change
except errors.HttpError, error:
except (errors.HttpError, error):
web.app.logger.exception(error)
return None

View File

@ -13,6 +13,7 @@ import os
import traceback
import re
import unicodedata
try:
from StringIO import StringIO
from email.MIMEBase import MIMEBase
@ -80,7 +81,7 @@ def make_mobi(book_id, calibrepath):
file_path = os.path.join(calibrepath, book.path, data.name)
if os.path.exists(file_path + u".epub"):
p = subprocess.Popen((kindlegen + " \"" + file_path + u".epub\" ").encode(sys.getfilesystemencoding()),
shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE, stdin=subprocess.PIPE)
stdout=subprocess.PIPE, stderr=subprocess.PIPE)
# Poll process for new output until finished
while True:
nextline = p.stdout.readline()
@ -93,7 +94,7 @@ def make_mobi(book_id, calibrepath):
if not check or check < 2:
book.data.append(db.Data(
name=book.data[0].name,
format="MOBI",
book_format="MOBI",
book=book.id,
uncompressed_size=os.path.getsize(file_path + ".mobi")
))
@ -251,7 +252,7 @@ def get_valid_filename(value, replace_whitespace=True):
value=value.replace(u'ß',u'ss')
value = unicodedata.normalize('NFKD', value)
re_slugify = re.compile('[\W\s-]', re.UNICODE)
if type(value) is str: #Python3 str, Python2 unicode
if isinstance(value, str): #Python3 str, Python2 unicode
value = re_slugify.sub('', value).strip()
else:
value = unicode(re_slugify.sub('', value).strip())
@ -295,6 +296,7 @@ def update_dir_stucture(book_id, calibrepath):
book.path = new_authordir + '/' + book.path.split('/')[1]
db.session.commit()
def update_dir_structure_gdrive(book_id):
db.session.connection().connection.connection.create_function("title_sort", 1, db.title_sort)
book = db.session.query(db.Books).filter(db.Books.id == book_id).first()
@ -356,12 +358,15 @@ class Updater(threading.Thread):
def get_update_status(self):
return self.status
@classmethod
def file_to_list(self, file):
return [x.strip() for x in open(file, 'r') if not x.startswith('#EXT')]
@classmethod
def one_minus_two(self, one, two):
return [x for x in one if x not in set(two)]
@classmethod
def reduce_dirs(self, delete_files, new_list):
new_delete = []
for file in delete_files:
@ -382,6 +387,7 @@ class Updater(threading.Thread):
break
return list(set(new_delete))
@classmethod
def reduce_files(self, remove_items, exclude_items):
rf = []
for item in remove_items:
@ -389,6 +395,7 @@ class Updater(threading.Thread):
rf.append(item)
return rf
@classmethod
def moveallfiles(self, root_src_dir, root_dst_dir):
change_permissions = True
if sys.platform == "win32" or sys.platform == "darwin":
@ -462,9 +469,9 @@ class Updater(threading.Thread):
else:
try:
logging.getLogger('cps.web').debug("Delete file " + item_path)
log_from_thread("Delete file " + item_path)
# log_from_thread("Delete file " + item_path)
os.remove(item_path)
except Exception as e:
except Exception:
logging.getLogger('cps.web').debug("Could not remove:" + item_path)
shutil.rmtree(source, ignore_errors=True)

View File

@ -1,13 +1,15 @@
/**
* Created by SpeedProg on 05.04.2015.
*/
/* global Bloodhound */
/*
Takes a prefix, query typeahead callback, Bloodhound typeahead adapter
and returns the completions it gets from the bloodhound engine prefixed.
*/
function prefixed_source(prefix, query, cb, bh_adapter) {
bh_adapter(query, function(retArray){
function prefixedSource(prefix, query, cb, bhAdapter) {
bhAdapter(query, function(retArray){
var matches = [];
for (var i = 0; i < retArray.length; i++) {
var obj = {name : prefix + retArray[i].name};
@ -16,184 +18,161 @@ function prefixed_source(prefix, query, cb, bh_adapter) {
cb(matches);
});
}
function get_path(){
var jsFileLocation = $('script[src*=edit_books]').attr('src'); // the js file path
jsFileLocation = jsFileLocation.replace('/static/js/edit_books.js', ''); // the js folder path
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
return jsFileLocation;
}
var authors = new Bloodhound({
name: 'authors',
datumTokenizer: function(datum) {
name: "authors",
datumTokenizer(datum) {
return [datum.name];
},
queryTokenizer: Bloodhound.tokenizers.whitespace,
remote: {
url: get_path()+'/get_authors_json?q=%QUERY'
url: getPath()+"/get_authors_json?q=%QUERY"
}
});
function authors_source(query, cb) {
var bh_adapter = authors.ttAdapter();
var series = new Bloodhound({
name: "series",
datumTokenizer(datum) {
return [datum.name];
},
queryTokenizer(query) {
return [query];
},
remote: {
url: getPath()+"/get_series_json?q=",
replace(url, query) {
return url+encodeURIComponent(query);
}
}
});
var tokens = query.split("&");
var current_author = tokens[tokens.length-1].trim();
var tags = new Bloodhound({
name: "tags",
datumTokenizer(datum) {
return [datum.name];
},
queryTokenizer(query) {
var tokens = query.split(",");
tokens = [tokens[tokens.length-1].trim()];
return tokens;
},
remote: {
url: getPath()+"/get_tags_json?q=%QUERY"
}
});
var languages = new Bloodhound({
name: "languages",
datumTokenizer(datum) {
return [datum.name];
},
queryTokenizer(query) {
return [query];
},
remote: {
url: getPath()+"/get_languages_json?q=",
replace(url, query) {
return url+encodeURIComponent(query);
}
}
});
function sourceSplit(query, cb, split, source) {
var bhAdapter = source.ttAdapter();
var tokens = query.split(split);
var currentSource = tokens[tokens.length-1].trim();
tokens.splice(tokens.length-1, 1); // remove last element
var prefix = "";
var newSplit;
if (split === "&"){
newSplit = " " + split + " ";
}else{
newSplit = split + " ";
}
for (var i = 0; i < tokens.length; i++) {
var author = tokens[i].trim();
prefix += author + " & ";
prefix += tokens[i].trim() + newSplit;
}
prefixedSource(prefix, currentSource, cb, bhAdapter);
}
prefixed_source(prefix, current_author, cb, bh_adapter);
}
var promise = authors.initialize();
promise.done(function(){
var promiseAuthors = authors.initialize();
promiseAuthors.done(function(){
$("#bookAuthor").typeahead(
{
highlight: true, minLength: 1,
hint: true
}, {
name: 'authors', displayKey: 'name',
source: authors_source
name: "authors",
displayKey: "name",
source(query, cb){
return sourceSplit(query, cb, "&", authors); //sourceSplit //("&")
}
)
});
});
var series = new Bloodhound({
name: 'series',
datumTokenizer: function(datum) {
return [datum.name];
},
queryTokenizer: function(query) {
return [query];
},
remote: {
url: get_path()+'/get_series_json?q=',
replace: function(url, query) {
url_query = url+encodeURIComponent(query);
return url_query;
}
}
});
var promise = series.initialize();
promise.done(function(){
var promiseSeries = series.initialize();
promiseSeries.done(function(){
$("#series").typeahead(
{
highlight: true, minLength: 0,
hint: true
}, {
name: 'series', displayKey: 'name',
name: "series",
displayKey: "name",
source: series.ttAdapter()
}
)
);
});
var tags = new Bloodhound({
name: 'tags',
datumTokenizer: function(datum) {
return [datum.name];
},
queryTokenizer: function(query) {
tokens = query.split(",");
tokens = [tokens[tokens.length-1].trim()];
return tokens
},
remote: {
url: get_path()+'/get_tags_json?q=%QUERY'
}
});
function tag_source(query, cb) {
var bh_adapter = tags.ttAdapter();
var tokens = query.split(",");
var current_tag = tokens[tokens.length-1].trim();
tokens.splice(tokens.length-1, 1); // remove last element
var prefix = "";
for (var i = 0; i < tokens.length; i++) {
var tag = tokens[i].trim();
prefix += tag + ", ";
}
prefixed_source(prefix, current_tag, cb, bh_adapter);
}
var promise = tags.initialize();
promise.done(function(){
var promiseTags = tags.initialize();
promiseTags.done(function(){
$("#tags").typeahead(
{
highlight: true, minLength: 0,
hint: true
}, {
name: 'tags', displayKey: 'name',
source: tag_source
}
)
});
var languages = new Bloodhound({
name: 'languages',
datumTokenizer: function(datum) {
return [datum.name];
},
queryTokenizer: function(query) {
return [query];
},
remote: {
url: get_path()+'/get_languages_json?q=',
replace: function(url, query) {
url_query = url+encodeURIComponent(query);
return url_query;
}
name: "tags",
displayKey: "name",
source(query, cb){
return sourceSplit(query, cb, ",", tags);
}
});
});
function language_source(query, cb) {
var bh_adapter = languages.ttAdapter();
var tokens = query.split(",");
var current_language = tokens[tokens.length-1].trim();
tokens.splice(tokens.length-1, 1); // remove last element
var prefix = "";
for (var i = 0; i < tokens.length; i++) {
var tag = tokens[i].trim();
prefix += tag + ", ";
}
prefixed_source(prefix, current_language, cb, bh_adapter);
}
var promise = languages.initialize();
promise.done(function(){
var promiseLanguages = languages.initialize();
promiseLanguages.done(function(){
$("#languages").typeahead(
{
highlight: true, minLength: 0,
hint: true
}, {
name: 'languages', displayKey: 'name',
source: language_source
name: "languages",
displayKey: "name",
source(query, cb){
return sourceSplit(query, cb, ",", languages); //(",")
}
)
});
});
$('form').on('change input typeahead:selected', function(data){
form = $('form').serialize();
$.getJSON( get_path()+"/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'))) {
$(this).addClass('disabled');
$("form").on("change input typeahead:selected", function(data){
var form = $("form").serialize();
$.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"))) {
$(this).addClass("disabled");
}
}
else {
$(this).removeClass('disabled');
$(this).removeClass("disabled");
}
});
});

View File

@ -4,176 +4,182 @@
* 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 */
$(document).ready(function () {
var msg = i18n_msg;
var douban = 'https://api.douban.com';
var db_search = '/v2/book/search';
var db_get_info = '/v2/book/';
var db_get_info_by_isbn = '/v2/book/isbn/ ';
var db_done = false;
var msg = i18nMsg;
var douban = "https://api.douban.com";
var dbSearch = "/v2/book/search";
// var dbGetInfo = "/v2/book/";
// var db_get_info_by_isbn = "/v2/book/isbn/ ";
var dbDone = false;
var google = 'https://www.googleapis.com/';
var gg_search = '/books/v1/volumes';
var gg_get_info = '/books/v1/volumes/';
var gg_done = false;
var google = "https://www.googleapis.com/";
var ggSearch = "/books/v1/volumes";
// var gg_get_info = "/books/v1/volumes/";
var ggDone = false;
var db_results = [];
var gg_results = [];
var show_flag = 0;
String.prototype.replaceAll = function (s1, s2) {  
return this.replace(new RegExp(s1, "gm"), s2);  
var dbResults = [];
var ggResults = [];
var showFlag = 0;
String.prototype.replaceAll = function (s1, s2) {
return this.replace(new RegExp(s1, "gm"), s2);
};
function showResult () {
var book;
var i;
var bookHtml;
showFlag++;
if (showFlag === 1) {
$("#meta-info").html("<ul id=\"book-list\" class=\"media-list\"></ul>");
}
if (ggDone && dbDone) {
if (!ggResults && !dbResults) {
$("#meta-info").html("<p class=\"text-danger\">"+ msg.no_result +"</p>");
return;
}
}
if (ggDone && ggResults.length > 0) {
for (i = 0; i < ggResults.length; i++) {
book = ggResults[i];
var bookCover;
if (book.volumeInfo.imageLinks) {
bookCover = book.volumeInfo.imageLinks.thumbnail;
} else {
bookCover = "/static/generic_cover.jpg";
}
bookHtml = "<li class=\"media\">" +
"<img class=\"pull-left img-responsive\" data-toggle=\"modal\" data-target=\"#metaModal\" src=\"" +
bookCover + "\" alt=\"Cover\" style=\"width:100px;height:150px\" onclick='javascript:getMeta(\"google\"," +
i + ")\\>\"" +
"<div class=\"media-body\">" +
"<h4 class=\"media-heading\"><a href=\"https://books.google.com/books?id=" +
book.id + "\" target=\"_blank\">" + book.volumeInfo.title + "</a></h4>" +
"<p>"+ msg.author +"" + book.volumeInfo.authors + "</p>" +
"<p>"+ msg.publisher + "" + book.volumeInfo.publisher + "</p>" +
"<p>"+ msg.description + ":" + book.volumeInfo.description + "</p>" +
"<p>"+ msg.source + ":<a href=\"https://books.google.com\" target=\"_blank\">Google Books</a></p>" +
"</div>" +
"</li>";
$("#book-list").append(bookHtml);
}
ggDone = false;
}
if (dbDone && dbResults.length > 0) {
for (i = 0; i < dbResults.length; i++) {
book = dbResults[i];
bookHtml = "<li class=\"media\">" +
"<img class=\"pull-left img-responsive\" data-toggle=\"modal\" data-target=\"#metaModal\" src=\"" +
book.image + "\" alt=\"Cover\" style=\"width:100px;height: 150px\" onclick='javascript:getMeta(\"douban\"," +
i + ")\\'>" +
"<div class=\"media-body\">" +
"<h4 class=\"media-heading\"><a href=\"https://book.douban.com/subject/" +
book.id + "\" target=\"_blank\">" + book.title + "</a></h4>" +
"<p>" + msg.author + "" + book.author + "</p>" +
"<p>" + msg.publisher + "" + book.publisher + "</p>" +
"<p>" + msg.description + ":" + book.summary + "</p>" +
"<p>" + msg.source + ":<a href=\"https://book.douban.com\" target=\"_blank\">Douban Books</a></p>" +
"</div>" +
"</li>";
$("#book-list").append(bookHtml);
}
dbDone = false;
}
}
gg_search_book = function (title) {
title = title.replaceAll(/\s+/, '+');
var url = google + gg_search + '?q=' + title;
function ggSearchBook (title) {
title = title.replaceAll(/\s+/, "+");
var url = google + ggSearch + "?q=" + title;
$.ajax({
url: url,
url,
type: "GET",
dataType: "jsonp",
jsonp: 'callback',
success: function (data) {
gg_results = data.items;
jsonp: "callback",
success (data) {
ggResults = data.items;
},
complete: function () {
gg_done = true;
show_result();
complete () {
ggDone = true;
showResult();
}
});
}
get_meta = function (source, id) {
function getMeta (source, id) {
var meta;
if (source == 'google') {;
meta = gg_results[id];
$('#description').val(meta.volumeInfo.description);
$('#bookAuthor').val(meta.volumeInfo.authors.join(' & '));
$('#book_title').val(meta.volumeInfo.title);
var tags;
if (source === "google") {
meta = ggResults[id];
$("#description").val(meta.volumeInfo.description);
$("#bookAuthor").val(meta.volumeInfo.authors.join(" & "));
$("#book_title").val(meta.volumeInfo.title);
if (meta.volumeInfo.categories) {
var tags = meta.volumeInfo.categories.join(',');
$('#tags').val(tags);
tags = meta.volumeInfo.categories.join(",");
$("#tags").val(tags);
}
if (meta.volumeInfo.averageRating) {
$('#rating').val(Math.round(meta.volumeInfo.averageRating));
$("#rating").val(Math.round(meta.volumeInfo.averageRating));
}
return;
}
if (source == 'douban') {
meta = db_results[id];
$('#description').val(meta.summary);
$('#bookAuthor').val(meta.author.join(' & '));
$('#book_title').val(meta.title);
var tags = '';
if (source === "douban") {
meta = dbResults[id];
$("#description").val(meta.summary);
$("#bookAuthor").val(meta.author.join(" & "));
$("#book_title").val(meta.title);
tags = "";
for (var i = 0; i < meta.tags.length; i++) {
tags = tags + meta.tags[i].title + ',';
tags = tags + meta.tags[i].title + ",";
}
$('#tags').val(tags);
$('#rating').val(Math.round(meta.rating.average / 2));
$("#tags").val(tags);
$("#rating").val(Math.round(meta.rating.average / 2));
return;
}
}
do_search = function (keyword) {
show_flag = 0;
$('#meta-info').text(msg.loading);
var keyword = $('#keyword').val();
if (keyword) {
db_search_book(keyword);
gg_search_book(keyword);
}
}
db_search_book = function (title) {
var url = douban + db_search + '?q=' + title + '&fields=all&count=10';
function dbSearchBook (title) {
var url = douban + dbSearch + "?q=" + title + "&fields=all&count=10";
$.ajax({
url: url,
url,
type: "GET",
dataType: "jsonp",
jsonp: 'callback',
success: function (data) {
db_results = data.books;
jsonp: "callback",
success (data) {
dbResults = data.books;
},
error: function () {
$('#meta-info').html('<p class="text-danger">'+ msg.search_error+'!</p>');
error () {
$("#meta-info").html("<p class=\"text-danger\">"+ msg.search_error+"!</p>");
},
complete: function () {
db_done = true;
show_result();
complete () {
dbDone = true;
showResult();
}
});
}
show_result = function () {
show_flag++;
if (show_flag == 1) {
$('#meta-info').html('<ul id="book-list" class="media-list"></ul>');
}
if (gg_done && db_done) {
if (!gg_results && !db_results) {
$('#meta-info').html('<p class="text-danger">'+ msg.no_result +'</p>');
return;
}
}
if (gg_done && gg_results.length > 0) {
for (var i = 0; i < gg_results.length; i++) {
var book = gg_results[i];
var book_cover;
if (book.volumeInfo.imageLinks) {
book_cover = book.volumeInfo.imageLinks.thumbnail;
} else {
book_cover = '/static/generic_cover.jpg';
}
var book_html = '<li class="media">' +
'<img class="pull-left img-responsive" data-toggle="modal" data-target="#metaModal" src="' +
book_cover + '" alt="Cover" style="width:100px;height:150px" onclick=\'javascript:get_meta("google",' +
i + ')\'>' +
'<div class="media-body">' +
'<h4 class="media-heading"><a href="https://books.google.com/books?id=' +
book.id + '" target="_blank">' + book.volumeInfo.title + '</a></h4>' +
'<p>'+ msg.author +'' + book.volumeInfo.authors + '</p>' +
'<p>'+ msg.publisher + '' + book.volumeInfo.publisher + '</p>' +
'<p>'+ msg.description + ':' + book.volumeInfo.description + '</p>' +
'<p>'+ msg.source + ':<a href="https://books.google.com" target="_blank">Google Books</a></p>' +
'</div>' +
'</li>';
$("#book-list").append(book_html);
}
gg_done = false;
}
if (db_done && db_results.length > 0) {
for (var i = 0; i < db_results.length; i++) {
var book = db_results[i];
var book_html = '<li class="media">' +
'<img class="pull-left img-responsive" data-toggle="modal" data-target="#metaModal" src="' +
book.image + '" alt="Cover" style="width:100px;height: 150px" onclick=\'javascript:get_meta("douban",' +
i + ')\'>' +
'<div class="media-body">' +
'<h4 class="media-heading"><a href="https://book.douban.com/subject/' +
book.id + '" target="_blank">' + book.title + '</a></h4>' +
'<p>' + msg.author + '' + book.author + '</p>' +
'<p>' + msg.publisher + '' + book.publisher + '</p>' +
'<p>' + msg.description + ':' + book.summary + '</p>' +
'<p>' + msg.source + ':<a href="https://book.douban.com" target="_blank">Douban Books</a></p>' +
'</div>' +
'</li>';
$("#book-list").append(book_html);
}
db_done = false;
}
}
$('#do-search').click(function () {
var keyword = $('#keyword').val();
function doSearch (keyword) {
showFlag = 0;
$("#meta-info").text(msg.loading);
// var keyword = $("#keyword").val();
if (keyword) {
do_search(keyword);
dbSearchBook(keyword);
ggSearchBook(keyword);
}
}
$("#do-search").click(function () {
var keyword = $("#keyword").val();
if (keyword) {
doSearch(keyword);
}
});
$('#get_meta').click(function () {
var book_title = $('#book_title').val();
if (book_title) {
$('#keyword').val(book_title);
do_search(book_title);
$("#get_meta").click(function () {
var bookTitle = $("#book_title").val();
if (bookTitle) {
$("#keyword").val(bookTitle);
doSearch(bookTitle);
}
});

View File

@ -3,13 +3,47 @@ var updateTimerID;
var updateText;
$(function() {
$('.discover .row').isotope({
function restartTimer() {
$("#spinner").addClass("hidden");
$("#RestartDialog").modal("hide");
}
function updateTimer() {
$.ajax({
dataType: "json",
url: window.location.pathname+"/../../get_updater_status",
success(data) {
// console.log(data.status);
$("#UpdateprogressDialog #Updatecontent").html(updateText[data.status]);
if (data.status >6){
clearInterval(updateTimerID);
$("#spinner2").hide();
$("#UpdateprogressDialog #updateFinished").removeClass("hidden");
$("#check_for_update").removeClass("hidden");
$("#perform_update").addClass("hidden");
}
},
error() {
// console.log('Done');
clearInterval(updateTimerID);
$("#spinner2").hide();
$("#UpdateprogressDialog #Updatecontent").html(updateText[7]);
$("#UpdateprogressDialog #updateFinished").removeClass("hidden");
$("#check_for_update").removeClass("hidden");
$("#perform_update").addClass("hidden");
},
timeout:2000
});
}
$(".discover .row").isotope({
// options
itemSelector : '.book',
layoutMode : 'fitRows'
itemSelector : ".book",
layoutMode : "fitRows"
});
$('.load-more .row').infinitescroll({
$(".load-more .row").infinitescroll({
debug: false,
navSelector : ".pagination",
// selector for the paged navigation (it will be hidden)
@ -20,109 +54,74 @@ $(function() {
extraScrollPx: 300,
// selector for all items you'll retrieve
}, function(data){
$('.load-more .row').isotope( 'appended', $(data), null );
$(".load-more .row").isotope( "appended", $(data), null );
});
$('#sendbtn').click(function(){
$("#sendbtn").click(function(){
var $this = $(this);
$this.text('Please wait...');
$this.addClass('disabled');
$this.text("Please wait...");
$this.addClass("disabled");
});
$("#restart").click(function() {
$.ajax({
dataType: 'json',
dataType: "json",
url: window.location.pathname+"/../../shutdown",
data: {"parameter":0},
success: function(data) {
$('#spinner').show();
success(data) {
$("#spinner").show();
displaytext=data.text;
setTimeout(restartTimer, 3000);}
});
});
$("#shutdown").click(function() {
$.ajax({
dataType: 'json',
dataType: "json",
url: window.location.pathname+"/../../shutdown",
data: {"parameter":1},
success: function(data) {
success(data) {
return alert(data.text);}
});
});
$("#check_for_update").click(function() {
var button_text = $("#check_for_update").html();
$("#check_for_update").html('...');
var buttonText = $("#check_for_update").html();
$("#check_for_update").html("...");
$.ajax({
dataType: 'json',
dataType: "json",
url: window.location.pathname+"/../../get_update_status",
success: function(data) {
$("#check_for_update").html(button_text);
if (data.status == true) {
$("#check_for_update").addClass('hidden');
$("#perform_update").removeClass('hidden');
$("#update_info").removeClass('hidden');
$("#update_info").find('span').html(data.commit);
success(data) {
$("#check_for_update").html(buttonText);
if (data.status === true) {
$("#check_for_update").addClass("hidden");
$("#perform_update").removeClass("hidden");
$("#update_info").removeClass("hidden");
$("#update_info").find("span").html(data.commit);
}
}
});
});
$("#restart_database").click(function() {
$.ajax({
dataType: 'json',
dataType: "json",
url: window.location.pathname+"/../../shutdown",
data: {"parameter":2}
});
});
$("#perform_update").click(function() {
$('#spinner2').show();
$("#spinner2").show();
$.ajax({
type: "POST",
dataType: 'json',
dataType: "json",
data: { start: "True"},
url: window.location.pathname+"/../../get_updater_status",
success: function(data) {
updateText=data.text
success(data) {
updateText=data.text;
$("#UpdateprogressDialog #Updatecontent").html(updateText[data.status]);
console.log(data.status);
// console.log(data.status);
updateTimerID=setInterval(updateTimer, 2000);}
});
});
});
function restartTimer() {
$('#spinner').hide();
$('#RestartDialog').modal('hide');
}
function updateTimer() {
$.ajax({
dataType: 'json',
url: window.location.pathname+"/../../get_updater_status",
success: function(data) {
console.log(data.status);
$("#UpdateprogressDialog #Updatecontent").html(updateText[data.status]);
if (data.status >6){
clearInterval(updateTimerID);
$('#spinner2').hide();
$('#UpdateprogressDialog #updateFinished').removeClass('hidden');
$("#check_for_update").removeClass('hidden');
$("#perform_update").addClass('hidden');
}
},
error: function() {
console.log('Done');
clearInterval(updateTimerID);
$('#spinner2').hide();
$("#UpdateprogressDialog #Updatecontent").html(updateText[7]);
$('#UpdateprogressDialog #updateFinished').removeClass('hidden');
$("#check_for_update").removeClass('hidden');
$("#perform_update").addClass('hidden');
},
timeout:2000
});
}
$(window).resize(function(event) {
$('.discover .row').isotope('reLayout');
$(".discover .row").isotope("reLayout");
});
});

View File

@ -1,4 +1,6 @@
Sortable.create(sortTrue, {
/* global Sortable,sortTrue */
var sortable = Sortable.create(sortTrue, {
group: "sorting",
sort: true
});
@ -9,7 +11,7 @@ function sendData(path){
var maxElements;
var tmp=[];
elements=Sortable.utils.find(sortTrue,"div");
elements=sortable.utils.find(sortTrue,"div");
maxElements=elements.length;
var form = document.createElement("form");

View File

@ -106,7 +106,7 @@
</div>
<a href="#" id="get_meta" class="btn btn-default" data-toggle="modal" data-target="#metaModal">{{_('Get metadata')}}</a>
<button type="submit" class="btn btn-default">{{_('Submit')}}</button>
<a href="{{ url_for('show_book',id=book.id) }}" class="btn btn-default">{{_('Back')}}</a>
<a href="{{ url_for('show_book', book_id=book.id) }}" class="btn btn-default">{{_('Back')}}</a>
</form>
</div>
{% endif %}
@ -138,7 +138,7 @@
{% block js %}
<script>
var i18n_msg = {
var i18nMsg = {
'loading': {{_('Loading...')|safe|tojson}},
'search_error': {{_('Search error!')|safe|tojson}},
'no_result': {{_('No Result! Please try anonther keyword.')|safe|tojson}},

View File

@ -15,7 +15,7 @@
<h2>{{entry.title}}</h2>
<p class="author">
{% for author in entry.authors %}
<a href="{{url_for('author', id=author.id ) }}">{{author.name}}</a>
<a href="{{url_for('author', book_id=author.id ) }}">{{author.name}}</a>
{% if not loop.last %}
&amp;
{% endif %}
@ -37,7 +37,7 @@
{% endif %}
{% if entry.series|length > 0 %}
<p>{{_('Book')}} {{entry.series_index}} {{_('of')}} <a href="{{url_for('series', id=entry.series[0].id)}}">{{entry.series[0].name}}</a></p>
<p>{{_('Book')}} {{entry.series_index}} {{_('of')}} <a href="{{url_for('series', book_id=entry.series[0].id)}}">{{entry.series[0].name}}</a></p>
{% endif %}
{% if entry.languages.__len__() > 0 %}
@ -65,7 +65,7 @@
<span class="glyphicon glyphicon-tags"></span>
{% for tag in entry.tags %}
<a href="{{ url_for('category', id=tag.id) }}" class="btn btn-xs btn-info" role="button">{{tag.name}}</a>
<a href="{{ url_for('category', book_id=tag.id) }}" class="btn btn-xs btn-info" role="button">{{tag.name}}</a>
{%endfor%}
</div>
</p>
@ -110,7 +110,7 @@
{% if not g.user.is_anonymous() %}
<p>
<div class="custom_columns" id="have_read_container">
<form id="have_read_form" action="{{ url_for('toggle_read', id=entry.id)}}" method="POST") >
<form id="have_read_form" action="{{ url_for('toggle_read', book_id=entry.id)}}" method="POST") >
<input id="have_read_cb" type="checkbox" {% if have_read %}checked{% endif %} >
<label for="have_read_cb">{{_('Read')}}</label>
</form>
@ -136,7 +136,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, 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}}</a></li>
{%endfor%}
</ul>
</div>
@ -154,7 +154,7 @@
<ul class="dropdown-menu" aria-labelledby="btnGroupDrop2">
{% for format in entry.data %}
{%if format.format|lower == 'epub' or format.format|lower == 'txt' or format.format|lower == 'pdf' or format.format|lower == 'cbr' or format.format|lower == 'cbt' or format.format|lower == 'cbz' %}
<li><a target="_blank" href="{{ url_for('read_book', book_id=entry.id, format=format.format|lower) }}">{{format.format}}</a></li>
<li><a target="_blank" href="{{ url_for('read_book', book_id=entry.id, book_format=format.format|lower) }}">{{format.format}}</a></li>
{% endif %}
{%endfor%}
</ul>

View File

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

View File

@ -56,7 +56,7 @@
<link type="image/jpeg" href="{{url_for('feed_get_cover', book_id=entry.id)}}" rel="http://opds-spec.org/image/thumbnail"/>
{% endif %}
{% for format in entry.data %}
<link rel="http://opds-spec.org/acquisition" href="{{ url_for('get_opds_download_link', book_id=entry.id, format=format.format|lower)}}"
<link rel="http://opds-spec.org/acquisition" href="{{ url_for('get_opds_download_link', book_id=entry.id, book_format=format.format|lower)}}"
length="{{format.uncompressed_size}}" mtime="{{entry.timestamp}}" type="{{format.format|lower|mimetype}}"/>
{% endfor %}
</entry>
@ -65,9 +65,9 @@
{% for entry in listelements %}
<entry>
<title>{{entry.name}}</title>
<id>{{ url_for(folder, id=entry.id) }}</id>
<link type="application/atom+xml;profile=opds-catalog;type=feed;kind=acquisition" href="{{url_for(folder, id=entry.id)}}"/>
<link type="application/atom+xml" href="{{url_for(folder, id=entry.id)}}" rel="subsection"/>
<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"/>
</entry>
{% endfor %}
</feed>

View File

@ -8,7 +8,7 @@
{% for entry in random %}
<div id="books_rand" class="col-sm-3 col-lg-2 col-xs-6 book">
<div class="cover">
<a href="{{ url_for('show_book', id=entry.id) }}">
<a href="{{ url_for('show_book', book_id=entry.id) }}">
{% if entry.has_cover %}
<img src="{{ url_for('get_cover', cover_path=entry.path.replace('\\','/')) }}" />
{% else %}
@ -18,7 +18,7 @@
</div>
<div class="meta">
<p class="title">{{entry.title|shortentitle}}</p>
<p class="author"><a href="{{url_for('author', id=entry.authors[0].id) }}">{{entry.authors[0].name}}</a></p>
<p class="author"><a href="{{url_for('author', book_id=entry.authors[0].id) }}">{{entry.authors[0].name}}</a></p>
{% if entry.ratings.__len__() > 0 %}
<div class="rating">
{% for number in range((entry.ratings[0].rating/2)|int(2)) %}
@ -44,7 +44,7 @@
{% for entry in entries %}
<div id="books" class="col-sm-3 col-lg-2 col-xs-6 book">
<div class="cover">
<a href="{{ url_for('show_book', id=entry.id) }}">
<a href="{{ url_for('show_book', book_id=entry.id) }}">
{% if entry.has_cover %}
<img src="{{ url_for('get_cover', cover_path=entry.path.replace('\\','/')) }}" />
{% else %}
@ -56,7 +56,7 @@
<p class="title">{{entry.title|shortentitle}}</p>
<p class="author">
{% for author in entry.authors %}
<a href="{{url_for('author', id=author.id) }}">{{author.name}}</a>
<a href="{{url_for('author', book_id=author.id) }}">{{author.name}}</a>
{% if not loop.last %}
&amp;
{% endif %}

View File

@ -36,7 +36,7 @@
"timestamp": "{{entry.timestamp}}",
"thumbnail": "{{url_for('feed_get_cover', book_id=entry.id)}}",
"main_format": {
"{{entry.data[0].format|lower}}": "{{ url_for('get_opds_download_link', book_id=entry.id, format=entry.data[0].format|lower)}}"
"{{entry.data[0].format|lower}}": "{{ url_for('get_opds_download_link', book_id=entry.id, book_format=entry.data[0].format|lower)}}"
},
"rating":{% if entry.ratings.__len__() > 0 %} "{{entry.ratings[0].rating}}.0"{% else %}0.0{% endif %},
"authors": [
@ -47,7 +47,7 @@
"other_formats": {
{% if entry.data.__len__() > 1 %}
{% for format in entry.data[1:] %}
"{{format.format|lower}}": "{{ url_for('get_opds_download_link', book_id=entry.id, format=format.format|lower)}}"{% if not loop.last %},{% endif %}
"{{format.format|lower}}": "{{ url_for('get_opds_download_link', book_id=entry.id, book_format=format.format|lower)}}"{% if not loop.last %},{% endif %}
{% endfor %}
{% endif %} },
"title_sort": "{{entry.sort}}"

View File

@ -10,7 +10,7 @@
{% endif %}
<div class="row">
<div class="col-xs-1" align="left"><span class="badge">{{entry.count}}</span></div>
<div class="col-xs-6"><a id="list_{{loop.index0}}" href="{{url_for(folder, id=entry[0].id )}}">{{entry[0].name}}</a></div>
<div class="col-xs-6"><a id="list_{{loop.index0}}" href="{{url_for(folder, book_id=entry[0].id )}}">{{entry[0].name}}</a></div>
</div>
{% endfor %}
</div>

View File

@ -15,7 +15,7 @@
<div class="col-sm-3 col-lg-2 col-xs-6 book">
<div class="cover">
{% if entry.has_cover is defined %}
<a href="{{ url_for('show_book', id=entry.id) }}">
<a href="{{ url_for('show_book', book_id=entry.id) }}">
<img src="{{ url_for('get_cover', cover_path=entry.path.replace('\\','/')) }}" />
</a>
{% 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', id=author.id ) }}">{{author.name}}</a>
<a href="{{url_for('author', book_id=author.id ) }}">{{author.name}}</a>
{% if not loop.last %}
&amp;
{% endif %}

View File

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

View File

@ -131,7 +131,7 @@
<h2>{{_('Recent Downloads')}}</h2>
{% for entry in downloads %}
<div class="col-sm-2">
<a class="pull-left" href="{{ url_for('show_book', id=entry.id) }}">
<a class="pull-left" href="{{ url_for('show_book', book_id=entry.id) }}">
<img class="media-object" width="100" src="{{ url_for('get_cover', cover_path=entry.path.replace('\\','/')) }}" alt="...">
</a>
</div>

View File

@ -15,15 +15,16 @@ msgstr ""
"Project-Id-Version: Calibre-web\n"
"Report-Msgid-Bugs-To: https://github.com/janeczku/calibre-web\n"
"POT-Creation-Date: 2017-03-19 19:20+0100\n"
"PO-Revision-Date: 2016-11-13 18:35+0100\n"
"PO-Revision-Date: 2017-04-04 15:09+0200\n"
"Last-Translator: Juan F. Villa <juan.villa@paisdelconocimiento.org>\n"
"Language: es\n"
"Language-Team: \n"
"Plural-Forms: nplurals=2; plural=(n != 1)\n"
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=utf-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Generated-By: Babel 2.3.4\n"
"X-Generator: Poedit 1.8.7.1\n"
#: cps/book_formats.py:113 cps/book_formats.py:117 cps/web.py:1244
msgid "not installed"
@ -56,35 +57,35 @@ msgstr "No fue posible convertir de epub a mobi"
#: cps/ub.py:488
msgid "Guest"
msgstr ""
msgstr "Invitado"
#: cps/web.py:904
msgid "Requesting update package"
msgstr ""
msgstr "Solicitando paquete de actualización"
#: cps/web.py:905
msgid "Downloading update package"
msgstr ""
msgstr "Descargando paquete de actualización"
#: cps/web.py:906
msgid "Unzipping update package"
msgstr ""
msgstr "Descomprimiendo paquete de actualización"
#: cps/web.py:907
msgid "Files are replaced"
msgstr ""
msgstr "Ficheros sustituidos"
#: cps/web.py:908
msgid "Database connections are closed"
msgstr ""
msgstr "Los conexiones de base datos están cerradas"
#: cps/web.py:909
msgid "Server is stopped"
msgstr ""
msgstr "El servidor está detenido"
#: cps/web.py:910
msgid "Update finished, please press okay and reload page"
msgstr ""
msgstr "Actualización finalizada. Por favor, pulse OK y recargue la página"
#: cps/web.py:983
msgid "Latest Books"
@ -92,33 +93,33 @@ msgstr "Libros recientes"
#: cps/web.py:1014
msgid "Hot Books (most downloaded)"
msgstr "Libros Populares (los mas descargados)"
msgstr "Libros populares (los mas descargados)"
#: cps/web.py:1024
msgid "Best rated books"
msgstr ""
msgstr "Libros mejor valorados"
#: cps/templates/index.xml:36 cps/web.py:1033
msgid "Random Books"
msgstr "Libros al Azar"
msgstr "Libros al azar"
#: cps/web.py:1046
msgid "Author list"
msgstr "Lista de Autores"
msgstr "Lista de autores"
#: cps/web.py:1057
#, python-format
msgid "Author: %(name)s"
msgstr ""
msgstr "Autor:%(name)s"
#: cps/web.py:1059 cps/web.py:1087 cps/web.py:1221 cps/web.py:1626
#: cps/web.py:2579
msgid "Error opening eBook. File does not exist or file is not accessible:"
msgstr "Error en apertura del Objeto. El archivo no existe o no es accesible"
msgstr "Error en la apertura del eBook. El archivo no existe o no es accesible:"
#: cps/templates/index.xml:71 cps/web.py:1073
msgid "Series list"
msgstr "lista de Series"
msgstr "Lista de series"
#: cps/web.py:1085
#, python-format
@ -136,12 +137,12 @@ msgstr "Lenguaje: %(name)s"
#: cps/templates/index.xml:64 cps/web.py:1146
msgid "Category list"
msgstr "Lista de Categorias"
msgstr "Lista de categorias"
#: cps/web.py:1158
#, python-format
msgid "Category: %(name)s"
msgstr "Categoria : %(name)s"
msgstr "Categoría : %(name)s"
#: cps/web.py:1267
msgid "Statistics"
@ -149,39 +150,39 @@ msgstr "Estadisticas"
#: cps/web.py:1375
msgid "Server restarted, please reload page"
msgstr ""
msgstr "Servidor reiniciado. Por favor, recargue la página"
#: cps/web.py:1377
msgid "Performing shutdown of server, please close window"
msgstr ""
msgstr "Servidor en proceso de apagado. Por favor, cierre la ventana."
#: cps/web.py:1392
msgid "Update done"
msgstr ""
msgstr "Actualización realizada"
#: cps/web.py:1470 cps/web.py:1483
msgid "search"
msgstr ""
msgstr "búsqueda"
#: cps/web.py:1602 cps/web.py:1609 cps/web.py:1616 cps/web.py:1623
msgid "Read a Book"
msgstr "Leer un Libro"
msgstr "Leer un libro"
#: cps/web.py:1676 cps/web.py:2152
msgid "Please fill out all fields!"
msgstr "Por favor llenar todos los campos!"
msgstr "¡Por favor completar todos los campos!"
#: cps/web.py:1677 cps/web.py:1693 cps/web.py:1698 cps/web.py:1700
msgid "register"
msgstr "Registrarse"
msgstr "registrarse"
#: cps/web.py:1692
msgid "An unknown error occured. Please try again later."
msgstr "Ocurrio un error. Intentar de nuevo mas tarde."
msgstr "Error desconocido. Por favor, inténtelo de nuevo mas tarde."
#: cps/web.py:1697
msgid "This username or email address is already in use."
msgstr "Usuario o direccion de correo en uso."
msgstr "Usuario o dirección de correo en uso."
#: cps/web.py:1715
#, python-format
@ -194,7 +195,7 @@ msgstr "Usuario o contraseña invalido"
#: cps/web.py:1722
msgid "login"
msgstr "Iniciar Sesion"
msgstr "Iniciar sesión"
#: cps/web.py:1739
msgid "Please configure the SMTP mail settings first..."
@ -236,20 +237,20 @@ msgstr "Estante %(title)s creado"
#: cps/web.py:1819 cps/web.py:1847
msgid "There was an error"
msgstr "Hemos tenido un error"
msgstr "Ha sucedido un error"
#: cps/web.py:1820 cps/web.py:1822
msgid "create a shelf"
msgstr "Crear un Estante"
msgstr "crear un estante"
#: cps/web.py:1845
#, python-format
msgid "Shelf %(title)s changed"
msgstr ""
msgstr "Estante %(title)s cambiado"
#: cps/web.py:1848 cps/web.py:1850
msgid "Edit a shelf"
msgstr ""
msgstr "Editar un estante"
#: cps/web.py:1868
#, python-format
@ -264,11 +265,11 @@ msgstr "Estante: '%(name)s'"
#: cps/web.py:1921
#, python-format
msgid "Change order of Shelf: '%(name)s'"
msgstr ""
msgstr "Cambiar orden del estante: '%(name)s'"
#: cps/web.py:1985
msgid "Found an existing account for this email address."
msgstr "Existe una cuenta vinculada a esta cuenta de correo."
msgstr "Existe una cuenta vinculada a esta dirección de correo."
#: cps/web.py:1987 cps/web.py:1991
#, python-format
@ -281,19 +282,19 @@ msgstr "Perfil actualizado"
#: cps/web.py:2002
msgid "Admin page"
msgstr ""
msgstr "Página de administración"
#: cps/web.py:2106
msgid "Calibre-web configuration updated"
msgstr ""
msgstr "Configuración de Calibre-web actualizada"
#: cps/web.py:2113 cps/web.py:2119 cps/web.py:2133
msgid "Basic Configuration"
msgstr ""
msgstr "Configuración básica"
#: cps/web.py:2117
msgid "DB location is not valid, please enter correct path"
msgstr ""
msgstr "Localicación de la BD inválida. Por favor, introduzca la ruta correcta."
#: cps/templates/admin.html:34 cps/web.py:2154 cps/web.py:2202
msgid "Add new user"
@ -306,11 +307,11 @@ msgstr "Usuario '%(user)s' creado"
#: cps/web.py:2198
msgid "Found an existing account for this email address or nickname."
msgstr "Se ha encontrado una cuenta vinculada a esta cuenta de correo o usuario."
msgstr "Se ha encontrado una cuenta vinculada a esta dirección de correo o nombre de usuario."
#: cps/web.py:2220
msgid "Mail settings updated"
msgstr "Parametros de correo actualizados"
msgstr "Parámetros de correo actualizados"
#: cps/web.py:2227
#, python-format
@ -324,7 +325,7 @@ msgstr "Error al realizar envio de prueba a E-Mail: %(res)s"
#: cps/web.py:2234
msgid "E-Mail settings updated"
msgstr ""
msgstr "Ajustes de correo electrónico actualizados"
#: cps/web.py:2235
msgid "Edit mail settings"
@ -338,11 +339,11 @@ msgstr "Usuario '%(nick)s' borrado"
#: cps/web.py:2349
#, python-format
msgid "User '%(nick)s' updated"
msgstr "Usuario '%(nick)s' Actualizado"
msgstr "Usuario '%(nick)s' actualizado"
#: cps/web.py:2352
msgid "An unknown error occured."
msgstr "Oups ! Error inesperado."
msgstr "Error inesperado."
#: cps/web.py:2355
#, python-format
@ -351,16 +352,16 @@ msgstr "Editar Usuario %(nick)s"
#: cps/web.py:2574 cps/web.py:2577 cps/web.py:2689
msgid "edit metadata"
msgstr ""
msgstr "editar metainformación"
#: cps/web.py:2598
#, python-format
msgid "File extension \"%s\" is not allowed to be uploaded to this server"
msgstr ""
msgstr "No se permite subir archivos con la extensión \"%s\" a este servidor"
#: cps/web.py:2604
msgid "File to be uploaded must have an extension"
msgstr ""
msgstr "El archivo a subir debe tener una extensión"
#: cps/web.py:2621
#, python-format
@ -399,7 +400,7 @@ msgstr "DLS"
#: cps/templates/admin.html:12 cps/templates/layout.html:85
msgid "Admin"
msgstr "Administracion"
msgstr "Administración"
#: cps/templates/admin.html:13 cps/templates/detail.html:134
msgid "Download"
@ -419,7 +420,7 @@ msgstr "Clave"
#: cps/templates/admin.html:35
msgid "SMTP mail settings"
msgstr "Parametros smtp del correo"
msgstr "Parámetros smtp del correo"
#: cps/templates/admin.html:38 cps/templates/email_edit.html:7
msgid "SMTP hostname"
@ -451,76 +452,76 @@ msgstr "Cambiar parametros smtp"
#: cps/templates/admin.html:57 cps/templates/admin.html:77
msgid "Configuration"
msgstr ""
msgstr "Configuración"
#: cps/templates/admin.html:60
msgid "Calibre DB dir"
msgstr ""
msgstr "Dir DB Calibre"
#: cps/templates/admin.html:61 cps/templates/config_edit.html:76
msgid "Log Level"
msgstr ""
msgstr "Nivel de registro"
#: cps/templates/admin.html:62
msgid "Port"
msgstr ""
msgstr "Puerto"
#: cps/templates/admin.html:63 cps/templates/config_edit.html:60
msgid "Books per page"
msgstr ""
msgstr "Libros por página"
#: cps/templates/admin.html:64
msgid "Uploading"
msgstr ""
msgstr "Subiendo"
#: cps/templates/admin.html:65
msgid "Public registration"
msgstr ""
msgstr "Registro público"
#: cps/templates/admin.html:66
msgid "Anonymous browsing"
msgstr ""
msgstr "Navegación anónima"
#: cps/templates/admin.html:78
msgid "Administration"
msgstr ""
msgstr "Administración"
#: cps/templates/admin.html:80
msgid "Current commit timestamp"
msgstr ""
msgstr "Marca temporal del commit actual"
#: cps/templates/admin.html:81
msgid "Newest commit timestamp"
msgstr ""
msgstr "Marca temporal del commit más reciente"
#: cps/templates/admin.html:83
msgid "Reconnect to Calibre DB"
msgstr ""
msgstr "Reconectar la BD Calibre"
#: cps/templates/admin.html:84
msgid "Restart Calibre-web"
msgstr ""
msgstr "Reinicial Calibre-web"
#: cps/templates/admin.html:85
msgid "Stop Calibre-web"
msgstr ""
msgstr "Detener Calibre-web"
#: cps/templates/admin.html:86
msgid "Check for update"
msgstr ""
msgstr "Buscar actualizaciones"
#: cps/templates/admin.html:87
msgid "Perform Update"
msgstr ""
msgstr "Actualizar"
#: cps/templates/admin.html:97
msgid "Do you really want to restart Calibre-web?"
msgstr ""
msgstr "¿Seguro que quiere reiniciar Calibre-web?"
#: cps/templates/admin.html:102 cps/templates/admin.html:116
#: cps/templates/admin.html:137
msgid "Ok"
msgstr ""
msgstr "Ok"
#: cps/templates/admin.html:103 cps/templates/admin.html:117
#: cps/templates/book_edit.html:109 cps/templates/config_edit.html:119
@ -531,11 +532,11 @@ msgstr "Regresar"
#: cps/templates/admin.html:115
msgid "Do you really want to stop Calibre-web?"
msgstr ""
msgstr "¿Seguro que quiere detener Calibre-web?"
#: cps/templates/admin.html:128
msgid "Updating, please do not reload page"
msgstr ""
msgstr "Actualizando. Por favor, no recargue la página."
#: cps/templates/book_edit.html:16 cps/templates/search_form.html:6
msgid "Book Title"
@ -589,7 +590,7 @@ msgstr "Ver libro tras la edicion"
#: cps/templates/book_edit.html:107 cps/templates/book_edit.html:118
msgid "Get metadata"
msgstr ""
msgstr "Obtener metainformación"
#: cps/templates/book_edit.html:108 cps/templates/config_edit.html:117
#: cps/templates/login.html:19 cps/templates/search_form.html:79
@ -599,76 +600,76 @@ msgstr "Enviar"
#: cps/templates/book_edit.html:121
msgid "Keyword"
msgstr ""
msgstr "Palabra clave"
#: cps/templates/book_edit.html:122
msgid " Search keyword "
msgstr ""
msgstr "Buscar palabras clave"
#: cps/templates/book_edit.html:124 cps/templates/layout.html:60
msgid "Go!"
msgstr "Vamos!"
msgstr "¡Vamos!"
#: cps/templates/book_edit.html:125
msgid "Click the cover to load metadata to the form"
msgstr ""
msgstr "Haga clic en la portada para cargar la metainformación en el formulario"
#: cps/templates/book_edit.html:129 cps/templates/book_edit.html:142
msgid "Loading..."
msgstr ""
msgstr "Cargando..."
#: cps/templates/book_edit.html:132
msgid "Close"
msgstr ""
msgstr "Cerrar"
#: cps/templates/book_edit.html:143
msgid "Search error!"
msgstr ""
msgstr "¡Error en la búsqueda!"
#: cps/templates/book_edit.html:144
msgid "No Result! Please try anonther keyword."
msgstr ""
msgstr "¡Sin resultados! Por favor, pruebe otra palabra clave."
#: cps/templates/book_edit.html:146 cps/templates/detail.html:76
#: cps/templates/search_form.html:14
msgid "Publisher"
msgstr ""
msgstr "Editor"
#: cps/templates/book_edit.html:148
msgid "Source"
msgstr ""
msgstr "Origen"
#: cps/templates/config_edit.html:7
msgid "Location of Calibre database"
msgstr ""
msgstr "Ubicación de la base de datos Calibre"
#: cps/templates/config_edit.html:13
msgid "Use google drive?"
msgstr ""
msgstr "¿Utiliza google drive?"
#: cps/templates/config_edit.html:17
msgid "Client id"
msgstr ""
msgstr "Id cliente"
#: cps/templates/config_edit.html:21
msgid "Client secret"
msgstr ""
msgstr "Contraseña cliente"
#: cps/templates/config_edit.html:25
msgid "Calibre Base URL"
msgstr ""
msgstr "URL Base de Calibre"
#: cps/templates/config_edit.html:29
msgid "Google drive Calibre folder"
msgstr ""
msgstr "Carpeta Calibre de Google drive"
#: cps/templates/config_edit.html:38
msgid "Metadata Watch Channel ID"
msgstr ""
msgstr "Metadata Watch Channel ID"
#: cps/templates/config_edit.html:52
msgid "Server Port"
msgstr ""
msgstr "Puerto del servidor"
#: cps/templates/config_edit.html:56 cps/templates/shelf_edit.html:7
msgid "Title"
@ -676,31 +677,31 @@ msgstr "Titulo"
#: cps/templates/config_edit.html:64
msgid "No. of random books to show"
msgstr ""
msgstr "Número de libros aletorios a mostrar"
#: cps/templates/config_edit.html:68
msgid "Regular expression for ignoring columns"
msgstr ""
msgstr "Expresión regular para ignorar columnas"
#: cps/templates/config_edit.html:72
msgid "Regular expression for title sorting"
msgstr ""
msgstr "Expresión regular para ordenar títulos"
#: cps/templates/config_edit.html:86
msgid "Enable uploading"
msgstr ""
msgstr "Permitir subida"
#: cps/templates/config_edit.html:90
msgid "Enable anonymous browsing"
msgstr ""
msgstr "Permitir navegación anónima"
#: cps/templates/config_edit.html:94
msgid "Enable public registration"
msgstr ""
msgstr "Permitir registro público"
#: cps/templates/config_edit.html:96
msgid "Default Settings for new users"
msgstr ""
msgstr "Ajustes por defecto para nuevos usuarios"
#: cps/templates/config_edit.html:99 cps/templates/user_edit.html:87
msgid "Admin user"
@ -741,11 +742,11 @@ msgstr "Lenguaje"
#: cps/templates/detail.html:81
msgid "Publishing date"
msgstr ""
msgstr "Fecha de publicación"
#: cps/templates/detail.html:115
msgid "Read"
msgstr ""
msgstr "Leer"
#: cps/templates/detail.html:123
msgid "Description:"
@ -765,23 +766,23 @@ msgstr "Editar la metadata"
#: cps/templates/email_edit.html:11
msgid "SMTP port (usually 25 for plain SMTP and 465 for SSL and 587 for STARTTLS)"
msgstr ""
msgstr "Puerto SMTP (por lo general 25 para SMTP plano, 465 para SSL y 587 para STARTTLS)"
#: cps/templates/email_edit.html:15
msgid "Encryption"
msgstr ""
msgstr "Cifrado"
#: cps/templates/email_edit.html:17
msgid "None"
msgstr ""
msgstr "Ninguno"
#: cps/templates/email_edit.html:18
msgid "STARTTLS"
msgstr ""
msgstr "STATRTTLS"
#: cps/templates/email_edit.html:19
msgid "SSL/TLS"
msgstr ""
msgstr "SSL/TLS"
#: cps/templates/email_edit.html:31
msgid "From e-mail"
@ -817,11 +818,11 @@ msgstr "Libros Populares"
#: cps/templates/index.xml:19
msgid "Popular publications from this catalog based on Downloads."
msgstr ""
msgstr "Publicaciones mas populares para este catálogo basadas en las descargas."
#: cps/templates/index.xml:22 cps/templates/layout.html:129
msgid "Best rated Books"
msgstr ""
msgstr "Libros mejor valorados"
#: cps/templates/index.xml:26
msgid "Popular publications from this catalog based on Rating."
@ -829,11 +830,11 @@ msgstr "Publicaciones populares del catalogo basados en el puntaje."
#: cps/templates/index.xml:29 cps/templates/layout.html:124
msgid "New Books"
msgstr "Nuevos Libros"
msgstr "Nuevos libros"
#: cps/templates/index.xml:33
msgid "The latest Books"
msgstr "Libros Recientes"
msgstr "Libros recientes"
#: cps/templates/index.xml:40
msgid "Show Random Books"
@ -842,12 +843,12 @@ msgstr "Mostrar libros al azar"
#: cps/templates/index.xml:43 cps/templates/index.xml:47
#: cps/templates/layout.html:132
msgid "Read Books"
msgstr ""
msgstr "Libros leídos"
#: cps/templates/index.xml:50 cps/templates/index.xml:54
#: cps/templates/layout.html:133
msgid "Unread Books"
msgstr ""
msgstr "Libros no leídos"
#: cps/templates/index.xml:57 cps/templates/layout.html:144
msgid "Authors"
@ -867,7 +868,7 @@ msgstr "Libros ordenados por Series"
#: cps/templates/layout.html:48
msgid "Toggle navigation"
msgstr ""
msgstr "Alternar navegación"
#: cps/templates/layout.html:68
msgid "Advanced Search"
@ -875,7 +876,7 @@ msgstr "Busqueda avanzada"
#: cps/templates/layout.html:89
msgid "Logout"
msgstr "Cerrar Sesion"
msgstr "Cerrar sesión"
#: cps/templates/layout.html:94 cps/templates/register.html:18
msgid "Register"
@ -899,11 +900,11 @@ msgstr "Lenguaje"
#: cps/templates/layout.html:149
msgid "Public Shelves"
msgstr "Estantes Publicos"
msgstr "Estantes blicos"
#: cps/templates/layout.html:153
msgid "Your Shelves"
msgstr "Sus Estantes"
msgstr "Sus estantes"
#: cps/templates/layout.html:158
msgid "Create a Shelf"
@ -929,7 +930,7 @@ msgstr "Recordarme"
#: cps/templates/osd.xml:5
msgid "Calibre Web ebook catalog"
msgstr ""
msgstr "Catálogo de libros electrónicos Calibre Web"
#: cps/templates/read.html:136
msgid "Reflow text when sidebars are open."
@ -941,7 +942,7 @@ msgstr "Visor PDF.js"
#: cps/templates/readtxt.html:6
msgid "Basic txt Reader"
msgstr ""
msgstr "Lector básico de txt"
#: cps/templates/register.html:4
msgid "Register a new account"
@ -981,11 +982,11 @@ msgstr "Excluir etiquetas"
#: cps/templates/search_form.html:47
msgid "Exclude Series"
msgstr ""
msgstr "Excluir series"
#: cps/templates/search_form.html:68
msgid "Exclude Languages"
msgstr ""
msgstr "Excluir idiomas"
#: cps/templates/shelf.html:6
msgid "Delete this Shelf"
@ -997,15 +998,15 @@ msgstr "Editar nombre del estante"
#: cps/templates/shelf.html:8 cps/templates/shelf_order.html:11
msgid "Change order"
msgstr ""
msgstr "Cambiar orden"
#: cps/templates/shelf_edit.html:12
msgid "should the shelf be public?"
msgstr "Debe ser el estante publico?"
msgstr "¿Hacer público el estante?"
#: cps/templates/shelf_order.html:5
msgid "Drag 'n drop to rearrange order"
msgstr ""
msgstr "Pinchar y arrastrar para reordenar"
#: cps/templates/stats.html:3
msgid "Calibre library statistics"
@ -1021,11 +1022,11 @@ msgstr "Autores en esta Biblioteca"
#: cps/templates/stats.html:16
msgid "Categories in this Library"
msgstr ""
msgstr "Categorías en esta librería"
#: cps/templates/stats.html:20
msgid "Series in this Library"
msgstr ""
msgstr "Series en esta librería"
#: cps/templates/stats.html:24
msgid "Linked libraries"
@ -1061,7 +1062,7 @@ msgstr "Mostrar libros populares"
#: cps/templates/user_edit.html:55
msgid "Show best rated books"
msgstr ""
msgstr "Mostrar libros mejor valorados"
#: cps/templates/user_edit.html:59
msgid "Show language selection"
@ -1077,15 +1078,15 @@ msgstr "Mostrar categorias elegidas"
#: cps/templates/user_edit.html:71
msgid "Show author selection"
msgstr ""
msgstr "Mostrar selección de autores"
#: cps/templates/user_edit.html:75
msgid "Show read and unread"
msgstr ""
msgstr "Mostrar leídos y no leídos"
#: cps/templates/user_edit.html:79
msgid "Show random books in detail view"
msgstr ""
msgstr "Mostrar libro aleatorios con vista detallada"
#: cps/templates/user_edit.html:112
msgid "Delete this user"
@ -31922,4 +31923,3 @@ msgstr "Zaza"
#. name for zzj
msgid "Zhuang; Zuojiang"
msgstr "Chuang zuojiang"

View File

@ -307,7 +307,7 @@ msgstr "用户 '%(user)s' 已被创建"
#: cps/web.py:2198
msgid "Found an existing account for this email address or nickname."
msgstr "已找到使用此邮箱或昵称的账号。"
msgstr "已存在使用此邮箱或昵称的账号。"
#: cps/web.py:2220
msgid "Mail settings updated"
@ -496,7 +496,7 @@ msgstr "最新提交时间戳"
#: cps/templates/admin.html:83
msgid "Reconnect to Calibre DB"
msgstr ""
msgstr "重新连接到Calibre数据库"
#: cps/templates/admin.html:84
msgid "Restart Calibre-web"
@ -590,7 +590,7 @@ msgstr "编辑后查看书籍"
#: cps/templates/book_edit.html:107 cps/templates/book_edit.html:118
msgid "Get metadata"
msgstr ""
msgstr "获取元数据"
#: cps/templates/book_edit.html:108 cps/templates/config_edit.html:117
#: cps/templates/login.html:19 cps/templates/search_form.html:79
@ -600,11 +600,11 @@ msgstr "提交"
#: cps/templates/book_edit.html:121
msgid "Keyword"
msgstr ""
msgstr "关键字"
#: cps/templates/book_edit.html:122
msgid " Search keyword "
msgstr ""
msgstr "搜索关键字"
#: cps/templates/book_edit.html:124 cps/templates/layout.html:60
msgid "Go!"
@ -612,32 +612,32 @@ msgstr "走起!"
#: cps/templates/book_edit.html:125
msgid "Click the cover to load metadata to the form"
msgstr ""
msgstr "点击封面加载元数据到表单"
#: cps/templates/book_edit.html:129 cps/templates/book_edit.html:142
msgid "Loading..."
msgstr ""
msgstr "加载中..."
#: cps/templates/book_edit.html:132
msgid "Close"
msgstr ""
msgstr "关闭"
#: cps/templates/book_edit.html:143
msgid "Search error!"
msgstr ""
msgstr "搜索错误"
#: cps/templates/book_edit.html:144
msgid "No Result! Please try anonther keyword."
msgstr ""
msgstr "没有结果!请尝试别的关键字."
#: cps/templates/book_edit.html:146 cps/templates/detail.html:76
#: cps/templates/search_form.html:14
msgid "Publisher"
msgstr ""
msgstr "出版社"
#: cps/templates/book_edit.html:148
msgid "Source"
msgstr ""
msgstr "来源"
#: cps/templates/config_edit.html:7
msgid "Location of Calibre database"
@ -645,7 +645,7 @@ msgstr "Calibre 数据库位置"
#: cps/templates/config_edit.html:13
msgid "Use google drive?"
msgstr ""
msgstr "是否使用google drive?"
#: cps/templates/config_edit.html:17
msgid "Client id"
@ -660,8 +660,8 @@ msgid "Calibre Base URL"
msgstr ""
#: cps/templates/config_edit.html:29
msgid "Google drive Calibre folder"
msgstr ""
msgid "Google drive Calibre folde"
msgstr "Google drive Calibre 文件夹"
#: cps/templates/config_edit.html:38
msgid "Metadata Watch Channel ID"
@ -843,12 +843,12 @@ msgstr "显示随机书籍"
#: cps/templates/index.xml:43 cps/templates/index.xml:47
#: cps/templates/layout.html:132
msgid "Read Books"
msgstr ""
msgstr "已读书籍"
#: cps/templates/index.xml:50 cps/templates/index.xml:54
#: cps/templates/layout.html:133
msgid "Unread Books"
msgstr ""
msgstr "未读书籍"
#: cps/templates/index.xml:57 cps/templates/layout.html:144
msgid "Authors"
@ -1082,7 +1082,7 @@ msgstr "显示作者选择"
#: cps/templates/user_edit.html:75
msgid "Show read and unread"
msgstr ""
msgstr "显示已读和未读"
#: cps/templates/user_edit.html:79
msgid "Show random books in detail view"

View File

@ -13,7 +13,7 @@ from flask_babel import gettext as _
import json
#from builtins import str
dbpath = os.path.join(os.path.normpath(os.path.dirname(os.path.realpath(__file__)) + os.sep + ".." + os.sep), "app.db")
dbpath = os.path.join(os.path.normpath(os.getenv("CALIBRE_DBPATH", os.path.dirname(os.path.realpath(__file__)) + os.sep + ".." + os.sep)), "app.db")
engine = create_engine('sqlite:///{0}'.format(dbpath), echo=False)
Base = declarative_base()
@ -64,10 +64,7 @@ class UserBase:
return False
def role_upload(self):
if self.role is not None:
return True if self.role & ROLE_UPLOAD == ROLE_UPLOAD else False
else:
return False
return bool((self.role is not None)and(self.role & ROLE_UPLOAD == ROLE_UPLOAD))
def role_edit(self):
if self.role is not None:
@ -93,9 +90,11 @@ class UserBase:
else:
return False
@classmethod
def is_active(self):
return True
@classmethod
def is_anonymous(self):
return False
@ -106,58 +105,31 @@ class UserBase:
return self.default_language
def show_random_books(self):
if self.sidebar_view is not None:
return True if self.sidebar_view & SIDEBAR_RANDOM == SIDEBAR_RANDOM else False
else:
return False
return bool((self.sidebar_view is not None)and(self.sidebar_view & SIDEBAR_RANDOM == SIDEBAR_RANDOM))
def show_language(self):
if self.sidebar_view is not None:
return True if self.sidebar_view & SIDEBAR_LANGUAGE == SIDEBAR_LANGUAGE else False
else:
return False
return bool((self.sidebar_view is not None)and(self.sidebar_view & SIDEBAR_LANGUAGE == SIDEBAR_LANGUAGE))
def show_hot_books(self):
if self.sidebar_view is not None:
return True if self.sidebar_view & SIDEBAR_HOT == SIDEBAR_HOT else False
else:
return False
return bool((self.sidebar_view is not None)and(self.sidebar_view & SIDEBAR_HOT == SIDEBAR_HOT))
def show_series(self):
if self.sidebar_view is not None:
return True if self.sidebar_view & SIDEBAR_SERIES == SIDEBAR_SERIES else False
else:
return False
return bool((self.sidebar_view is not None)and(self.sidebar_view & SIDEBAR_SERIES == SIDEBAR_SERIES))
def show_category(self):
if self.sidebar_view is not None:
return True if self.sidebar_view & SIDEBAR_CATEGORY == SIDEBAR_CATEGORY else False
else:
return False
return bool((self.sidebar_view is not None)and(self.sidebar_view & SIDEBAR_CATEGORY == SIDEBAR_CATEGORY))
def show_author(self):
if self.sidebar_view is not None:
return True if self.sidebar_view & SIDEBAR_AUTHOR == SIDEBAR_AUTHOR else False
else:
return False
return bool((self.sidebar_view is not None)and(self.sidebar_view & SIDEBAR_AUTHOR == SIDEBAR_AUTHOR))
def show_best_rated_books(self):
if self.sidebar_view is not None:
return True if self.sidebar_view & SIDEBAR_BEST_RATED == SIDEBAR_BEST_RATED else False
else:
return False
return bool((self.sidebar_view is not None)and(self.sidebar_view & SIDEBAR_BEST_RATED == SIDEBAR_BEST_RATED))
def show_read_and_unread(self):
if self.sidebar_view is not None:
return True if self.sidebar_view & SIDEBAR_READ_AND_UNREAD == SIDEBAR_READ_AND_UNREAD else False
else:
return False
return bool((self.sidebar_view is not None)and(self.sidebar_view & SIDEBAR_READ_AND_UNREAD == SIDEBAR_READ_AND_UNREAD))
def show_detail_random(self):
if self.sidebar_view is not None:
return True if self.sidebar_view & DETAIL_RANDOM == DETAIL_RANDOM else False
else:
return False
return bool((self.sidebar_view is not None)and(self.sidebar_view & DETAIL_RANDOM == DETAIL_RANDOM))
def __repr__(self):
return '<User %r>' % self.nickname
@ -321,10 +293,8 @@ class Config:
else:
self.config_google_drive_watch_changes_response=None
self.config_columns_to_ignore = data.config_columns_to_ignore
if self.config_calibre_dir is not None and (not self.config_use_google_drive or os.path.exists(self.config_calibre_dir + '/metadata.db')):
self.db_configured = True
else:
self.db_configured = False
self.db_configured = bool(self.config_calibre_dir is not None and
(not self.config_use_google_drive or os.path.exists(self.config_calibre_dir + '/metadata.db')))
@property
def get_main_dir(self):
@ -506,9 +476,8 @@ def create_anonymous_user():
session.add(user)
try:
session.commit()
except Exception as e:
except Exception:
session.rollback()
pass
# Generate User admin with admin123 password, and access to everything
@ -525,9 +494,8 @@ def create_admin_user():
session.add(user)
try:
session.commit()
except Exception as e:
except Exception:
session.rollback()
pass
# Open session for database connection

View File

@ -32,6 +32,7 @@ from flask_babel import gettext as _
import requests
import zipfile
from werkzeug.security import generate_password_hash, check_password_hash
from werkzeug.datastructures import Headers
from babel import Locale as LC
from babel import negotiate_locale
from babel import __version__ as babelVersion
@ -40,7 +41,6 @@ from functools import wraps
import base64
from sqlalchemy.sql import *
import json
import urllib
import datetime
from iso639 import languages as isoLanguages
from iso639 import __version__ as iso639Version
@ -53,24 +53,21 @@ import db
from shutil import move, copyfile
from tornado.ioloop import IOLoop
import shutil
import StringIO
import gdriveutils
import tempfile
import io
import hashlib
import threading
from tornado import version as tornadoVersion
try:
from urllib.parse import quote
from imp import reload
except ImportError as e:
except ImportError:
from urllib import quote
try:
from flask_login import __version__ as flask_loginVersion
except ImportError as e:
except ImportError:
from flask_login.__about__ import __version__ as flask_loginVersion
import time
@ -82,7 +79,6 @@ try:
use_generic_pdf_cover = False
except ImportError:
use_generic_pdf_cover = True
from cgi import escape
# Global variables
gdrive_watch_callback_token='target=calibreweb-watch_files'
@ -135,6 +131,7 @@ class Singleton:
def __instancecheck__(self, inst):
return isinstance(inst, self._decorated)
@Singleton
class Gauth:
def __init__(self):
@ -275,14 +272,9 @@ def load_user_from_header(header_val):
return user
return
def check_auth(username, password):
user = ub.session.query(ub.User).filter(ub.User.nickname == username).first()
if user and check_password_hash(user.password, password):
return True
else:
return False
return bool(user and check_password_hash(user.password, password))
def authenticate():
return Response(
@ -389,7 +381,7 @@ def shortentitle_filter(s):
def mimetype_filter(val):
try:
s = mimetypes.types_map['.' + val]
except Exception as e:
except Exception:
s = 'application/octet-stream'
return s
@ -406,10 +398,10 @@ def timestamptodate(date, fmt=None):
)
native = date.replace(tzinfo=None)
if fmt:
format=fmt
time_format=fmt
else:
format='%d %m %Y - %H:%S'
return native.strftime(format)
time_format='%d %m %Y - %H:%S'
return native.strftime(time_format)
def admin_required(f):
"""
@ -472,22 +464,22 @@ def edit_required(f):
# Fill indexpage with all requested data from database
def fill_indexpage(page, database, db_filter, order):
if current_user.filter_language() != "all":
filter = db.Books.languages.any(db.Languages.lang_code == current_user.filter_language())
lang_filter = db.Books.languages.any(db.Languages.lang_code == current_user.filter_language())
else:
filter = True
lang_filter = True
if current_user.show_detail_random():
random = db.session.query(db.Books).filter(filter).order_by(func.random()).limit(config.config_random_books)
random = db.session.query(db.Books).filter(lang_filter).order_by(func.random()).limit(config.config_random_books)
else:
random = false
off = int(int(config.config_books_per_page) * (page - 1))
pagination = Pagination(page, config.config_books_per_page,
len(db.session.query(database).filter(db_filter).filter(filter).all()))
entries = db.session.query(database).filter(db_filter).filter(filter).order_by(order).offset(off).limit(
len(db.session.query(database).filter(db_filter).filter(lang_filter).all()))
entries = db.session.query(database).filter(db_filter).filter(lang_filter).order_by(order).offset(off).limit(
config.config_books_per_page)
return entries, random, pagination
def modify_database_object(input_elements, db_book_object, db_object, db_session, type):
def modify_database_object(input_elements, db_book_object, db_object, db_session, db_type):
input_elements = [x for x in input_elements if x != '']
# we have all input element (authors, series, tags) names now
# 1. search for elements to remove
@ -519,7 +511,7 @@ def modify_database_object(input_elements, db_book_object, db_object, db_session
db_session.delete(del_element)
# if there are elements to add, we add them now!
if len(add_elements) > 0:
if type == 'languages':
if db_type == 'languages':
db_filter = db_object.lang_code
else:
db_filter = db_object.name
@ -528,12 +520,12 @@ def modify_database_object(input_elements, db_book_object, db_object, db_session
new_element = db_session.query(db_object).filter(db_filter == add_element).first()
# if no element is found add it
if new_element is None:
if type == 'author':
if db_type == 'author':
new_element = db_object(add_element, add_element, "")
else:
if type == 'series':
if db_type == 'series':
new_element = db_object(add_element, add_element)
else: # type should be tag, or languages
else: # db_type should be tag, or languages
new_element = db_object(add_element)
db_session.add(new_element)
new_element = db.session.query(db_object).filter(db_filter == add_element).first()
@ -562,10 +554,6 @@ def before_request():
@app.route("/opds")
@requires_basic_auth_if_no_ano
def feed_index():
if current_user.filter_language() != "all":
filter = db.Books.languages.any(db.Languages.lang_code == current_user.filter_language())
else:
filter = True
xml = render_title_template('index.xml')
response = make_response(xml)
response.headers["Content-Type"] = "application/xml"
@ -595,15 +583,15 @@ def feed_normal_search():
def feed_search(term):
if current_user.filter_language() != "all":
filter = db.Books.languages.any(db.Languages.lang_code == current_user.filter_language())
lang_filter = db.Books.languages.any(db.Languages.lang_code == current_user.filter_language())
else:
filter = True
lang_filter = True
if term:
entries = db.session.query(db.Books).filter(db.or_(db.Books.tags.any(db.Tags.name.like("%" + term + "%")),
db.Books.series.any(db.Series.name.like("%" + term + "%")),
db.Books.authors.any(db.Authors.name.like("%" + term + "%")),
db.Books.publishers.any(db.Publishers.name.like("%" + term + "%")),
db.Books.title.like("%" + term + "%"))).filter(filter).all()
db.Books.title.like("%" + term + "%"))).filter(lang_filter).all()
entriescount = len(entries) if len(entries) > 0 else 1
pagination = Pagination(1, entriescount, entriescount)
xml = render_title_template('feed.xml', searchterm=term, entries=entries, pagination=pagination)
@ -620,7 +608,7 @@ def feed_new():
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, True, db.Books.timestamp.desc())
xml = render_title_template('feed.xml', entries=entries, pagination=pagination)
response = make_response(xml)
@ -632,10 +620,10 @@ def feed_new():
@requires_basic_auth_if_no_ano
def feed_discover():
if current_user.filter_language() != "all":
filter = db.Books.languages.any(db.Languages.lang_code == current_user.filter_language())
lang_filter = db.Books.languages.any(db.Languages.lang_code == current_user.filter_language())
else:
filter = True
entries = db.session.query(db.Books).filter(filter).order_by(func.random()).limit(config.config_books_per_page)
lang_filter = True
entries = db.session.query(db.Books).filter(lang_filter).order_by(func.random()).limit(config.config_books_per_page)
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)
@ -663,9 +651,9 @@ def feed_hot():
if not off:
off = 0
if current_user.filter_language() != "all":
filter = db.Books.languages.any(db.Languages.lang_code == current_user.filter_language())
lang_filter = db.Books.languages.any(db.Languages.lang_code == current_user.filter_language())
else:
filter = True
lang_filter = True
all_books = ub.session.query(ub.Downloads, ub.func.count(ub.Downloads.book_id)).order_by(
ub.func.count(ub.Downloads.book_id).desc()).group_by(ub.Downloads.book_id)
hot_books = all_books.offset(off).limit(config.config_books_per_page)
@ -674,7 +662,7 @@ def feed_hot():
downloadBook = db.session.query(db.Books).filter(db.Books.id == book.Downloads.book_id).first()
if downloadBook:
entries.append(
db.session.query(db.Books).filter(filter).filter(db.Books.id == book.Downloads.book_id).first())
db.session.query(db.Books).filter(lang_filter).filter(db.Books.id == book.Downloads.book_id).first())
else:
ub.session.query(ub.Downloads).filter(book.Downloads.book_id == ub.Downloads.book_id).delete()
ub.session.commit()
@ -693,10 +681,10 @@ def feed_authorindex():
if not off:
off = 0
if current_user.filter_language() != "all":
filter = db.Books.languages.any(db.Languages.lang_code == current_user.filter_language())
lang_filter = db.Books.languages.any(db.Languages.lang_code == current_user.filter_language())
else:
filter = True
entries = db.session.query(db.Authors).join(db.books_authors_link).join(db.Books).filter(filter)\
lang_filter = True
entries = db.session.query(db.Authors).join(db.books_authors_link).join(db.Books).filter(lang_filter)\
.group_by('books_authors_link.author').order_by(db.Authors.sort).limit(config.config_books_per_page).offset(off)
pagination = Pagination((int(off) / (int(config.config_books_per_page)) + 1), config.config_books_per_page,
len(db.session.query(db.Authors).all()))
@ -706,14 +694,14 @@ def feed_authorindex():
return response
@app.route("/opds/author/<int:id>")
@app.route("/opds/author/<int:book_id>")
@requires_basic_auth_if_no_ano
def feed_author(id):
def feed_author(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),
db.Books, db.Books.authors.any(db.Authors.id == id), db.Books.timestamp.desc())
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"
@ -727,10 +715,10 @@ def feed_categoryindex():
if not off:
off = 0
if current_user.filter_language() != "all":
filter = db.Books.languages.any(db.Languages.lang_code == current_user.filter_language())
lang_filter = db.Books.languages.any(db.Languages.lang_code == current_user.filter_language())
else:
filter = True
entries = db.session.query(db.Tags).join(db.books_tags_link).join(db.Books).filter(filter).\
lang_filter = True
entries = db.session.query(db.Tags).join(db.books_tags_link).join(db.Books).filter(lang_filter).\
group_by('books_tags_link.tag').order_by(db.Tags.name).offset(off).limit(config.config_books_per_page)
pagination = Pagination((int(off) / (int(config.config_books_per_page)) + 1), config.config_books_per_page,
len(db.session.query(db.Tags).all()))
@ -740,14 +728,14 @@ def feed_categoryindex():
return response
@app.route("/opds/category/<int:id>")
@app.route("/opds/category/<int:book_id>")
@requires_basic_auth_if_no_ano
def feed_category(id):
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),
db.Books, db.Books.tags.any(db.Tags.id == id), db.Books.timestamp.desc())
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"
@ -761,10 +749,10 @@ def feed_seriesindex():
if not off:
off = 0
if current_user.filter_language() != "all":
filter = db.Books.languages.any(db.Languages.lang_code == current_user.filter_language())
lang_filter = db.Books.languages.any(db.Languages.lang_code == current_user.filter_language())
else:
filter = True
entries = db.session.query(db.Series).join(db.books_series_link).join(db.Books).filter(filter).\
lang_filter = True
entries = db.session.query(db.Series).join(db.books_series_link).join(db.Books).filter(lang_filter).\
group_by('books_series_link.series').order_by(db.Series.sort).offset(off).all()
pagination = Pagination((int(off) / (int(config.config_books_per_page)) + 1), config.config_books_per_page,
len(db.session.query(db.Series).all()))
@ -774,14 +762,14 @@ def feed_seriesindex():
return response
@app.route("/opds/series/<int:id>")
@app.route("/opds/series/<int:book_id>")
@requires_basic_auth_if_no_ano
def feed_series(id):
def feed_series(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),
db.Books, db.Books.series.any(db.Series.id == id),db.Books.series_index)
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"
@ -796,13 +784,12 @@ def partial(total_byte_len, part_size_limit):
return s
def do_gdrive_download(df, headers):
startTime=time.time()
total_size = int(df.metadata.get('fileSize'))
download_url = df.metadata.get('downloadUrl')
s = partial(total_size, 1024 * 1024) # I'm downloading BIG files, so 100M chunk size is fine for me
def stream():
for bytes in s:
headers = {"Range" : 'bytes=%s-%s' % (bytes[0], bytes[1])}
for byte in s:
headers = {"Range" : 'bytes=%s-%s' % (byte[0], byte[1])}
resp, content = df.auth.Get_Http_Object().request(download_url, headers=headers)
if resp.status == 206 :
yield content
@ -811,14 +798,14 @@ def do_gdrive_download(df, headers):
return
return Response(stream_with_context(stream()), headers=headers)
@app.route("/opds/download/<book_id>/<format>/")
@app.route("/opds/download/<book_id>/<book_format>/")
@requires_basic_auth_if_no_ano
@download_required
def get_opds_download_link(book_id, format):
def get_opds_download_link(book_id, book_format):
startTime=time.time()
format = format.split(".")[0]
book_format = book_format.split(".")[0]
book = db.session.query(db.Books).filter(db.Books.id == book_id).first()
data = db.session.query(db.Data).filter(db.Data.book == book.id).filter(db.Data.format == format.upper()).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:
helper.update_download(book_id, int(current_user.id))
@ -826,16 +813,16 @@ def get_opds_download_link(book_id, format):
if len(book.authors) > 0:
file_name = book.authors[0].name + '_' + file_name
file_name = helper.get_valid_filename(file_name)
headers={}
headers["Content-Disposition"] = "attachment; filename*=UTF-8''%s.%s" % (quote(file_name.encode('utf8')), format)
headers = Headers ()
headers["Content-Disposition"] = "attachment; filename*=UTF-8''%s.%s" % (quote(file_name.encode('utf8')), book_format)
app.logger.info (time.time()-startTime)
startTime=time.time()
if config.config_use_google_drive:
df=gdriveutils.getFileFromEbooksFolder(Gdrive.Instance().drive, book.path, data.name + "." + format)
app.logger.info(time.time() - startTime)
df=gdriveutils.getFileFromEbooksFolder(Gdrive.Instance().drive, book.path, data.name + "." + book_format)
return do_gdrive_download(df, headers)
else:
# file_name = helper.get_valid_filename(file_name)
response = make_response(send_from_directory(os.path.join(config.config_calibre_dir, book.path), data.name + "." + format))
response = make_response(send_from_directory(os.path.join(config.config_calibre_dir, book.path), data.name + "." + book_format))
response.headers=headers
return response
@ -858,8 +845,9 @@ def get_metadata_calibre_companion(uuid):
def get_authors_json():
if request.method == "GET":
query = request.args.get('q')
entries = db.session.execute("select name from authors where name like '%" + query + "%'")
json_dumps = json.dumps([dict(r) for r in entries])
# entries = db.session.execute("select name from authors where name like '%" + query + "%'")
entries = db.session.query(db.Authors).filter(db.Authors.name.like("%" + query + "%")).all()
json_dumps = json.dumps([dict(name=r.name) for r in entries])
return json_dumps
@ -868,8 +856,11 @@ def get_authors_json():
def get_tags_json():
if request.method == "GET":
query = request.args.get('q')
entries = db.session.execute("select name from tags where name like '%" + query + "%'")
json_dumps = json.dumps([dict(r) for r in entries])
# entries = db.session.execute("select name from tags where name like '%" + query + "%'")
entries = db.session.query(db.Tags).filter(db.Tags.name.like("%" + query + "%")).all()
#for x in entries:
# alfa = dict(name=x.name)
json_dumps = json.dumps([dict(name=r.name) for r in entries])
return json_dumps
@app.route("/get_update_status", methods=['GET'])
@ -915,7 +906,7 @@ def get_updater_status():
elif request.method == "GET":
try:
status['status']=helper.updater_thread.get_update_status()
except Exception as e:
except Exception:
status['status'] = 7
return json.dumps(status)
@ -930,7 +921,7 @@ def get_languages_json():
try:
cur_l = LC.parse(lang.lang_code)
lang.name = cur_l.get_language_name(get_locale())
except Exception as e:
except Exception:
lang.name = _(isoLanguages.get(part3=lang.lang_code).name)
entries = [s for s in languages if query in s.name.lower()]
json_dumps = json.dumps([dict(name=r.name) for r in entries])
@ -942,8 +933,9 @@ def get_languages_json():
def get_series_json():
if request.method == "GET":
query = request.args.get('q')
entries = db.session.execute("select name from series where name like '%" + query + "%'")
json_dumps = json.dumps([dict(r) for r in entries])
entries = db.session.query(db.Series).filter(db.Series.name.like("%" + query + "%")).all()
# entries = db.session.execute("select name from series where name like '%" + query + "%'")
json_dumps = json.dumps([dict(name=r.name) for r in entries])
return json_dumps
@ -987,11 +979,11 @@ def index(page):
@login_required_if_no_ano
def hot_books(page):
if current_user.filter_language() != "all":
filter = db.Books.languages.any(db.Languages.lang_code == current_user.filter_language())
lang_filter = db.Books.languages.any(db.Languages.lang_code == current_user.filter_language())
else:
filter = True
lang_filter = True
if current_user.show_detail_random():
random = db.session.query(db.Books).filter(filter).order_by(func.random()).limit(config.config_random_books)
random = db.session.query(db.Books).filter(lang_filter).order_by(func.random()).limit(config.config_random_books)
else:
random = false
off = int(int(config.config_books_per_page) * (page - 1))
@ -1003,7 +995,7 @@ def hot_books(page):
downloadBook = db.session.query(db.Books).filter(db.Books.id == book.Downloads.book_id).first()
if downloadBook:
entries.append(
db.session.query(db.Books).filter(filter).filter(db.Books.id == book.Downloads.book_id).first())
db.session.query(db.Books).filter(lang_filter).filter(db.Books.id == book.Downloads.book_id).first())
else:
ub.session.query(ub.Downloads).filter(book.Downloads.book_id == ub.Downloads.book_id).delete()
ub.session.commit()
@ -1036,22 +1028,22 @@ def discover(page):
@login_required_if_no_ano
def author_list():
if current_user.filter_language() != "all":
filter = db.Books.languages.any(db.Languages.lang_code == current_user.filter_language())
lang_filter = db.Books.languages.any(db.Languages.lang_code == current_user.filter_language())
else:
filter = True
lang_filter = True
entries = db.session.query(db.Authors, func.count('books_authors_link.book').label('count')).join(
db.books_authors_link).join(db.Books).filter(
filter).group_by('books_authors_link.author').order_by(db.Authors.sort).all()
lang_filter).group_by('books_authors_link.author').order_by(db.Authors.sort).all()
return render_title_template('list.html', entries=entries, folder='author', title=_(u"Author list"))
@app.route("/author/<int:id>", defaults={'page': 1})
@app.route("/author/<int:id>/<int:page>'")
@app.route("/author/<int:book_id>", defaults={'page': 1})
@app.route("/author/<int:book_id>/<int:page>'")
@login_required_if_no_ano
def author(id,page):
entries, random, pagination = fill_indexpage(page, db.Books, db.Books.authors.any(db.Authors.id == id),
def author(book_id,page):
entries, random, pagination = fill_indexpage(page, db.Books, db.Books.authors.any(db.Authors.id == book_id),
db.Books.timestamp.desc())
name = db.session.query(db.Authors).filter(db.Authors.id == id).first().name
name = db.session.query(db.Authors).filter(db.Authors.id == book_id).first().name
if entries:
return render_title_template('index.html', random=random, entries=entries, title=_(u"Author: %(name)s", name=name))
else:
@ -1063,22 +1055,22 @@ def author(id,page):
@login_required_if_no_ano
def series_list():
if current_user.filter_language() != "all":
filter = db.Books.languages.any(db.Languages.lang_code == current_user.filter_language())
lang_filter = db.Books.languages.any(db.Languages.lang_code == current_user.filter_language())
else:
filter = True
lang_filter = True
entries = db.session.query(db.Series, func.count('books_series_link.book').label('count')).join(
db.books_series_link).join(db.Books).filter(
filter).group_by('books_series_link.series').order_by(db.Series.sort).all()
lang_filter).group_by('books_series_link.series').order_by(db.Series.sort).all()
return render_title_template('list.html', entries=entries, folder='series', title=_(u"Series list"))
@app.route("/series/<int:id>/", defaults={'page': 1})
@app.route("/series/<int:id>/<int:page>'")
@app.route("/series/<int:book_id>/", defaults={'page': 1})
@app.route("/series/<int:book_id>/<int:page>'")
@login_required_if_no_ano
def series(id, page):
entries, random, pagination = fill_indexpage(page, db.Books, db.Books.series.any(db.Series.id == id),
def series(book_id, page):
entries, random, pagination = fill_indexpage(page, db.Books, db.Books.series.any(db.Series.id == book_id),
db.Books.series_index)
name=db.session.query(db.Series).filter(db.Series.id == id).first().name
name=db.session.query(db.Series).filter(db.Series.id == book_id).first().name
if entries:
return render_title_template('index.html', random=random, pagination=pagination, entries=entries,
title=_(u"Series: %(serie)s", serie=name))
@ -1096,13 +1088,13 @@ def language_overview():
try:
cur_l = LC.parse(lang.lang_code)
lang.name = cur_l.get_language_name(get_locale())
except Exception as e:
except Exception:
lang.name = _(isoLanguages.get(part3=lang.lang_code).name)
else:
try:
langfound = 1
cur_l = LC.parse(current_user.filter_language())
except Exception as e:
except Exception:
langfound = 0
languages = db.session.query(db.Languages).filter(
db.Languages.lang_code == current_user.filter_language()).all()
@ -1126,7 +1118,7 @@ def language(name, page):
try:
cur_l = LC.parse(name)
name = cur_l.get_language_name(get_locale())
except Exception as e:
except Exception:
name = _(isoLanguages.get(part3=name).name)
return render_title_template('index.html', random=random, entries=entries, pagination=pagination,
title=_(u"Language: %(name)s", name=name))
@ -1136,58 +1128,58 @@ def language(name, page):
@login_required_if_no_ano
def category_list():
if current_user.filter_language() != "all":
filter = db.Books.languages.any(db.Languages.lang_code == current_user.filter_language())
lang_filter = db.Books.languages.any(db.Languages.lang_code == current_user.filter_language())
else:
filter = True
lang_filter = True
entries = db.session.query(db.Tags, func.count('books_tags_link.book').label('count')).join(
db.books_tags_link).join(db.Books).filter(
filter).group_by('books_tags_link.tag').all()
lang_filter).group_by('books_tags_link.tag').all()
return render_title_template('list.html', entries=entries, folder='category', title=_(u"Category list"))
@app.route("/category/<int:id>", defaults={'page': 1})
@app.route('/category/<int:id>/<int:page>')
@app.route("/category/<int:book_id>", defaults={'page': 1})
@app.route('/category/<int:book_id>/<int:page>')
@login_required_if_no_ano
def category(id, page):
entries, random, pagination = fill_indexpage(page, db.Books, db.Books.tags.any(db.Tags.id == id),
def category(book_id, page):
entries, random, pagination = fill_indexpage(page, db.Books, db.Books.tags.any(db.Tags.id == book_id),
db.Books.timestamp.desc())
name=db.session.query(db.Tags).filter(db.Tags.id == id).first().name
name=db.session.query(db.Tags).filter(db.Tags.id == book_id).first().name
return render_title_template('index.html', random=random, entries=entries, pagination=pagination,
title=_(u"Category: %(name)s", name=name))
@app.route("/ajax/toggleread/<int:id>", methods=['POST'])
@app.route("/ajax/toggleread/<int:book_id>", methods=['POST'])
@login_required
def toggle_read(id):
def toggle_read(book_id):
book = ub.session.query(ub.ReadBook).filter(ub.and_(ub.ReadBook.user_id == int(current_user.id),
ub.ReadBook.book_id == id)).first()
ub.ReadBook.book_id == book_id)).first()
if book:
book.is_read=not book.is_read
else:
readBook=ub.ReadBook()
readBook.user_id=int(current_user.id)
readBook.book_id = id
readBook.book_id = book_id
readBook.is_read=True
book=readBook
ub.session.merge(book)
ub.session.commit()
return ""
@app.route("/book/<int:id>")
@app.route("/book/<int:book_id>")
@login_required_if_no_ano
def show_book(id):
def show_book(book_id):
if current_user.filter_language() != "all":
filter = db.Books.languages.any(db.Languages.lang_code == current_user.filter_language())
lang_filter = db.Books.languages.any(db.Languages.lang_code == current_user.filter_language())
else:
filter = True
entries = db.session.query(db.Books).filter(db.Books.id == id).filter(filter).first()
lang_filter = True
entries = db.session.query(db.Books).filter(db.Books.id == book_id).filter(lang_filter).first()
if entries:
for index in range(0, len(entries.languages)):
try:
entries.languages[index].language_name = LC.parse(entries.languages[index].lang_code).get_language_name(
get_locale())
except Exception as e:
except Exception:
entries.languages[index].language_name = _(
isoLanguages.get(part3=entries.languages[index].lang_code).name)
tmpcc = db.session.query(db.Custom_Columns).filter(db.Custom_Columns.datatype.notin_(db.cc_exceptions)).all()
@ -1201,7 +1193,7 @@ def show_book(id):
else:
cc=tmpcc
book_in_shelfs = []
shelfs = ub.session.query(ub.BookShelf).filter(ub.BookShelf.book_id == id).all()
shelfs = ub.session.query(ub.BookShelf).filter(ub.BookShelf.book_id == book_id).all()
for entry in shelfs:
book_in_shelfs.append(entry.shelf)
@ -1209,7 +1201,7 @@ def show_book(id):
# title=entries.title, books_shelfs=book_in_shelfs)
if not current_user.is_anonymous():
matching_have_read_book=ub.session.query(ub.ReadBook).filter(ub.and_(ub.ReadBook.user_id == int(current_user.id),
ub.ReadBook.book_id == id)).all()
ub.ReadBook.book_id == book_id)).all()
have_read=len(matching_have_read_book) > 0 and matching_have_read_book[0].is_read
else:
have_read=None
@ -1242,11 +1234,10 @@ def stats():
kindlegen = os.path.join(vendorpath, u"kindlegen")
versions['KindlegenVersion'] = _('not installed')
if os.path.exists(kindlegen):
p = subprocess.Popen(kindlegen, shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE,
stdin=subprocess.PIPE)
p = subprocess.Popen(kindlegen, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
p.wait()
for lines in p.stdout.readlines():
if type(lines) is bytes:
if isinstance(lines, bytes):
lines = lines.decode('utf-8')
if re.search('Amazon kindlegen\(', lines):
versions['KindlegenVersion'] = lines
@ -1308,7 +1299,7 @@ def revoke_watch_gdrive():
last_watch_response=config.config_google_drive_watch_changes_response
if last_watch_response:
try:
response=gdriveutils.stopChannel(Gdrive.Instance().drive, last_watch_response['id'], last_watch_response['resourceId'])
gdriveutils.stopChannel(Gdrive.Instance().drive, last_watch_response['id'], last_watch_response['resourceId'])
except HttpError:
pass
settings = ub.session.query(ub.Settings).first()
@ -1340,13 +1331,13 @@ def on_received_watch_confirmation():
if not response['deleted'] and response['file']['title'] == 'metadata.db' and response['file']['md5Checksum'] != md5(dbpath):
tmpDir=tempfile.gettempdir()
app.logger.info ('Database file updated')
copyfile (dbpath, tmpDir + "/metadata.db_" + str(current_milli_time()))
copyfile (dbpath, os.path.join(tmpDir, "metadata.db_" + str(current_milli_time())))
app.logger.info ('Backing up existing and downloading updated metadata.db')
gdriveutils.downloadFile(Gdrive.Instance().drive, None, "metadata.db", tmpDir + "/tmp_metadata.db")
gdriveutils.downloadFile(Gdrive.Instance().drive, None, "metadata.db", os.path.join(tmpDir, "tmp_metadata.db"))
app.logger.info ('Setting up new DB')
os.rename(tmpDir + "/tmp_metadata.db", dbpath)
os.rename(os.path.join(tmpDir, "tmp_metadata.db"), dbpath)
db.setup_db()
except Exception, e:
except Exception as e:
app.logger.exception(e)
updateMetaData()
@ -1389,7 +1380,7 @@ def shutdown():
def update():
helper.updater_thread = helper.Updater()
flash(_(u"Update done"), category="info")
return ""
return abort(404)
@app.route("/search", methods=["GET"])
@ -1398,14 +1389,14 @@ def search():
term = request.args.get("query").strip()
if term:
if current_user.filter_language() != "all":
filter = db.Books.languages.any(db.Languages.lang_code == current_user.filter_language())
lang_filter = db.Books.languages.any(db.Languages.lang_code == current_user.filter_language())
else:
filter = True
lang_filter = True
entries = db.session.query(db.Books).filter(db.or_(db.Books.tags.any(db.Tags.name.like("%" + term + "%")),
db.Books.series.any(db.Series.name.like("%" + term + "%")),
db.Books.authors.any(db.Authors.name.like("%" + term + "%")),
db.Books.publishers.any(db.Publishers.name.like("%" + term + "%")),
db.Books.title.like("%" + term + "%"))).filter(filter).all()
db.Books.title.like("%" + term + "%"))).filter(lang_filter).all()
return render_title_template('search.html', searchterm=term, entries=entries)
else:
return render_title_template('search.html', searchterm="")
@ -1443,7 +1434,7 @@ def advanced_search():
try:
cur_l = LC.parse(lang.lang_code)
lang.name = cur_l.get_language_name(get_locale())
except Exception as e:
except Exception:
lang.name = _(isoLanguages.get(part3=lang.lang_code).name)
searchterm.extend(language.name for language in language_names)
searchterm = " + ".join(filter(None, searchterm))
@ -1475,7 +1466,7 @@ def advanced_search():
try:
cur_l = LC.parse(lang.lang_code)
lang.name = cur_l.get_language_name(get_locale())
except Exception as e:
except Exception:
lang.name = _(isoLanguages.get(part3=lang.lang_code).name)
else:
languages = None
@ -1485,7 +1476,7 @@ def advanced_search():
def get_cover_via_gdrive(cover_path):
df=gdriveutils.getFileFromEbooksFolder(Gdrive.Instance().drive, cover_path, 'cover.jpg')
if not gdriveutils.session.query(gdriveutils.PermissionAdded).filter(gdriveutils.PermissionAdded.gdrive_id == df['id']).first():
permissions=df.GetPermissions()
df.GetPermissions()
df.InsertPermission({
'type': 'anyone',
'value': 'anyone',
@ -1566,15 +1557,15 @@ def feed_unread_books():
def unread_books(page):
return render_read_books(page, False)
@app.route("/read/<int:book_id>/<format>")
@app.route("/read/<int:book_id>/<book_format>")
@login_required_if_no_ano
def read_book(book_id, format):
def read_book(book_id, book_format):
book = db.session.query(db.Books).filter(db.Books.id == book_id).first()
if book:
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)
if format.lower() == "epub":
if book_format.lower() == "epub":
# check if mimetype file is exists
mime_file = str(book_id) + "/mimetype"
if not os.path.exists(mime_file):
@ -1589,9 +1580,7 @@ def read_book(book_id, format):
try:
os.makedirs(newDir)
except OSError as exception:
if exception.errno == errno.EEXIST:
pass
else:
if not exception.errno == errno.EEXIST:
raise
if fileName:
fd = open(os.path.join(newDir, fileName), "wb")
@ -1599,21 +1588,21 @@ def read_book(book_id, format):
fd.close()
zfile.close()
return render_title_template('read.html', bookid=book_id, title=_(u"Read a Book"))
elif format.lower() == "pdf":
elif book_format.lower() == "pdf":
all_name = str(book_id) + "/" + book.data[0].name + ".pdf"
tmp_file = os.path.join(book_dir, book.data[0].name) + ".pdf"
if not os.path.exists(tmp_file):
pdf_file = os.path.join(config.config_calibre_dir, book.path, book.data[0].name) + ".pdf"
copyfile(pdf_file, tmp_file)
return render_title_template('readpdf.html', pdffile=all_name, title=_(u"Read a Book"))
elif format.lower() == "txt":
elif book_format.lower() == "txt":
all_name = str(book_id) + "/" + book.data[0].name + ".txt"
tmp_file = os.path.join(book_dir, book.data[0].name) + ".txt"
if not os.path.exists(all_name):
txt_file = os.path.join(config.config_calibre_dir, book.path, book.data[0].name) + ".txt"
copyfile(txt_file, tmp_file)
return render_title_template('readtxt.html', txtfile=all_name, title=_(u"Read a Book"))
elif format.lower() == "cbr":
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"
if not os.path.exists(all_name):
@ -1625,13 +1614,13 @@ def read_book(book_id, format):
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>/<format>")
@app.route("/download/<int:book_id>/<book_format>")
@login_required_if_no_ano
@download_required
def get_download_link(book_id, format):
format = format.split(".")[0]
def get_download_link(book_id, book_format):
book_format = book_format.split(".")[0]
book = db.session.query(db.Books).filter(db.Books.id == book_id).first()
data = db.session.query(db.Data).filter(db.Data.book == book.id).filter(db.Data.format == format.upper()).first()
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:
@ -1640,27 +1629,27 @@ def get_download_link(book_id, format):
if len(book.authors) > 0:
file_name = book.authors[0].name + '_' + file_name
file_name = helper.get_valid_filename(file_name)
headers={}
headers = Headers ()
try:
headers["Content-Type"] = mimetypes.types_map['.' + format]
except:
pass
headers["Content-Disposition"] = "attachment; filename*=UTF-8''%s.%s" % (urllib.quote(file_name.encode('utf-8')), format)
headers["Content-Type"] = mimetypes.types_map['.' + book_format]
except KeyError:
headers["Content-Type"] = "application/octet-stream"
headers["Content-Disposition"] = "attachment; filename*=UTF-8''%s.%s" % (quote(file_name.encode('utf-8')), book_format)
if config.config_use_google_drive:
df=gdriveutils.getFileFromEbooksFolder(Gdrive.Instance().drive, book.path, '%s.%s' % (data.name, format))
df=gdriveutils.getFileFromEbooksFolder(Gdrive.Instance().drive, book.path, '%s.%s' % (data.name, book_format))
return do_gdrive_download(df, headers)
else:
response = make_response(send_from_directory(os.path.join(config.config_calibre_dir, book.path), data.name + "." + format))
response = make_response(send_from_directory(os.path.join(config.config_calibre_dir, book.path), data.name + "." + book_format))
response.headers=headers
return response
else:
abort(404)
@app.route("/download/<int:book_id>/<format>/<anyname>")
@app.route("/download/<int:book_id>/<book_format>/<anyname>")
@login_required_if_no_ano
@download_required
def get_download_link_ext(book_id, format, anyname):
return get_download_link(book_id, format)
def get_download_link_ext(book_id, book_format, anyname):
return get_download_link(book_id, book_format)
@app.route('/register', methods=['GET', 'POST'])
def register():
@ -1686,7 +1675,7 @@ def register():
try:
ub.session.add(content)
ub.session.commit()
except Exception as e:
except Exception:
ub.session.rollback()
flash(_(u"An unknown error occured. Please try again later."), category="error")
return render_title_template('register.html', title=_(u"register"))
@ -1814,7 +1803,7 @@ def create_shelf():
ub.session.add(shelf)
ub.session.commit()
flash(_(u"Shelf %(title)s created", title=to_save["title"]), category="success")
except Exception as e:
except Exception:
flash(_(u"There was an error"), category="error")
return render_title_template('shelf_edit.html', shelf=shelf, title=_(u"create a shelf"))
else:
@ -1842,7 +1831,7 @@ def edit_shelf(shelf_id):
try:
ub.session.commit()
flash(_(u"Shelf %(title)s changed", title=to_save["title"]), category="success")
except Exception as e:
except Exception:
flash(_(u"There was an error"), category="error")
return render_title_template('shelf_edit.html', shelf=shelf, title=_(u"Edit a shelf"))
else:
@ -1932,7 +1921,7 @@ def profile():
try:
cur_l = LC.parse(lang.lang_code)
lang.name = cur_l.get_language_name(get_locale())
except Exception as e:
except Exception:
lang.name = _(isoLanguages.get(part3=lang.lang_code).name)
translations = babel.list_translations() + [LC('en')]
for book in content.downloads:
@ -2146,7 +2135,7 @@ def new_user():
try:
cur_l = LC.parse(lang.lang_code)
lang.name = cur_l.get_language_name(get_locale())
except Exception as e:
except Exception:
lang.name = _(isoLanguages.get(part3=lang.lang_code).name)
translations = [LC('en')] + babel.list_translations()
if request.method == "POST":
@ -2251,7 +2240,7 @@ def edit_user(user_id):
try:
cur_l = LC.parse(lang.lang_code)
lang.name = cur_l.get_language_name(get_locale())
except Exception as e:
except Exception:
lang.name = _(isoLanguages.get(part3=lang.lang_code).name)
translations = babel.list_translations() + [LC('en')]
for book in content.downloads:
@ -2373,17 +2362,17 @@ def edit_book(book_id):
db.session.connection().connection.connection.create_function("title_sort", 1, db.title_sort)
cc = db.session.query(db.Custom_Columns).filter(db.Custom_Columns.datatype.notin_(db.cc_exceptions)).all()
if current_user.filter_language() != "all":
filter = db.Books.languages.any(db.Languages.lang_code == current_user.filter_language())
lang_filter = db.Books.languages.any(db.Languages.lang_code == current_user.filter_language())
else:
filter = True
book = db.session.query(db.Books).filter(db.Books.id == book_id).filter(filter).first()
lang_filter = True
book = db.session.query(db.Books).filter(db.Books.id == book_id).filter(lang_filter).first()
author_names = []
if book:
for index in range(0, len(book.languages)):
try:
book.languages[index].language_name = LC.parse(book.languages[index].lang_code).get_language_name(
get_locale())
except Exception as e:
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)
@ -2441,7 +2430,7 @@ def edit_book(book_id):
for lang in languages:
try:
lang.name = LC.parse(lang.lang_code).get_language_name(get_locale()).lower()
except Exception as e:
except Exception:
lang.name = _(isoLanguages.get(part3=lang.lang_code).name).lower()
for inp_lang in input_languages:
if inp_lang == lang.name:
@ -2578,7 +2567,7 @@ def edit_book(book_id):
if config.config_use_google_drive:
updateGdriveCalibreFromLocal()
if "detail_view" in to_save:
return redirect(url_for('show_book', id=book.id))
return redirect(url_for('show_book', book_id=book.id))
else:
return render_title_template('book_edit.html', book=book, authors=author_names, cc=cc,
title=_(u"edit metadata"))
@ -2600,9 +2589,9 @@ def upload():
db.session.connection().connection.connection.create_function("title_sort", 1, db.title_sort)
db.session.connection().connection.connection.create_function('uuid4', 0, lambda: str(uuid4()))
if request.method == 'POST' and 'btn-upload' in request.files:
file = request.files['btn-upload']
if '.' in file.filename:
file_ext = file.filename.rsplit('.', 1)[-1].lower()
requested_file = request.files['btn-upload']
if '.' in requested_file.filename:
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' %
@ -2613,7 +2602,7 @@ def upload():
else:
flash(_('File to be uploaded must have an extension'), category="error")
return redirect(url_for('index'))
meta = uploader.upload(file)
meta = uploader.upload(requested_file)
title = meta.title
author = meta.author
@ -2632,12 +2621,12 @@ def upload():
return redirect(url_for('index'))
try:
copyfile(meta.file_path, saved_filename)
except OSError as e:
except OSError:
flash(_(u"Failed to store file %s (Permission denied)." % saved_filename), category="error")
return redirect(url_for('index'))
try:
os.unlink(meta.file_path)
except OSError as e:
except OSError:
flash(_(u"Failed to delete file %s (Permission denied)." % meta.file_path), category="warning")
file_size = os.path.getsize(saved_filename)
@ -2700,6 +2689,8 @@ def upload():
book_in_shelfs = []
return render_title_template('detail.html', entry=db_book, cc=cc, title=db_book.title,
books_shelfs=book_in_shelfs, )
else:
return redirect(url_for("index"))
def start_gevent():
from gevent.wsgi import WSGIServer