1
0
mirror of https://github.com/janeczku/calibre-web synced 2024-12-25 09:30:31 +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 __name__ == '__main__':
if web.ub.DEVELOPMENT: 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: else:
if gevent_present: if gevent_present:
web.app.logger.info('Attempting to start gevent') web.app.logger.info('Attempting to start gevent')

View File

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

View File

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

View File

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

View File

@ -4,12 +4,11 @@ try:
from apiclient import errors from apiclient import errors
except ImportError: except ImportError:
pass pass
import os, time import os
from ub import config from ub import config
from sqlalchemy import * from sqlalchemy import *
from sqlalchemy import exc
from sqlalchemy.ext.declarative import declarative_base from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy.orm import * from sqlalchemy.orm import *
@ -104,7 +103,7 @@ def getEbooksFolderId(drive=None):
gDriveId.path='/' gDriveId.path='/'
session.merge(gDriveId) session.merge(gDriveId)
session.commit() session.commit()
return return
def getFolderInFolder(parentId, folderName, drive=None): def getFolderInFolder(parentId, folderName, drive=None):
if not drive: if not drive:
@ -113,7 +112,7 @@ def getFolderInFolder(parentId, folderName, drive=None):
drive.auth.Refresh() drive.auth.Refresh()
folder= "title = '%s' and '%s' in parents and mimeType = 'application/vnd.google-apps.folder' and trashed = false" % (folderName.replace("'", "\\'"), parentId) folder= "title = '%s' and '%s' in parents and mimeType = 'application/vnd.google-apps.folder' and trashed = false" % (folderName.replace("'", "\\'"), parentId)
fileList = drive.ListFile({'q': folder}).GetList() fileList = drive.ListFile({'q': folder}).GetList()
return fileList[0] return fileList[0]
def getFile(pathId, fileName, drive=None): def getFile(pathId, fileName, drive=None):
if not drive: if not drive:
@ -165,11 +164,11 @@ def getFileFromEbooksFolder(drive, path, fileName):
if drive.auth.access_token_expired: if drive.auth.access_token_expired:
drive.auth.Refresh() drive.auth.Refresh()
if path: if path:
sqlCheckPath=path if path[-1] =='/' else path + '/' # sqlCheckPath=path if path[-1] =='/' else path + '/'
folderId=getFolderId(path, drive) folderId=getFolderId(path, drive)
else: else:
folderId=getEbooksFolderId(drive) folderId=getEbooksFolderId(drive)
return getFile(folderId, fileName, drive) return getFile(folderId, fileName, drive)
def copyDriveFileRemote(drive, origin_file_id, copy_title): def copyDriveFileRemote(drive, origin_file_id, copy_title):
@ -195,7 +194,6 @@ def downloadFile(drive, path, filename, output):
f.GetContentFile(output) f.GetContentFile(output)
def backupCalibreDbAndOptionalDownload(drive, f=None): def backupCalibreDbAndOptionalDownload(drive, f=None):
pass
if not drive: if not drive:
drive=getDrive() drive=getDrive()
if drive.auth.access_token_expired: if drive.auth.access_token_expired:
@ -203,15 +201,16 @@ def backupCalibreDbAndOptionalDownload(drive, f=None):
metaDataFile="'%s' in parents and title = 'metadata.db' and trashed = false" % getEbooksFolderId() metaDataFile="'%s' in parents and title = 'metadata.db' and trashed = false" % getEbooksFolderId()
fileList = drive.ListFile({'q': metaDataFile}).GetList() fileList = drive.ListFile({'q': metaDataFile}).GetList()
databaseFile=fileList[0] databaseFile=fileList[0]
if f: if f:
databaseFile.GetContentFile(f) databaseFile.GetContentFile(f)
def copyToDrive(drive, uploadFile, createRoot, replaceFiles, def copyToDrive(drive, uploadFile, createRoot, replaceFiles,
ignoreFiles=[], ignoreFiles=[],
parent=None, prevDir=''): parent=None, prevDir=''):
if not drive: if not drive:
drive=getDrive() drive=getDrive()
if drive.auth.access_token_expired: if drive.auth.access_token_expired:
@ -222,7 +221,7 @@ def copyToDrive(drive, uploadFile, createRoot, replaceFiles,
if os.path.isdir(os.path.join(prevDir,uploadFile)): if os.path.isdir(os.path.join(prevDir,uploadFile)):
existingFolder=drive.ListFile({'q' : "title = '%s' and '%s' in parents and trashed = false" % (os.path.basename(uploadFile), parent['id'])}).GetList() existingFolder=drive.ListFile({'q' : "title = '%s' and '%s' in parents and trashed = false" % (os.path.basename(uploadFile), parent['id'])}).GetList()
if len(existingFolder) == 0 and (not isInitial or createRoot): if len(existingFolder) == 0 and (not isInitial or createRoot):
parent = drive.CreateFile({'title': os.path.basename(uploadFile), 'parents' : [{"kind": "drive#fileLink", 'id' : parent['id']}], parent = drive.CreateFile({'title': os.path.basename(uploadFile), 'parents' : [{"kind": "drive#fileLink", 'id' : parent['id']}],
"mimeType": "application/vnd.google-apps.folder" }) "mimeType": "application/vnd.google-apps.folder" })
parent.Upload() parent.Upload()
else: else:
@ -260,7 +259,7 @@ def uploadFileToEbooksFolder(drive, destFile, f):
else: else:
existingFolder=drive.ListFile({'q' : "title = '%s' and '%s' in parents and trashed = false" % (x, parent['id'])}).GetList() existingFolder=drive.ListFile({'q' : "title = '%s' and '%s' in parents and trashed = false" % (x, parent['id'])}).GetList()
if len(existingFolder) == 0: if len(existingFolder) == 0:
parent = drive.CreateFile({'title': x, 'parents' : [{"kind": "drive#fileLink", 'id' : parent['id']}], parent = drive.CreateFile({'title': x, 'parents' : [{"kind": "drive#fileLink", 'id' : parent['id']}],
"mimeType": "application/vnd.google-apps.folder" }) "mimeType": "application/vnd.google-apps.folder" })
parent.Upload() parent.Upload()
else: else:
@ -273,20 +272,19 @@ def watchChange(drive, channel_id, channel_type, channel_address,
drive=getDrive() drive=getDrive()
if drive.auth.access_token_expired: if drive.auth.access_token_expired:
drive.auth.Refresh() drive.auth.Refresh()
"""Watch for all changes to a user's Drive. # Watch for all changes to a user's Drive.
Args: # Args:
service: Drive API service instance. # service: Drive API service instance.
channel_id: Unique string that identifies this channel. # channel_id: Unique string that identifies this channel.
channel_type: Type of delivery mechanism used for this channel. # channel_type: Type of delivery mechanism used for this channel.
channel_address: Address where notifications are delivered. # channel_address: Address where notifications are delivered.
channel_token: An arbitrary string delivered to the target address with # channel_token: An arbitrary string delivered to the target address with
each notification delivered over this channel. Optional. # each notification delivered over this channel. Optional.
channel_address: Address where notifications are delivered. Optional. # channel_address: Address where notifications are delivered. Optional.
Returns: # Returns:
The created channel if successful # The created channel if successful
Raises: # Raises:
apiclient.errors.HttpError: if http request to create channel fails. # apiclient.errors.HttpError: if http request to create channel fails.
"""
body = { body = {
'id': channel_id, 'id': channel_id,
'type': channel_type, 'type': channel_type,
@ -343,8 +341,8 @@ def stopChannel(drive, channel_id, resource_id):
if not drive: if not drive:
drive=getDrive() drive=getDrive()
if drive.auth.access_token_expired: if drive.auth.access_token_expired:
drive.auth.Refresh() drive.auth.Refresh()
service=drive.auth.service # service=drive.auth.service
body = { body = {
'id': channel_id, 'id': channel_id,
'resourceId': resource_id 'resourceId': resource_id
@ -355,16 +353,15 @@ def getChangeById (drive, change_id):
if not drive: if not drive:
drive=getDrive() drive=getDrive()
if drive.auth.access_token_expired: if drive.auth.access_token_expired:
drive.auth.Refresh() drive.auth.Refresh()
"""Print a single Change resource information. # Print a single Change resource information.
#
Args: # Args:
service: Drive API service instance. # service: Drive API service instance.
change_id: ID of the Change resource to retrieve. # change_id: ID of the Change resource to retrieve.
"""
try: try:
change = drive.auth.service.changes().get(changeId=change_id).execute() change = drive.auth.service.changes().get(changeId=change_id).execute()
return change return change
except errors.HttpError, error: except (errors.HttpError, error):
web.app.logger.exception(error) web.app.logger.exception(error)
return None return None

View File

@ -13,6 +13,7 @@ import os
import traceback import traceback
import re import re
import unicodedata import unicodedata
try: try:
from StringIO import StringIO from StringIO import StringIO
from email.MIMEBase import MIMEBase from email.MIMEBase import MIMEBase
@ -43,9 +44,9 @@ import web
try: try:
import unidecode import unidecode
use_unidecode=True use_unidecode = True
except Exception as e: except Exception as e:
use_unidecode=False use_unidecode = False
# Global variables # Global variables
global_task = None global_task = None
@ -80,7 +81,7 @@ def make_mobi(book_id, calibrepath):
file_path = os.path.join(calibrepath, book.path, data.name) file_path = os.path.join(calibrepath, book.path, data.name)
if os.path.exists(file_path + u".epub"): if os.path.exists(file_path + u".epub"):
p = subprocess.Popen((kindlegen + " \"" + file_path + u".epub\" ").encode(sys.getfilesystemencoding()), 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 # Poll process for new output until finished
while True: while True:
nextline = p.stdout.readline() nextline = p.stdout.readline()
@ -93,7 +94,7 @@ def make_mobi(book_id, calibrepath):
if not check or check < 2: if not check or check < 2:
book.data.append(db.Data( book.data.append(db.Data(
name=book.data[0].name, name=book.data[0].name,
format="MOBI", book_format="MOBI",
book=book.id, book=book.id,
uncompressed_size=os.path.getsize(file_path + ".mobi") uncompressed_size=os.path.getsize(file_path + ".mobi")
)) ))
@ -242,7 +243,7 @@ def get_valid_filename(value, replace_whitespace=True):
Returns the given string converted to a string that can be used for a clean Returns the given string converted to a string that can be used for a clean
filename. Limits num characters to 128 max. filename. Limits num characters to 128 max.
""" """
if value[-1:] ==u'.': if value[-1:] == u'.':
value = value[:-1]+u'_' value = value[:-1]+u'_'
if use_unidecode: if use_unidecode:
value=(unidecode.unidecode(value)).strip() value=(unidecode.unidecode(value)).strip()
@ -251,7 +252,7 @@ def get_valid_filename(value, replace_whitespace=True):
value=value.replace(u'ß',u'ss') value=value.replace(u'ß',u'ss')
value = unicodedata.normalize('NFKD', value) value = unicodedata.normalize('NFKD', value)
re_slugify = re.compile('[\W\s-]', re.UNICODE) 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() value = re_slugify.sub('', value).strip()
else: else:
value = unicode(re_slugify.sub('', value).strip()) value = unicode(re_slugify.sub('', value).strip())
@ -266,7 +267,7 @@ def get_sorted_author(value):
regexes = ["^(JR|SR)\.?$","^I{1,3}\.?$","^IV\.?$"] regexes = ["^(JR|SR)\.?$","^I{1,3}\.?$","^IV\.?$"]
combined = "(" + ")|(".join(regexes) + ")" combined = "(" + ")|(".join(regexes) + ")"
value = value.split(" ") value = value.split(" ")
if re.match(combined,value[-1].upper()): if re.match(combined, value[-1].upper()):
value2 = value[-2] + ", " + " ".join(value[:-2]) + " " + value[-1] value2 = value[-2] + ", " + " ".join(value[:-2]) + " " + value[-1]
else: else:
value2 = value[-1] + ", " + " ".join(value[:-1]) value2 = value[-1] + ", " + " ".join(value[:-1])
@ -277,28 +278,29 @@ def update_dir_stucture(book_id, calibrepath):
db.session.connection().connection.connection.create_function("title_sort", 1, db.title_sort) db.session.connection().connection.connection.create_function("title_sort", 1, db.title_sort)
book = db.session.query(db.Books).filter(db.Books.id == book_id).first() book = db.session.query(db.Books).filter(db.Books.id == book_id).first()
path = os.path.join(calibrepath, book.path)#.replace('/',os.path.sep)).replace('\\',os.path.sep) path = os.path.join(calibrepath, book.path)#.replace('/',os.path.sep)).replace('\\',os.path.sep)
authordir = book.path.split('/')[0] authordir = book.path.split('/')[0]
new_authordir = get_valid_filename(book.authors[0].name) new_authordir = get_valid_filename(book.authors[0].name)
titledir = book.path.split('/')[1] titledir = book.path.split('/')[1]
new_titledir = get_valid_filename(book.title) + " (" + str(book_id) + ")" new_titledir = get_valid_filename(book.title) + " (" + str(book_id) + ")"
if titledir != new_titledir: if titledir != new_titledir:
new_title_path = os.path.join(os.path.dirname(path), new_titledir) new_title_path = os.path.join(os.path.dirname(path), new_titledir)
os.rename(path, new_title_path) os.rename(path, new_title_path)
path = new_title_path path = new_title_path
book.path = book.path.split('/')[0] + '/' + new_titledir book.path = book.path.split('/')[0] + '/' + new_titledir
if authordir != new_authordir: if authordir != new_authordir:
new_author_path = os.path.join(os.path.join(calibrepath, new_authordir), os.path.basename(path)) new_author_path = os.path.join(os.path.join(calibrepath, new_authordir), os.path.basename(path))
os.renames(path, new_author_path) os.renames(path, new_author_path)
book.path = new_authordir + '/' + book.path.split('/')[1] book.path = new_authordir + '/' + book.path.split('/')[1]
db.session.commit() db.session.commit()
def update_dir_structure_gdrive(book_id): def update_dir_structure_gdrive(book_id):
db.session.connection().connection.connection.create_function("title_sort", 1, db.title_sort) db.session.connection().connection.connection.create_function("title_sort", 1, db.title_sort)
book = db.session.query(db.Books).filter(db.Books.id == book_id).first() book = db.session.query(db.Books).filter(db.Books.id == book_id).first()
authordir = book.path.split('/')[0] authordir = book.path.split('/')[0]
new_authordir = get_valid_filename(book.authors[0].name) new_authordir = get_valid_filename(book.authors[0].name)
titledir = book.path.split('/')[1] titledir = book.path.split('/')[1]
@ -313,7 +315,7 @@ def update_dir_structure_gdrive(book_id):
if authordir != new_authordir: if authordir != new_authordir:
gFile=gd.getFileFromEbooksFolder(web.Gdrive.Instance().drive,None,authordir) gFile=gd.getFileFromEbooksFolder(web.Gdrive.Instance().drive,None,authordir)
gFile['title']= new_authordir gFile['title'] = new_authordir
gFile.Upload() gFile.Upload()
book.path = new_authordir + '/' + book.path.split('/')[1] book.path = new_authordir + '/' + book.path.split('/')[1]
@ -327,41 +329,44 @@ class Updater(threading.Thread):
def run(self): def run(self):
global global_task global global_task
self.status=1 self.status = 1
r = requests.get('https://api.github.com/repos/janeczku/calibre-web/zipball/master', stream=True) r = requests.get('https://api.github.com/repos/janeczku/calibre-web/zipball/master', stream=True)
fname = re.findall("filename=(.+)", r.headers['content-disposition'])[0] fname = re.findall("filename=(.+)", r.headers['content-disposition'])[0]
self.status=2 self.status = 2
z = zipfile.ZipFile(StringIO(r.content)) z = zipfile.ZipFile(StringIO(r.content))
self.status=3 self.status = 3
tmp_dir = gettempdir() tmp_dir = gettempdir()
z.extractall(tmp_dir) z.extractall(tmp_dir)
self.status=4 self.status = 4
self.update_source(os.path.join(tmp_dir,os.path.splitext(fname)[0]),ub.config.get_main_dir) self.update_source(os.path.join(tmp_dir,os.path.splitext(fname)[0]),ub.config.get_main_dir)
self.status=5 self.status = 5
global_task = 0 global_task = 0
db.session.close() db.session.close()
db.engine.dispose() db.engine.dispose()
ub.session.close() ub.session.close()
ub.engine.dispose() ub.engine.dispose()
self.status=6 self.status = 6
if web.gevent_server: if web.gevent_server:
web.gevent_server.stop() web.gevent_server.stop()
else: else:
# stop tornado server # stop tornado server
server = IOLoop.instance() server = IOLoop.instance()
server.add_callback(server.stop) server.add_callback(server.stop)
self.status=7 self.status = 7
def get_update_status(self): def get_update_status(self):
return self.status return self.status
@classmethod
def file_to_list(self, file): def file_to_list(self, file):
return [x.strip() for x in open(file, 'r') if not x.startswith('#EXT')] return [x.strip() for x in open(file, 'r') if not x.startswith('#EXT')]
@classmethod
def one_minus_two(self, one, two): def one_minus_two(self, one, two):
return [x for x in one if x not in set(two)] return [x for x in one if x not in set(two)]
@classmethod
def reduce_dirs(self, delete_files, new_list): def reduce_dirs(self, delete_files, new_list):
new_delete = [] new_delete = []
for file in delete_files: for file in delete_files:
@ -382,6 +387,7 @@ class Updater(threading.Thread):
break break
return list(set(new_delete)) return list(set(new_delete))
@classmethod
def reduce_files(self, remove_items, exclude_items): def reduce_files(self, remove_items, exclude_items):
rf = [] rf = []
for item in remove_items: for item in remove_items:
@ -389,6 +395,7 @@ class Updater(threading.Thread):
rf.append(item) rf.append(item)
return rf return rf
@classmethod
def moveallfiles(self, root_src_dir, root_dst_dir): def moveallfiles(self, root_src_dir, root_dst_dir):
change_permissions = True change_permissions = True
if sys.platform == "win32" or sys.platform == "darwin": if sys.platform == "win32" or sys.platform == "darwin":
@ -431,8 +438,8 @@ class Updater(threading.Thread):
# destination files # destination files
old_list = list() old_list = list()
exclude = ( exclude = (
'vendor' + os.sep + 'kindlegen.exe', 'vendor' + os.sep + 'kindlegen', os.sep + 'app.db', 'vendor' + os.sep + 'kindlegen.exe', 'vendor' + os.sep + 'kindlegen', os.sep + 'app.db',
os.sep + 'vendor',os.sep + 'calibre-web.log') os.sep + 'vendor', os.sep + 'calibre-web.log')
for root, dirs, files in os.walk(destination, topdown=True): for root, dirs, files in os.walk(destination, topdown=True):
for name in files: for name in files:
old_list.append(os.path.join(root, name).replace(destination, '')) old_list.append(os.path.join(root, name).replace(destination, ''))
@ -462,9 +469,9 @@ class Updater(threading.Thread):
else: else:
try: try:
logging.getLogger('cps.web').debug("Delete file " + item_path) 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) os.remove(item_path)
except Exception as e: except Exception:
logging.getLogger('cps.web').debug("Could not remove:" + item_path) logging.getLogger('cps.web').debug("Could not remove:" + item_path)
shutil.rmtree(source, ignore_errors=True) shutil.rmtree(source, ignore_errors=True)

View File

@ -1,13 +1,15 @@
/** /**
* Created by SpeedProg on 05.04.2015. * Created by SpeedProg on 05.04.2015.
*/ */
/* global Bloodhound */
/* /*
Takes a prefix, query typeahead callback, Bloodhound typeahead adapter Takes a prefix, query typeahead callback, Bloodhound typeahead adapter
and returns the completions it gets from the bloodhound engine prefixed. and returns the completions it gets from the bloodhound engine prefixed.
*/ */
function prefixed_source(prefix, query, cb, bh_adapter) { function prefixedSource(prefix, query, cb, bhAdapter) {
bh_adapter(query, function(retArray){ bhAdapter(query, function(retArray){
var matches = []; var matches = [];
for (var i = 0; i < retArray.length; i++) { for (var i = 0; i < retArray.length; i++) {
var obj = {name : prefix + retArray[i].name}; var obj = {name : prefix + retArray[i].name};
@ -16,184 +18,161 @@ function prefixed_source(prefix, query, cb, bh_adapter) {
cb(matches); cb(matches);
}); });
} }
function get_path(){ function getPath(){
var jsFileLocation = $('script[src*=edit_books]').attr('src'); // the js file 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 jsFileLocation = jsFileLocation.replace("/static/js/edit_books.js", ""); // the js folder path
return jsFileLocation; return jsFileLocation;
} }
var authors = new Bloodhound({ var authors = new Bloodhound({
name: 'authors', name: "authors",
datumTokenizer: function(datum) { datumTokenizer(datum) {
return [datum.name]; return [datum.name];
}, },
queryTokenizer: Bloodhound.tokenizers.whitespace, queryTokenizer: Bloodhound.tokenizers.whitespace,
remote: { remote: {
url: get_path()+'/get_authors_json?q=%QUERY' url: getPath()+"/get_authors_json?q=%QUERY"
} }
}); });
function authors_source(query, cb) { var series = new Bloodhound({
var bh_adapter = authors.ttAdapter(); 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 tokens.splice(tokens.length-1, 1); // remove last element
var prefix = ""; var prefix = "";
for (var i = 0; i < tokens.length; i++) { var newSplit;
var author = tokens[i].trim(); if (split === "&"){
prefix += author + " & "; newSplit = " " + split + " ";
}else{
newSplit = split + " ";
} }
for (var i = 0; i < tokens.length; i++) {
prefixed_source(prefix, current_author, cb, bh_adapter); prefix += tokens[i].trim() + newSplit;
}
prefixedSource(prefix, currentSource, cb, bhAdapter);
} }
var promiseAuthors = authors.initialize();
promiseAuthors.done(function(){
var promise = authors.initialize();
promise.done(function(){
$("#bookAuthor").typeahead( $("#bookAuthor").typeahead(
{ {
highlight: true, minLength: 1, highlight: true, minLength: 1,
hint: true hint: true
}, { }, {
name: 'authors', displayKey: 'name', name: "authors",
source: authors_source displayKey: "name",
source(query, cb){
return sourceSplit(query, cb, "&", authors); //sourceSplit //("&")
} }
) });
}); });
var series = new Bloodhound({ var promiseSeries = series.initialize();
name: 'series', promiseSeries.done(function(){
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(){
$("#series").typeahead( $("#series").typeahead(
{ {
highlight: true, minLength: 0, highlight: true, minLength: 0,
hint: true hint: true
}, { }, {
name: 'series', displayKey: 'name', name: "series",
displayKey: "name",
source: series.ttAdapter() source: series.ttAdapter()
} }
) );
}); });
var tags = new Bloodhound({ var promiseTags = tags.initialize();
name: 'tags', promiseTags.done(function(){
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(){
$("#tags").typeahead( $("#tags").typeahead(
{ {
highlight: true, minLength: 0, highlight: true, minLength: 0,
hint: true hint: true
}, { }, {
name: 'tags', displayKey: 'name', name: "tags",
source: tag_source displayKey: "name",
} source(query, cb){
) return sourceSplit(query, cb, ",", tags);
}); }
});
});
var languages = new Bloodhound({ var promiseLanguages = languages.initialize();
name: 'languages', promiseLanguages.done(function(){
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;
}
}
});
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(){
$("#languages").typeahead( $("#languages").typeahead(
{ {
highlight: true, minLength: 0, highlight: true, minLength: 0,
hint: true hint: true
}, { }, {
name: 'languages', displayKey: 'name', name: "languages",
source: language_source displayKey: "name",
} source(query, cb){
) return sourceSplit(query, cb, ",", languages); //(",")
}); }
});
});
$('form').on('change input typeahead:selected', function(data){ $("form").on("change input typeahead:selected", function(data){
form = $('form').serialize(); var form = $("form").serialize();
$.getJSON( get_path()+"/get_matching_tags", form, function( data ) { $.getJSON( getPath()+"/get_matching_tags", form, function( data ) {
$('.tags_click').each(function() { $(".tags_click").each(function() {
if ($.inArray(parseInt($(this).children('input').first().val(), 10), data.tags) == -1 ) { if ($.inArray(parseInt($(this).children("input").first().val(), 10), data.tags) === -1 ) {
if (!($(this).hasClass('active'))) { if (!($(this).hasClass("active"))) {
$(this).addClass('disabled'); $(this).addClass("disabled");
} }
} }
else { 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 * 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) * Douban Books api document: https://developers.douban.com/wiki/?title=book_v2 (Chinese Only)
*/ */
/* global i18nMsg */
$(document).ready(function () { $(document).ready(function () {
var msg = i18n_msg; var msg = i18nMsg;
var douban = 'https://api.douban.com'; var douban = "https://api.douban.com";
var db_search = '/v2/book/search'; var dbSearch = "/v2/book/search";
var db_get_info = '/v2/book/'; // var dbGetInfo = "/v2/book/";
var db_get_info_by_isbn = '/v2/book/isbn/ '; // var db_get_info_by_isbn = "/v2/book/isbn/ ";
var db_done = false; var dbDone = false;
var google = 'https://www.googleapis.com/'; var google = "https://www.googleapis.com/";
var gg_search = '/books/v1/volumes'; var ggSearch = "/books/v1/volumes";
var gg_get_info = '/books/v1/volumes/'; // var gg_get_info = "/books/v1/volumes/";
var gg_done = false; var ggDone = false;
var db_results = []; var dbResults = [];
var gg_results = []; var ggResults = [];
var show_flag = 0; var showFlag = 0;
String.prototype.replaceAll = function (s1, s2) {   String.prototype.replaceAll = function (s1, s2) {
return this.replace(new RegExp(s1, "gm"), s2);   return this.replace(new RegExp(s1, "gm"), s2);
} };
gg_search_book = function (title) { function showResult () {
title = title.replaceAll(/\s+/, '+'); var book;
var url = google + gg_search + '?q=' + title; var i;
$.ajax({ var bookHtml;
url: url, showFlag++;
type: "GET", if (showFlag === 1) {
dataType: "jsonp", $("#meta-info").html("<ul id=\"book-list\" class=\"media-list\"></ul>");
jsonp: 'callback',
success: function (data) {
gg_results = data.items;
},
complete: function () {
gg_done = true;
show_result();
}
});
}
get_meta = function (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);
if (meta.volumeInfo.categories) {
var tags = meta.volumeInfo.categories.join(',');
$('#tags').val(tags);
}
if (meta.volumeInfo.averageRating) {
$('#rating').val(Math.round(meta.volumeInfo.averageRating));
}
return;
} }
if (source == 'douban') { if (ggDone && dbDone) {
meta = db_results[id]; if (!ggResults && !dbResults) {
$('#description').val(meta.summary); $("#meta-info").html("<p class=\"text-danger\">"+ msg.no_result +"</p>");
$('#bookAuthor').val(meta.author.join(' & '));
$('#book_title').val(meta.title);
var tags = '';
for (var i = 0; i < meta.tags.length; i++) {
tags = tags + meta.tags[i].title + ',';
}
$('#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';
$.ajax({
url: url,
type: "GET",
dataType: "jsonp",
jsonp: 'callback',
success: function (data) {
db_results = data.books;
},
error: function () {
$('#meta-info').html('<p class="text-danger">'+ msg.search_error+'!</p>');
},
complete: function () {
db_done = true;
show_result();
}
});
}
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; return;
} }
} }
if (gg_done && gg_results.length > 0) { if (ggDone && ggResults.length > 0) {
for (var i = 0; i < gg_results.length; i++) { for (i = 0; i < ggResults.length; i++) {
var book = gg_results[i]; book = ggResults[i];
var book_cover; var bookCover;
if (book.volumeInfo.imageLinks) { if (book.volumeInfo.imageLinks) {
book_cover = book.volumeInfo.imageLinks.thumbnail; bookCover = book.volumeInfo.imageLinks.thumbnail;
} else { } else {
book_cover = '/static/generic_cover.jpg'; bookCover = "/static/generic_cover.jpg";
} }
var book_html = '<li class="media">' + bookHtml = "<li class=\"media\">" +
'<img class="pull-left img-responsive" data-toggle="modal" data-target="#metaModal" src="' + "<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",' + bookCover + "\" alt=\"Cover\" style=\"width:100px;height:150px\" onclick='javascript:getMeta(\"google\"," +
i + ')\'>' + i + ")\\>\"" +
'<div class="media-body">' + "<div class=\"media-body\">" +
'<h4 class="media-heading"><a href="https://books.google.com/books?id=' + "<h4 class=\"media-heading\"><a href=\"https://books.google.com/books?id=" +
book.id + '" target="_blank">' + book.volumeInfo.title + '</a></h4>' + book.id + "\" target=\"_blank\">" + book.volumeInfo.title + "</a></h4>" +
'<p>'+ msg.author +'' + book.volumeInfo.authors + '</p>' + "<p>"+ msg.author +"" + book.volumeInfo.authors + "</p>" +
'<p>'+ msg.publisher + '' + book.volumeInfo.publisher + '</p>' + "<p>"+ msg.publisher + "" + book.volumeInfo.publisher + "</p>" +
'<p>'+ msg.description + ':' + book.volumeInfo.description + '</p>' + "<p>"+ msg.description + ":" + book.volumeInfo.description + "</p>" +
'<p>'+ msg.source + ':<a href="https://books.google.com" target="_blank">Google Books</a></p>' + "<p>"+ msg.source + ":<a href=\"https://books.google.com\" target=\"_blank\">Google Books</a></p>" +
'</div>' + "</div>" +
'</li>'; "</li>";
$("#book-list").append(book_html); $("#book-list").append(bookHtml);
} }
gg_done = false; ggDone = false;
} }
if (db_done && db_results.length > 0) { if (dbDone && dbResults.length > 0) {
for (var i = 0; i < db_results.length; i++) { for (i = 0; i < dbResults.length; i++) {
var book = db_results[i]; book = dbResults[i];
var book_html = '<li class="media">' + bookHtml = "<li class=\"media\">" +
'<img class="pull-left img-responsive" data-toggle="modal" data-target="#metaModal" src="' + "<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",' + book.image + "\" alt=\"Cover\" style=\"width:100px;height: 150px\" onclick='javascript:getMeta(\"douban\"," +
i + ')\'>' + i + ")\\'>" +
'<div class="media-body">' + "<div class=\"media-body\">" +
'<h4 class="media-heading"><a href="https://book.douban.com/subject/' + "<h4 class=\"media-heading\"><a href=\"https://book.douban.com/subject/" +
book.id + '" target="_blank">' + book.title + '</a></h4>' + book.id + "\" target=\"_blank\">" + book.title + "</a></h4>" +
'<p>' + msg.author + '' + book.author + '</p>' + "<p>" + msg.author + "" + book.author + "</p>" +
'<p>' + msg.publisher + '' + book.publisher + '</p>' + "<p>" + msg.publisher + "" + book.publisher + "</p>" +
'<p>' + msg.description + ':' + book.summary + '</p>' + "<p>" + msg.description + ":" + book.summary + "</p>" +
'<p>' + msg.source + ':<a href="https://book.douban.com" target="_blank">Douban Books</a></p>' + "<p>" + msg.source + ":<a href=\"https://book.douban.com\" target=\"_blank\">Douban Books</a></p>" +
'</div>' + "</div>" +
'</li>'; "</li>";
$("#book-list").append(book_html); $("#book-list").append(bookHtml);
} }
db_done = false; dbDone = false;
} }
} }
$('#do-search').click(function () { function ggSearchBook (title) {
var keyword = $('#keyword').val(); title = title.replaceAll(/\s+/, "+");
var url = google + ggSearch + "?q=" + title;
$.ajax({
url,
type: "GET",
dataType: "jsonp",
jsonp: "callback",
success (data) {
ggResults = data.items;
},
complete () {
ggDone = true;
showResult();
}
});
}
function getMeta (source, id) {
var meta;
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) {
tags = meta.volumeInfo.categories.join(",");
$("#tags").val(tags);
}
if (meta.volumeInfo.averageRating) {
$("#rating").val(Math.round(meta.volumeInfo.averageRating));
}
return;
}
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").val(tags);
$("#rating").val(Math.round(meta.rating.average / 2));
return;
}
}
function dbSearchBook (title) {
var url = douban + dbSearch + "?q=" + title + "&fields=all&count=10";
$.ajax({
url,
type: "GET",
dataType: "jsonp",
jsonp: "callback",
success (data) {
dbResults = data.books;
},
error () {
$("#meta-info").html("<p class=\"text-danger\">"+ msg.search_error+"!</p>");
},
complete () {
dbDone = true;
showResult();
}
});
}
function doSearch (keyword) {
showFlag = 0;
$("#meta-info").text(msg.loading);
// var keyword = $("#keyword").val();
if (keyword) { 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 () { $("#get_meta").click(function () {
var book_title = $('#book_title').val(); var bookTitle = $("#book_title").val();
if (book_title) { if (bookTitle) {
$('#keyword').val(book_title); $("#keyword").val(bookTitle);
do_search(book_title); doSearch(bookTitle);
} }
}); });

View File

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

View File

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

View File

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

View File

@ -15,7 +15,7 @@
<h2>{{entry.title}}</h2> <h2>{{entry.title}}</h2>
<p class="author"> <p class="author">
{% for author in entry.authors %} {% 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 %} {% if not loop.last %}
&amp; &amp;
{% endif %} {% endif %}
@ -37,7 +37,7 @@
{% endif %} {% endif %}
{% if entry.series|length > 0 %} {% 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 %} {% endif %}
{% if entry.languages.__len__() > 0 %} {% if entry.languages.__len__() > 0 %}
@ -65,7 +65,7 @@
<span class="glyphicon glyphicon-tags"></span> <span class="glyphicon glyphicon-tags"></span>
{% for tag in entry.tags %} {% 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%} {%endfor%}
</div> </div>
</p> </p>
@ -110,7 +110,7 @@
{% if not g.user.is_anonymous() %} {% if not g.user.is_anonymous() %}
<p> <p>
<div class="custom_columns" id="have_read_container"> <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 %} > <input id="have_read_cb" type="checkbox" {% if have_read %}checked{% endif %} >
<label for="have_read_cb">{{_('Read')}}</label> <label for="have_read_cb">{{_('Read')}}</label>
</form> </form>
@ -136,7 +136,7 @@
</button> </button>
<ul class="dropdown-menu" aria-labelledby="btnGroupDrop1"> <ul class="dropdown-menu" aria-labelledby="btnGroupDrop1">
{% for format in entry.data %} {% 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%} {%endfor%}
</ul> </ul>
</div> </div>
@ -154,7 +154,7 @@
<ul class="dropdown-menu" aria-labelledby="btnGroupDrop2"> <ul class="dropdown-menu" aria-labelledby="btnGroupDrop2">
{% for format in entry.data %} {% 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' %} {%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 %} {% endif %}
{%endfor%} {%endfor%}
</ul> </ul>

View File

@ -8,14 +8,14 @@
<div class="col-sm-3 col-lg-2 col-xs-6 book"> <div class="col-sm-3 col-lg-2 col-xs-6 book">
<div class="cover"> <div class="cover">
{% if entry.has_cover is defined %} {% 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('\\','/')) }}" /> <img src="{{ url_for('get_cover', cover_path=entry.path.replace('\\','/')) }}" />
</a> </a>
{% endif %} {% endif %}
</div> </div>
<div class="meta"> <div class="meta">
<p class="title">{{entry.title|shortentitle}}</p> <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 %} {% if entry.ratings.__len__() > 0 %}
<div class="rating"> <div class="rating">
{% for number in range((entry.ratings[0].rating/2)|int(2)) %} {% 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"/> <link type="image/jpeg" href="{{url_for('feed_get_cover', book_id=entry.id)}}" rel="http://opds-spec.org/image/thumbnail"/>
{% endif %} {% endif %}
{% for format in entry.data %} {% 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}}"/> length="{{format.uncompressed_size}}" mtime="{{entry.timestamp}}" type="{{format.format|lower|mimetype}}"/>
{% endfor %} {% endfor %}
</entry> </entry>
@ -65,9 +65,9 @@
{% for entry in listelements %} {% for entry in listelements %}
<entry> <entry>
<title>{{entry.name}}</title> <title>{{entry.name}}</title>
<id>{{ url_for(folder, id=entry.id) }}</id> <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, id=entry.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, id=entry.id)}}" rel="subsection"/> <link type="application/atom+xml" href="{{url_for(folder, book_id=entry.id)}}" rel="subsection"/>
</entry> </entry>
{% endfor %} {% endfor %}
</feed> </feed>

View File

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

View File

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

View File

@ -10,7 +10,7 @@
{% endif %} {% endif %}
<div class="row"> <div class="row">
<div class="col-xs-1" align="left"><span class="badge">{{entry.count}}</span></div> <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> </div>
{% endfor %} {% endfor %}
</div> </div>

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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