Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
d106ada9ed | ||
|
|
ed91048a63 | ||
|
|
e6ff2f1d90 | ||
|
|
1030e195a5 | ||
|
|
479b4b7d82 | ||
|
|
a42ebdc096 | ||
|
|
49ba221e85 | ||
|
|
cb0403a924 | ||
|
|
a2c7741e21 | ||
|
|
55bb8d4590 | ||
|
|
b80bfa5260 | ||
|
|
f941908f73 | ||
|
|
c2bfb29726 | ||
|
|
204de4aef6 | ||
|
|
8b6d165d64 | ||
|
|
6a7b8c7870 | ||
|
|
cc5c2c0f65 | ||
|
|
c748716ad2 | ||
|
|
544405f5aa | ||
|
|
67736fe187 | ||
|
|
89516fc2d6 | ||
|
|
e1e79a73e8 | ||
|
|
0b8624b542 | ||
|
|
91ebe33879 | ||
|
|
55bdd13fb1 | ||
|
|
4552208c02 | ||
|
|
436c2671d1 | ||
|
|
a669eacde9 | ||
|
|
5188c62bea | ||
|
|
e754ce313c | ||
|
|
dc933f3d66 | ||
|
|
afc79416b2 | ||
|
|
93720c9fdf | ||
|
|
cdfb6bf3e3 | ||
|
|
858c80bb20 | ||
|
|
f7e640fd98 | ||
|
|
439ce19b5d | ||
|
|
4b301a7961 | ||
|
|
6c493df2f5 | ||
|
|
8923e71220 | ||
|
|
843a93fe28 | ||
|
|
fa36072699 | ||
|
|
8ab9365688 | ||
|
|
169ace777a | ||
|
|
ec2103a3d0 | ||
|
|
a0a15b4598 | ||
|
|
76d40411dd | ||
|
|
8612dce97c | ||
|
|
0b0e62fdf2 | ||
|
|
6f80a63ca6 | ||
|
|
3dfc2f2580 | ||
|
|
5b0a0e2139 | ||
|
|
0744e84ba9 | ||
|
|
6f0b3bbda0 | ||
|
|
466af21548 | ||
|
|
6269a83c3d | ||
|
|
11b770b3fb | ||
|
|
fa276fd0d3 | ||
|
|
361a1243d7 | ||
|
|
bd7b0175a1 | ||
|
|
f5cf62f209 | ||
|
|
15aa49e163 | ||
|
|
3272b4ca1e | ||
|
|
d3ed0317bb | ||
|
|
e5bc1ae020 | ||
|
|
fad62a0909 | ||
|
|
f6fbe2e135 | ||
|
|
17b7333992 | ||
|
|
ae279c5123 | ||
|
|
ae7c4908f9 | ||
|
|
ea1a882353 | ||
|
|
9a0e8fefb7 | ||
|
|
c07cb23ef2 | ||
|
|
5a863541de | ||
|
|
4c80db4324 | ||
|
|
2505f0c1e4 | ||
|
|
7fb9bf8889 | ||
|
|
237502ae6b | ||
|
|
56826f67fc | ||
|
|
0fa45f5800 | ||
|
|
3593f904dc | ||
|
|
7f3e98b477 | ||
|
|
83a06fa6c9 | ||
|
|
d508b909d1 | ||
|
|
6842a8ac91 | ||
|
|
47b5a5652b | ||
|
|
d58d792f56 | ||
|
|
6fc4b318e8 | ||
|
|
6661be91ff | ||
|
|
ea98323c06 |
@@ -35,6 +35,7 @@ logger = logging.getLogger("book_formats")
|
|||||||
try:
|
try:
|
||||||
from wand.image import Image
|
from wand.image import Image
|
||||||
from wand import version as ImageVersion
|
from wand import version as ImageVersion
|
||||||
|
from wand.exceptions import PolicyError
|
||||||
use_generic_pdf_cover = False
|
use_generic_pdf_cover = False
|
||||||
except (ImportError, RuntimeError) as e:
|
except (ImportError, RuntimeError) as e:
|
||||||
logger.warning('cannot import Image, generating pdf covers for pdf uploads will not work: %s', e)
|
logger.warning('cannot import Image, generating pdf covers for pdf uploads will not work: %s', e)
|
||||||
@@ -61,6 +62,13 @@ except ImportError as e:
|
|||||||
logger.warning('cannot import fb2, extracting fb2 metadata will not work: %s', e)
|
logger.warning('cannot import fb2, extracting fb2 metadata will not work: %s', e)
|
||||||
use_fb2_meta = False
|
use_fb2_meta = False
|
||||||
|
|
||||||
|
try:
|
||||||
|
from PIL import Image
|
||||||
|
from PIL import __version__ as PILversion
|
||||||
|
use_PIL = True
|
||||||
|
except ImportError:
|
||||||
|
use_PIL = False
|
||||||
|
|
||||||
|
|
||||||
def process(tmp_file_path, original_file_name, original_file_extension):
|
def process(tmp_file_path, original_file_name, original_file_extension):
|
||||||
meta = None
|
meta = None
|
||||||
@@ -100,7 +108,7 @@ def default_meta(tmp_file_path, original_file_name, original_file_extension):
|
|||||||
def pdf_meta(tmp_file_path, original_file_name, original_file_extension):
|
def pdf_meta(tmp_file_path, original_file_name, original_file_extension):
|
||||||
|
|
||||||
if use_pdf_meta:
|
if use_pdf_meta:
|
||||||
pdf = PdfFileReader(open(tmp_file_path, 'rb'))
|
pdf = PdfFileReader(open(tmp_file_path, 'rb'), strict=False)
|
||||||
doc_info = pdf.getDocumentInfo()
|
doc_info = pdf.getDocumentInfo()
|
||||||
else:
|
else:
|
||||||
doc_info = None
|
doc_info = None
|
||||||
@@ -130,12 +138,58 @@ def pdf_preview(tmp_file_path, tmp_dir):
|
|||||||
if use_generic_pdf_cover:
|
if use_generic_pdf_cover:
|
||||||
return None
|
return None
|
||||||
else:
|
else:
|
||||||
cover_file_name = os.path.splitext(tmp_file_path)[0] + ".cover.jpg"
|
if use_PIL:
|
||||||
with Image(filename=tmp_file_path + "[0]", resolution=150) as img:
|
try:
|
||||||
img.compression_quality = 88
|
input1 = PdfFileReader(open(tmp_file_path, 'rb'), strict=False)
|
||||||
img.save(filename=os.path.join(tmp_dir, cover_file_name))
|
page0 = input1.getPage(0)
|
||||||
return cover_file_name
|
xObject = page0['/Resources']['/XObject'].getObject()
|
||||||
|
|
||||||
|
for obj in xObject:
|
||||||
|
if xObject[obj]['/Subtype'] == '/Image':
|
||||||
|
size = (xObject[obj]['/Width'], xObject[obj]['/Height'])
|
||||||
|
data = xObject[obj]._data # xObject[obj].getData()
|
||||||
|
if xObject[obj]['/ColorSpace'] == '/DeviceRGB':
|
||||||
|
mode = "RGB"
|
||||||
|
else:
|
||||||
|
mode = "P"
|
||||||
|
if '/Filter' in xObject[obj]:
|
||||||
|
if xObject[obj]['/Filter'] == '/FlateDecode':
|
||||||
|
img = Image.frombytes(mode, size, data)
|
||||||
|
cover_file_name = os.path.splitext(tmp_file_path)[0] + ".cover.png"
|
||||||
|
img.save(filename=os.path.join(tmp_dir, cover_file_name))
|
||||||
|
return cover_file_name
|
||||||
|
# img.save(obj[1:] + ".png")
|
||||||
|
elif xObject[obj]['/Filter'] == '/DCTDecode':
|
||||||
|
cover_file_name = os.path.splitext(tmp_file_path)[0] + ".cover.jpg"
|
||||||
|
img = open(cover_file_name, "wb")
|
||||||
|
img.write(data)
|
||||||
|
img.close()
|
||||||
|
return cover_file_name
|
||||||
|
elif xObject[obj]['/Filter'] == '/JPXDecode':
|
||||||
|
cover_file_name = os.path.splitext(tmp_file_path)[0] + ".cover.jp2"
|
||||||
|
img = open(cover_file_name, "wb")
|
||||||
|
img.write(data)
|
||||||
|
img.close()
|
||||||
|
return cover_file_name
|
||||||
|
else:
|
||||||
|
img = Image.frombytes(mode, size, data)
|
||||||
|
cover_file_name = os.path.splitext(tmp_file_path)[0] + ".cover.png"
|
||||||
|
img.save(filename=os.path.join(tmp_dir, cover_file_name))
|
||||||
|
return cover_file_name
|
||||||
|
except Exception as ex:
|
||||||
|
print(ex)
|
||||||
|
try:
|
||||||
|
cover_file_name = os.path.splitext(tmp_file_path)[0] + ".cover.jpg"
|
||||||
|
with Image(filename=tmp_file_path + "[0]", resolution=150) as img:
|
||||||
|
img.compression_quality = 88
|
||||||
|
img.save(filename=os.path.join(tmp_dir, cover_file_name))
|
||||||
|
return cover_file_name
|
||||||
|
except PolicyError as ex:
|
||||||
|
logger.warning('Pdf extraction forbidden by Imagemagick policy: %s', ex)
|
||||||
|
return None
|
||||||
|
except Exception as ex:
|
||||||
|
logger.warning('Cannot extract cover image, using default: %s', ex)
|
||||||
|
return None
|
||||||
|
|
||||||
def get_versions():
|
def get_versions():
|
||||||
if not use_generic_pdf_cover:
|
if not use_generic_pdf_cover:
|
||||||
@@ -152,4 +206,12 @@ def get_versions():
|
|||||||
XVersion = 'v'+'.'.join(map(str, lxmlversion))
|
XVersion = 'v'+'.'.join(map(str, lxmlversion))
|
||||||
else:
|
else:
|
||||||
XVersion = _(u'not installed')
|
XVersion = _(u'not installed')
|
||||||
return {'Image Magick': IVersion, 'PyPdf': PVersion, 'lxml':XVersion, 'Wand Version': WVersion}
|
if use_PIL:
|
||||||
|
PILVersion = 'v' + PILversion
|
||||||
|
else:
|
||||||
|
PILVersion = _(u'not installed')
|
||||||
|
return {'Image Magick': IVersion,
|
||||||
|
'PyPdf': PVersion,
|
||||||
|
'lxml':XVersion,
|
||||||
|
'Wand': WVersion,
|
||||||
|
'Pillow': PILVersion}
|
||||||
|
|||||||
@@ -24,21 +24,34 @@ import uploader
|
|||||||
|
|
||||||
|
|
||||||
def extractCover(tmp_file_name, original_file_extension):
|
def extractCover(tmp_file_name, original_file_extension):
|
||||||
|
cover_data = None
|
||||||
if original_file_extension.upper() == '.CBZ':
|
if original_file_extension.upper() == '.CBZ':
|
||||||
cf = zipfile.ZipFile(tmp_file_name)
|
cf = zipfile.ZipFile(tmp_file_name)
|
||||||
compressed_name = cf.namelist()[0]
|
for name in cf.namelist():
|
||||||
cover_data = cf.read(compressed_name)
|
ext = os.path.splitext(name)
|
||||||
|
if len(ext) > 1:
|
||||||
|
extension = ext[1].lower()
|
||||||
|
if extension == '.jpg':
|
||||||
|
cover_data = cf.read(name)
|
||||||
|
break
|
||||||
elif original_file_extension.upper() == '.CBT':
|
elif original_file_extension.upper() == '.CBT':
|
||||||
cf = tarfile.TarFile(tmp_file_name)
|
cf = tarfile.TarFile(tmp_file_name)
|
||||||
compressed_name = cf.getnames()[0]
|
for name in cf.getnames():
|
||||||
cover_data = cf.extractfile(compressed_name).read()
|
ext = os.path.splitext(name)
|
||||||
|
if len(ext) > 1:
|
||||||
|
extension = ext[1].lower()
|
||||||
|
if extension == '.jpg':
|
||||||
|
cover_data = cf.extractfile(name).read()
|
||||||
|
break
|
||||||
|
|
||||||
prefix = os.path.dirname(tmp_file_name)
|
prefix = os.path.dirname(tmp_file_name)
|
||||||
|
if cover_data:
|
||||||
tmp_cover_name = prefix + '/cover' + os.path.splitext(compressed_name)[1]
|
tmp_cover_name = prefix + '/cover' + extension
|
||||||
image = open(tmp_cover_name, 'wb')
|
image = open(tmp_cover_name, 'wb')
|
||||||
image.write(cover_data)
|
image.write(cover_data)
|
||||||
image.close()
|
image.close()
|
||||||
|
else:
|
||||||
|
tmp_cover_name = None
|
||||||
return tmp_cover_name
|
return tmp_cover_name
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -27,6 +27,7 @@ import ast
|
|||||||
from ub import config
|
from ub import config
|
||||||
import ub
|
import ub
|
||||||
import sys
|
import sys
|
||||||
|
import unidecode
|
||||||
|
|
||||||
session = None
|
session = None
|
||||||
cc_exceptions = ['datetime', 'comments', 'float', 'composite', 'series']
|
cc_exceptions = ['datetime', 'comments', 'float', 'composite', 'series']
|
||||||
@@ -46,7 +47,7 @@ def title_sort(title):
|
|||||||
|
|
||||||
|
|
||||||
def lcase(s):
|
def lcase(s):
|
||||||
return s.lower()
|
return unidecode.unidecode(s.lower())
|
||||||
|
|
||||||
|
|
||||||
def ucase(s):
|
def ucase(s):
|
||||||
@@ -112,6 +113,8 @@ class Identifiers(Base):
|
|||||||
return u"Google Books"
|
return u"Google Books"
|
||||||
elif self.type == "kobo":
|
elif self.type == "kobo":
|
||||||
return u"Kobo"
|
return u"Kobo"
|
||||||
|
if self.type == "lubimyczytac":
|
||||||
|
return u"Lubimyczytac"
|
||||||
else:
|
else:
|
||||||
return self.type
|
return self.type
|
||||||
|
|
||||||
@@ -130,6 +133,8 @@ class Identifiers(Base):
|
|||||||
return u"https://books.google.com/books?id={0}".format(self.val)
|
return u"https://books.google.com/books?id={0}".format(self.val)
|
||||||
elif self.type == "kobo":
|
elif self.type == "kobo":
|
||||||
return u"https://www.kobo.com/ebook/{0}".format(self.val)
|
return u"https://www.kobo.com/ebook/{0}".format(self.val)
|
||||||
|
elif self.type == "lubimyczytac":
|
||||||
|
return u" http://lubimyczytac.pl/ksiazka/{0}".format(self.val)
|
||||||
elif self.type == "url":
|
elif self.type == "url":
|
||||||
return u"{0}".format(self.val)
|
return u"{0}".format(self.val)
|
||||||
else:
|
else:
|
||||||
@@ -355,8 +360,8 @@ def setup_db():
|
|||||||
ub.session.commit()
|
ub.session.commit()
|
||||||
config.loadSettings()
|
config.loadSettings()
|
||||||
conn.connection.create_function('title_sort', 1, title_sort)
|
conn.connection.create_function('title_sort', 1, title_sort)
|
||||||
conn.connection.create_function('lower', 1, lcase)
|
# conn.connection.create_function('lower', 1, lcase)
|
||||||
conn.connection.create_function('upper', 1, ucase)
|
# conn.connection.create_function('upper', 1, ucase)
|
||||||
|
|
||||||
if not cc_classes:
|
if not cc_classes:
|
||||||
cc = conn.execute("SELECT id, datatype FROM custom_columns")
|
cc = conn.execute("SELECT id, datatype FROM custom_columns")
|
||||||
|
|||||||
@@ -21,7 +21,7 @@ import zipfile
|
|||||||
from lxml import etree
|
from lxml import etree
|
||||||
import os
|
import os
|
||||||
import uploader
|
import uploader
|
||||||
from iso639 import languages as isoLanguages
|
import isoLanguages
|
||||||
|
|
||||||
|
|
||||||
def extractCover(zipFile, coverFile, coverpath, tmp_file_name):
|
def extractCover(zipFile, coverFile, coverpath, tmp_file_name):
|
||||||
|
|||||||
@@ -20,7 +20,7 @@
|
|||||||
try:
|
try:
|
||||||
from pydrive.auth import GoogleAuth
|
from pydrive.auth import GoogleAuth
|
||||||
from pydrive.drive import GoogleDrive
|
from pydrive.drive import GoogleDrive
|
||||||
from pydrive.auth import RefreshError
|
from pydrive.auth import RefreshError, InvalidConfigError
|
||||||
from apiclient import errors
|
from apiclient import errors
|
||||||
gdrive_support = True
|
gdrive_support = True
|
||||||
except ImportError:
|
except ImportError:
|
||||||
@@ -31,12 +31,9 @@ from ub import config
|
|||||||
import cli
|
import cli
|
||||||
import shutil
|
import shutil
|
||||||
from flask import Response, stream_with_context
|
from flask import Response, stream_with_context
|
||||||
|
|
||||||
from sqlalchemy import *
|
from sqlalchemy import *
|
||||||
from sqlalchemy.ext.declarative import declarative_base
|
from sqlalchemy.ext.declarative import declarative_base
|
||||||
from sqlalchemy.orm import *
|
from sqlalchemy.orm import *
|
||||||
|
|
||||||
|
|
||||||
import web
|
import web
|
||||||
|
|
||||||
class Singleton:
|
class Singleton:
|
||||||
@@ -166,7 +163,10 @@ def getDrive(drive=None, gauth=None):
|
|||||||
# Save the current credentials to a file
|
# Save the current credentials to a file
|
||||||
return GoogleDrive(gauth)
|
return GoogleDrive(gauth)
|
||||||
if drive.auth.access_token_expired:
|
if drive.auth.access_token_expired:
|
||||||
drive.auth.Refresh()
|
try:
|
||||||
|
drive.auth.Refresh()
|
||||||
|
except RefreshError as e:
|
||||||
|
web.app.logger.error("Google Drive error: " + e.message)
|
||||||
return drive
|
return drive
|
||||||
|
|
||||||
def listRootFolders():
|
def listRootFolders():
|
||||||
@@ -184,7 +184,7 @@ def getFolderInFolder(parentId, folderName, drive):
|
|||||||
# drive = getDrive(drive)
|
# drive = getDrive(drive)
|
||||||
query=""
|
query=""
|
||||||
if folderName:
|
if folderName:
|
||||||
query = "title = '%s' and " % folderName.replace("'", "\\'")
|
query = "title = '%s' and " % folderName.replace("'", r"\'")
|
||||||
folder = query + "'%s' in parents and mimeType = 'application/vnd.google-apps.folder'" \
|
folder = query + "'%s' in parents and mimeType = 'application/vnd.google-apps.folder'" \
|
||||||
" and trashed = false" % parentId
|
" and trashed = false" % parentId
|
||||||
fileList = drive.ListFile({'q': folder}).GetList()
|
fileList = drive.ListFile({'q': folder}).GetList()
|
||||||
@@ -211,7 +211,7 @@ def getEbooksFolderId(drive=None):
|
|||||||
|
|
||||||
|
|
||||||
def getFile(pathId, fileName, drive):
|
def getFile(pathId, fileName, drive):
|
||||||
metaDataFile = "'%s' in parents and trashed = false and title = '%s'" % (pathId, fileName.replace("'", "\\'"))
|
metaDataFile = "'%s' in parents and trashed = false and title = '%s'" % (pathId, fileName.replace("'", r"\'"))
|
||||||
fileList = drive.ListFile({'q': metaDataFile}).GetList()
|
fileList = drive.ListFile({'q': metaDataFile}).GetList()
|
||||||
if fileList.__len__() == 0:
|
if fileList.__len__() == 0:
|
||||||
return None
|
return None
|
||||||
@@ -246,7 +246,7 @@ def getFolderId(path, drive):
|
|||||||
dbChange = True
|
dbChange = True
|
||||||
currentFolderId = currentFolder['id']
|
currentFolderId = currentFolder['id']
|
||||||
else:
|
else:
|
||||||
currentFolderId= None
|
currentFolderId = None
|
||||||
break
|
break
|
||||||
if dbChange:
|
if dbChange:
|
||||||
session.commit()
|
session.commit()
|
||||||
@@ -268,16 +268,9 @@ def getFileFromEbooksFolder(path, fileName):
|
|||||||
return None
|
return None
|
||||||
|
|
||||||
|
|
||||||
'''def copyDriveFileRemote(drive, origin_file_id, copy_title):
|
def moveGdriveFileRemote(origin_file_id, new_title):
|
||||||
drive = getDrive(drive)
|
origin_file_id['title']= new_title
|
||||||
copied_file = {'title': copy_title}
|
origin_file_id.Upload()
|
||||||
try:
|
|
||||||
file_data = drive.auth.service.files().copy(
|
|
||||||
fileId = origin_file_id, body=copied_file).execute()
|
|
||||||
return drive.CreateFile({'id': file_data['id']})
|
|
||||||
except errors.HttpError as error:
|
|
||||||
print ('An error occurred: %s' % error)
|
|
||||||
return None'''
|
|
||||||
|
|
||||||
|
|
||||||
# Download metadata.db from gdrive
|
# Download metadata.db from gdrive
|
||||||
@@ -289,9 +282,10 @@ def downloadFile(path, filename, output):
|
|||||||
def moveGdriveFolderRemote(origin_file, target_folder):
|
def moveGdriveFolderRemote(origin_file, target_folder):
|
||||||
drive = getDrive(Gdrive.Instance().drive)
|
drive = getDrive(Gdrive.Instance().drive)
|
||||||
previous_parents = ",".join([parent["id"] for parent in origin_file.get('parents')])
|
previous_parents = ",".join([parent["id"] for parent in origin_file.get('parents')])
|
||||||
|
children = drive.auth.service.children().list(folderId=previous_parents).execute()
|
||||||
gFileTargetDir = getFileFromEbooksFolder(None, target_folder)
|
gFileTargetDir = getFileFromEbooksFolder(None, target_folder)
|
||||||
if not gFileTargetDir:
|
if not gFileTargetDir:
|
||||||
# Folder is not exisiting, create, and move folder
|
# Folder is not existing, create, and move folder
|
||||||
gFileTargetDir = drive.CreateFile(
|
gFileTargetDir = drive.CreateFile(
|
||||||
{'title': target_folder, 'parents': [{"kind": "drive#fileLink", 'id': getEbooksFolderId()}],
|
{'title': target_folder, 'parents': [{"kind": "drive#fileLink", 'id': getEbooksFolderId()}],
|
||||||
"mimeType": "application/vnd.google-apps.folder"})
|
"mimeType": "application/vnd.google-apps.folder"})
|
||||||
@@ -301,13 +295,10 @@ def moveGdriveFolderRemote(origin_file, target_folder):
|
|||||||
addParents=gFileTargetDir['id'],
|
addParents=gFileTargetDir['id'],
|
||||||
removeParents=previous_parents,
|
removeParents=previous_parents,
|
||||||
fields='id, parents').execute()
|
fields='id, parents').execute()
|
||||||
# if previous_parents has no childs anymore, delete originfileparent
|
# if previous_parents has no childs anymore, delete original fileparent
|
||||||
# is not working correctly, because of slow update on gdrive -> could cause trouble in gdrive.db
|
if len(children['items']) == 1:
|
||||||
# (nonexisting folder has id)
|
deleteDatabaseEntry(previous_parents)
|
||||||
# children = drive.auth.service.children().list(folderId=previous_parents).execute()
|
drive.auth.service.files().delete(fileId=previous_parents).execute()
|
||||||
# if not len(children['items']):
|
|
||||||
# drive.auth.service.files().delete(fileId=previous_parents).execute()
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
def copyToDrive(drive, uploadFile, createRoot, replaceFiles,
|
def copyToDrive(drive, uploadFile, createRoot, replaceFiles,
|
||||||
@@ -320,7 +311,7 @@ def copyToDrive(drive, uploadFile, createRoot, replaceFiles,
|
|||||||
parent = getEbooksFolder(drive)
|
parent = getEbooksFolder(drive)
|
||||||
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" %
|
existingFolder = drive.ListFile({'q': "title = '%s' and '%s' in parents and trashed = false" %
|
||||||
(os.path.basename(uploadFile), parent['id'])}).GetList()
|
(os.path.basename(uploadFile).replace("'", r"\'"), 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),
|
parent = drive.CreateFile({'title': os.path.basename(uploadFile),
|
||||||
'parents': [{"kind": "drive#fileLink", 'id': parent['id']}],
|
'parents': [{"kind": "drive#fileLink", 'id': parent['id']}],
|
||||||
@@ -335,11 +326,11 @@ def copyToDrive(drive, uploadFile, createRoot, replaceFiles,
|
|||||||
else:
|
else:
|
||||||
if os.path.basename(uploadFile) not in ignoreFiles:
|
if os.path.basename(uploadFile) not in ignoreFiles:
|
||||||
existingFiles = drive.ListFile({'q': "title = '%s' and '%s' in parents and trashed = false" %
|
existingFiles = drive.ListFile({'q': "title = '%s' and '%s' in parents and trashed = false" %
|
||||||
(os.path.basename(uploadFile), parent['id'])}).GetList()
|
(os.path.basename(uploadFile).replace("'", r"\'"), parent['id'])}).GetList()
|
||||||
if len(existingFiles) > 0:
|
if len(existingFiles) > 0:
|
||||||
driveFile = existingFiles[0]
|
driveFile = existingFiles[0]
|
||||||
else:
|
else:
|
||||||
driveFile = drive.CreateFile({'title': os.path.basename(uploadFile),
|
driveFile = drive.CreateFile({'title': os.path.basename(uploadFile).replace("'", r"\'"),
|
||||||
'parents': [{"kind":"drive#fileLink", 'id': parent['id']}], })
|
'parents': [{"kind":"drive#fileLink", 'id': parent['id']}], })
|
||||||
driveFile.SetContentFile(os.path.join(prevDir, uploadFile))
|
driveFile.SetContentFile(os.path.join(prevDir, uploadFile))
|
||||||
driveFile.Upload()
|
driveFile.Upload()
|
||||||
@@ -352,7 +343,7 @@ def uploadFileToEbooksFolder(destFile, f):
|
|||||||
for i, x in enumerate(splitDir):
|
for i, x in enumerate(splitDir):
|
||||||
if i == len(splitDir)-1:
|
if i == len(splitDir)-1:
|
||||||
existingFiles = drive.ListFile({'q': "title = '%s' and '%s' in parents and trashed = false" %
|
existingFiles = drive.ListFile({'q': "title = '%s' and '%s' in parents and trashed = false" %
|
||||||
(x, parent['id'])}).GetList()
|
(x.replace("'", r"\'"), parent['id'])}).GetList()
|
||||||
if len(existingFiles) > 0:
|
if len(existingFiles) > 0:
|
||||||
driveFile = existingFiles[0]
|
driveFile = existingFiles[0]
|
||||||
else:
|
else:
|
||||||
@@ -361,7 +352,7 @@ def uploadFileToEbooksFolder(destFile, f):
|
|||||||
driveFile.Upload()
|
driveFile.Upload()
|
||||||
else:
|
else:
|
||||||
existingFolder = drive.ListFile({'q': "title = '%s' and '%s' in parents and trashed = false" %
|
existingFolder = drive.ListFile({'q': "title = '%s' and '%s' in parents and trashed = false" %
|
||||||
(x, parent['id'])}).GetList()
|
(x.replace("'", r"\'"), 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"})
|
||||||
@@ -454,6 +445,10 @@ def getChangeById (drive, change_id):
|
|||||||
except (errors.HttpError) as error:
|
except (errors.HttpError) as error:
|
||||||
web.app.logger.info(error.message)
|
web.app.logger.info(error.message)
|
||||||
return None
|
return None
|
||||||
|
except Exception as e:
|
||||||
|
web.app.logger.info(e)
|
||||||
|
return None
|
||||||
|
|
||||||
|
|
||||||
# Deletes the local hashes database to force search for new folder names
|
# Deletes the local hashes database to force search for new folder names
|
||||||
def deleteDatabaseOnChange():
|
def deleteDatabaseOnChange():
|
||||||
@@ -468,9 +463,10 @@ def updateGdriveCalibreFromLocal():
|
|||||||
|
|
||||||
# update gdrive.db on edit of books title
|
# update gdrive.db on edit of books title
|
||||||
def updateDatabaseOnEdit(ID,newPath):
|
def updateDatabaseOnEdit(ID,newPath):
|
||||||
|
sqlCheckPath = newPath if newPath[-1] == '/' else newPath + u'/'
|
||||||
storedPathName = session.query(GdriveId).filter(GdriveId.gdrive_id == ID).first()
|
storedPathName = session.query(GdriveId).filter(GdriveId.gdrive_id == ID).first()
|
||||||
if storedPathName:
|
if storedPathName:
|
||||||
storedPathName.path = newPath
|
storedPathName.path = sqlCheckPath
|
||||||
session.commit()
|
session.commit()
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -22,13 +22,12 @@
|
|||||||
import db
|
import db
|
||||||
import ub
|
import ub
|
||||||
from flask import current_app as app
|
from flask import current_app as app
|
||||||
# import logging
|
|
||||||
from tempfile import gettempdir
|
from tempfile import gettempdir
|
||||||
import sys
|
import sys
|
||||||
|
import io
|
||||||
import os
|
import os
|
||||||
import re
|
import re
|
||||||
import unicodedata
|
import unicodedata
|
||||||
# from io import BytesIO
|
|
||||||
import worker
|
import worker
|
||||||
import time
|
import time
|
||||||
from flask import send_from_directory, make_response, redirect, abort
|
from flask import send_from_directory, make_response, redirect, abort
|
||||||
@@ -36,16 +35,13 @@ from flask_babel import gettext as _
|
|||||||
from flask_login import current_user
|
from flask_login import current_user
|
||||||
from babel.dates import format_datetime
|
from babel.dates import format_datetime
|
||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
# import threading
|
|
||||||
import shutil
|
import shutil
|
||||||
import requests
|
import requests
|
||||||
# import zipfile
|
|
||||||
try:
|
try:
|
||||||
import gdriveutils as gd
|
import gdriveutils as gd
|
||||||
except ImportError:
|
except ImportError:
|
||||||
pass
|
pass
|
||||||
import web
|
import web
|
||||||
# import server
|
|
||||||
import random
|
import random
|
||||||
import subprocess
|
import subprocess
|
||||||
|
|
||||||
@@ -55,6 +51,12 @@ try:
|
|||||||
except ImportError:
|
except ImportError:
|
||||||
use_unidecode = False
|
use_unidecode = False
|
||||||
|
|
||||||
|
try:
|
||||||
|
from PIL import Image
|
||||||
|
use_PIL = True
|
||||||
|
except ImportError:
|
||||||
|
use_PIL = False
|
||||||
|
|
||||||
# Global variables
|
# Global variables
|
||||||
# updater_thread = None
|
# updater_thread = None
|
||||||
global_WorkerThread = worker.WorkerThread()
|
global_WorkerThread = worker.WorkerThread()
|
||||||
@@ -128,7 +130,7 @@ def send_registration_mail(e_mail, user_name, default_password, resend=False):
|
|||||||
text += "Sincerely\r\n\r\n"
|
text += "Sincerely\r\n\r\n"
|
||||||
text += "Your Calibre-Web team"
|
text += "Your Calibre-Web team"
|
||||||
global_WorkerThread.add_email(_(u'Get Started with Calibre-Web'),None, None, ub.get_mail_settings(),
|
global_WorkerThread.add_email(_(u'Get Started with Calibre-Web'),None, None, ub.get_mail_settings(),
|
||||||
e_mail, user_name, _(u"Registration e-mail for user: %(name)s", name=user_name), text)
|
e_mail, None, _(u"Registration e-mail for user: %(name)s", name=user_name), text)
|
||||||
return
|
return
|
||||||
|
|
||||||
def check_send_to_kindle(entry):
|
def check_send_to_kindle(entry):
|
||||||
@@ -146,8 +148,8 @@ def check_send_to_kindle(entry):
|
|||||||
bookformats.append({'format':'Pdf','convert':0,'text':_('Send %(format)s to Kindle',format='Pdf')})
|
bookformats.append({'format':'Pdf','convert':0,'text':_('Send %(format)s to Kindle',format='Pdf')})
|
||||||
if 'AZW' in ele.format:
|
if 'AZW' in ele.format:
|
||||||
bookformats.append({'format':'Azw','convert':0,'text':_('Send %(format)s to Kindle',format='Azw')})
|
bookformats.append({'format':'Azw','convert':0,'text':_('Send %(format)s to Kindle',format='Azw')})
|
||||||
if 'AZW3' in ele.format:
|
'''if 'AZW3' in ele.format:
|
||||||
bookformats.append({'format':'Azw3','convert':0,'text':_('Send %(format)s to Kindle',format='Azw3')})
|
bookformats.append({'format':'Azw3','convert':0,'text':_('Send %(format)s to Kindle',format='Azw3')})'''
|
||||||
else:
|
else:
|
||||||
formats = list()
|
formats = list()
|
||||||
for ele in iter(entry.data):
|
for ele in iter(entry.data):
|
||||||
@@ -156,18 +158,16 @@ def check_send_to_kindle(entry):
|
|||||||
bookformats.append({'format': 'Mobi','convert':0,'text':_('Send %(format)s to Kindle',format='Mobi')})
|
bookformats.append({'format': 'Mobi','convert':0,'text':_('Send %(format)s to Kindle',format='Mobi')})
|
||||||
if 'AZW' in formats:
|
if 'AZW' in formats:
|
||||||
bookformats.append({'format': 'Azw','convert':0,'text':_('Send %(format)s to Kindle',format='Azw')})
|
bookformats.append({'format': 'Azw','convert':0,'text':_('Send %(format)s to Kindle',format='Azw')})
|
||||||
if 'AZW3' in formats:
|
|
||||||
bookformats.append({'format': 'Azw3','convert':0,'text':_('Send %(format)s to Kindle',format='Azw3')})
|
|
||||||
if 'PDF' in formats:
|
if 'PDF' in formats:
|
||||||
bookformats.append({'format': 'Pdf','convert':0,'text':_('Send %(format)s to Kindle',format='Pdf')})
|
bookformats.append({'format': 'Pdf','convert':0,'text':_('Send %(format)s to Kindle',format='Pdf')})
|
||||||
if ub.config.config_ebookconverter >= 1:
|
if ub.config.config_ebookconverter >= 1:
|
||||||
if 'EPUB' in formats and not 'MOBI' in formats:
|
if 'EPUB' in formats and not 'MOBI' in formats:
|
||||||
bookformats.append({'format': 'Mobi','convert':1,
|
bookformats.append({'format': 'Mobi','convert':1,
|
||||||
'text':_('Convert %(orig)s to %(format)s and send to Kindle',orig='Epub',format='Mobi')})
|
'text':_('Convert %(orig)s to %(format)s and send to Kindle',orig='Epub',format='Mobi')})
|
||||||
if ub.config.config_ebookconverter == 2:
|
'''if ub.config.config_ebookconverter == 2:
|
||||||
if 'EPUB' in formats and not 'AZW3' in formats:
|
if 'EPUB' in formats and not 'AZW3' in formats:
|
||||||
bookformats.append({'format': 'Azw3','convert':1,
|
bookformats.append({'format': 'Azw3','convert':1,
|
||||||
'text':_('Convert %(orig)s to %(format)s and send to Kindle',orig='Epub',format='Azw3')})
|
'text':_('Convert %(orig)s to %(format)s and send to Kindle',orig='Epub',format='Azw3')})'''
|
||||||
return bookformats
|
return bookformats
|
||||||
else:
|
else:
|
||||||
app.logger.error(u'Cannot find book entry %d', entry.id)
|
app.logger.error(u'Cannot find book entry %d', entry.id)
|
||||||
@@ -177,7 +177,7 @@ def check_send_to_kindle(entry):
|
|||||||
# Check if a reader is existing for any of the book formats, if not, return empty list, otherwise return
|
# Check if a reader is existing for any of the book formats, if not, return empty list, otherwise return
|
||||||
# list with supported formats
|
# list with supported formats
|
||||||
def check_read_formats(entry):
|
def check_read_formats(entry):
|
||||||
EXTENSIONS_READER = {'TXT', 'PDF', 'EPUB', 'ZIP', 'CBZ', 'TAR', 'CBT', 'RAR', 'CBR'}
|
EXTENSIONS_READER = {'TXT', 'PDF', 'EPUB', 'CBZ', 'CBT', 'CBR'}
|
||||||
bookformats = list()
|
bookformats = list()
|
||||||
if len(entry.data):
|
if len(entry.data):
|
||||||
for ele in iter(entry.data):
|
for ele in iter(entry.data):
|
||||||
@@ -235,7 +235,10 @@ def get_valid_filename(value, replace_whitespace=True):
|
|||||||
value = value[:128]
|
value = value[:128]
|
||||||
if not value:
|
if not value:
|
||||||
raise ValueError("Filename cannot be empty")
|
raise ValueError("Filename cannot be empty")
|
||||||
return value
|
if sys.version_info.major == 3:
|
||||||
|
return value
|
||||||
|
else:
|
||||||
|
return value.decode('utf-8')
|
||||||
|
|
||||||
|
|
||||||
def get_sorted_author(value):
|
def get_sorted_author(value):
|
||||||
@@ -324,11 +327,11 @@ def update_dir_structure_file(book_id, calibrepath, first_author):
|
|||||||
# Rename all files from old names to new names
|
# Rename all files from old names to new names
|
||||||
if authordir != new_authordir or titledir != new_titledir:
|
if authordir != new_authordir or titledir != new_titledir:
|
||||||
try:
|
try:
|
||||||
|
new_name = get_valid_filename(localbook.title) + ' - ' + get_valid_filename(new_authordir)
|
||||||
|
path_name = os.path.join(calibrepath, new_authordir, os.path.basename(path))
|
||||||
for file_format in localbook.data:
|
for file_format in localbook.data:
|
||||||
path_name = os.path.join(calibrepath, new_authordir, os.path.basename(path))
|
|
||||||
new_name = get_valid_filename(localbook.title) + ' - ' + get_valid_filename(new_authordir)
|
|
||||||
os.renames(os.path.join(path_name, file_format.name + '.' + file_format.format.lower()),
|
os.renames(os.path.join(path_name, file_format.name + '.' + file_format.format.lower()),
|
||||||
os.path.join(path_name,new_name + '.' + file_format.format.lower()))
|
os.path.join(path_name, new_name + '.' + file_format.format.lower()))
|
||||||
file_format.name = new_name
|
file_format.name = new_name
|
||||||
except OSError as ex:
|
except OSError as ex:
|
||||||
web.app.logger.error("Rename file in path " + path + " to " + new_name + ": " + str(ex))
|
web.app.logger.error("Rename file in path " + path + " to " + new_name + ": " + str(ex))
|
||||||
@@ -341,6 +344,7 @@ def update_dir_structure_file(book_id, calibrepath, first_author):
|
|||||||
def update_dir_structure_gdrive(book_id, first_author):
|
def update_dir_structure_gdrive(book_id, first_author):
|
||||||
error = False
|
error = False
|
||||||
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 = book.path
|
||||||
|
|
||||||
authordir = book.path.split('/')[0]
|
authordir = book.path.split('/')[0]
|
||||||
if first_author:
|
if first_author:
|
||||||
@@ -348,40 +352,39 @@ def update_dir_structure_gdrive(book_id, first_author):
|
|||||||
else:
|
else:
|
||||||
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) + u" (" + str(book_id) + u")"
|
||||||
|
|
||||||
if titledir != new_titledir:
|
if titledir != new_titledir:
|
||||||
gFile = gd.getFileFromEbooksFolder(os.path.dirname(book.path), titledir)
|
gFile = gd.getFileFromEbooksFolder(os.path.dirname(book.path), titledir)
|
||||||
if gFile:
|
if gFile:
|
||||||
gFile['title'] = new_titledir
|
gFile['title'] = new_titledir
|
||||||
|
|
||||||
gFile.Upload()
|
gFile.Upload()
|
||||||
book.path = book.path.split('/')[0] + '/' + new_titledir
|
book.path = book.path.split('/')[0] + u'/' + new_titledir
|
||||||
|
path = book.path
|
||||||
gd.updateDatabaseOnEdit(gFile['id'], book.path) # only child folder affected
|
gd.updateDatabaseOnEdit(gFile['id'], book.path) # only child folder affected
|
||||||
else:
|
else:
|
||||||
error = _(u'File %(file)s not found on Google Drive', file=book.path) # file not found
|
error = _(u'File %(file)s not found on Google Drive', file=book.path) # file not found
|
||||||
|
|
||||||
if authordir != new_authordir:
|
if authordir != new_authordir:
|
||||||
gFile = gd.getFileFromEbooksFolder(os.path.dirname(book.path), titledir)
|
gFile = gd.getFileFromEbooksFolder(os.path.dirname(book.path), new_titledir)
|
||||||
if gFile:
|
if gFile:
|
||||||
gd.moveGdriveFolderRemote(gFile,new_authordir)
|
gd.moveGdriveFolderRemote(gFile, new_authordir)
|
||||||
book.path = new_authordir + '/' + book.path.split('/')[1]
|
book.path = new_authordir + u'/' + book.path.split('/')[1]
|
||||||
|
path = book.path
|
||||||
gd.updateDatabaseOnEdit(gFile['id'], book.path)
|
gd.updateDatabaseOnEdit(gFile['id'], book.path)
|
||||||
else:
|
else:
|
||||||
error = _(u'File %(file)s not found on Google Drive', file=authordir) # file not found
|
error = _(u'File %(file)s not found on Google Drive', file=authordir) # file not found
|
||||||
# Rename all files from old names to new names
|
# Rename all files from old names to new names
|
||||||
# ToDo: Rename also all bookfiles with new author name and new title name
|
|
||||||
'''
|
|
||||||
if authordir != new_authordir or titledir != new_titledir:
|
if authordir != new_authordir or titledir != new_titledir:
|
||||||
for format in book.data:
|
new_name = get_valid_filename(book.title) + u' - ' + get_valid_filename(new_authordir)
|
||||||
# path_name = os.path.join(calibrepath, new_authordir, os.path.basename(path))
|
for file_format in book.data:
|
||||||
new_name = get_valid_filename(book.title) + ' - ' + get_valid_filename(book)
|
gFile = gd.getFileFromEbooksFolder(path, file_format.name + u'.' + file_format.format.lower())
|
||||||
format.name = new_name
|
if not gFile:
|
||||||
if gFile:
|
error = _(u'File %(file)s not found on Google Drive', file=file_format.name) # file not found
|
||||||
pass
|
break
|
||||||
else:
|
gd.moveGdriveFileRemote(gFile, new_name + u'.' + file_format.format.lower())
|
||||||
error = _(u'File %(file)s not found on Google Drive', file=format.name) # file not found
|
file_format.name = new_name
|
||||||
break'''
|
|
||||||
return error
|
return error
|
||||||
|
|
||||||
|
|
||||||
@@ -427,6 +430,8 @@ def delete_book(book, calibrepath, book_format):
|
|||||||
def get_book_cover(cover_path):
|
def get_book_cover(cover_path):
|
||||||
if ub.config.config_use_google_drive:
|
if ub.config.config_use_google_drive:
|
||||||
try:
|
try:
|
||||||
|
if not web.is_gdrive_ready():
|
||||||
|
return send_from_directory(os.path.join(os.path.dirname(__file__), "static"), "generic_cover.jpg")
|
||||||
path=gd.get_cover_via_gdrive(cover_path)
|
path=gd.get_cover_via_gdrive(cover_path)
|
||||||
if path:
|
if path:
|
||||||
return redirect(path)
|
return redirect(path)
|
||||||
@@ -434,7 +439,7 @@ def get_book_cover(cover_path):
|
|||||||
web.app.logger.error(cover_path + '/cover.jpg not found on Google Drive')
|
web.app.logger.error(cover_path + '/cover.jpg not found on Google Drive')
|
||||||
return send_from_directory(os.path.join(os.path.dirname(__file__), "static"), "generic_cover.jpg")
|
return send_from_directory(os.path.join(os.path.dirname(__file__), "static"), "generic_cover.jpg")
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
web.app.logger.error("Error Message: "+e.message)
|
web.app.logger.error("Error Message: " + e.message)
|
||||||
web.app.logger.exception(e)
|
web.app.logger.exception(e)
|
||||||
# traceback.print_exc()
|
# traceback.print_exc()
|
||||||
return send_from_directory(os.path.join(os.path.dirname(__file__), "static"),"generic_cover.jpg")
|
return send_from_directory(os.path.join(os.path.dirname(__file__), "static"),"generic_cover.jpg")
|
||||||
@@ -442,27 +447,71 @@ def get_book_cover(cover_path):
|
|||||||
return send_from_directory(os.path.join(ub.config.config_calibre_dir, cover_path), "cover.jpg")
|
return send_from_directory(os.path.join(ub.config.config_calibre_dir, cover_path), "cover.jpg")
|
||||||
|
|
||||||
|
|
||||||
# saves book cover to gdrive or locally
|
# saves book cover from url
|
||||||
def save_cover(url, book_path):
|
def save_cover_from_url(url, book_path):
|
||||||
img = requests.get(url)
|
img = requests.get(url)
|
||||||
if img.headers.get('content-type') != 'image/jpeg':
|
return save_cover(img, book_path)
|
||||||
web.app.logger.error("Cover is no jpg file, can't save")
|
|
||||||
return False
|
|
||||||
|
def save_cover_from_filestorage(filepath, saved_filename, img):
|
||||||
|
if hasattr(img,'_content'):
|
||||||
|
f = open(os.path.join(filepath, saved_filename), "wb")
|
||||||
|
f.write(img._content)
|
||||||
|
f.close()
|
||||||
|
else:
|
||||||
|
# check if file path exists, otherwise create it, copy file to calibre path and delete temp file
|
||||||
|
if not os.path.exists(filepath):
|
||||||
|
try:
|
||||||
|
os.makedirs(filepath)
|
||||||
|
except OSError:
|
||||||
|
web.app.logger.error(u"Failed to create path for cover")
|
||||||
|
return False
|
||||||
|
try:
|
||||||
|
img.save(os.path.join(filepath, saved_filename))
|
||||||
|
except OSError:
|
||||||
|
web.app.logger.error(u"Failed to store cover-file")
|
||||||
|
return False
|
||||||
|
except IOError:
|
||||||
|
web.app.logger.error(u"Cover-file is not a valid image file")
|
||||||
|
return False
|
||||||
|
return True
|
||||||
|
|
||||||
|
|
||||||
|
# saves book cover to gdrive or locally
|
||||||
|
def save_cover(img, book_path):
|
||||||
|
content_type = img.headers.get('content-type')
|
||||||
|
|
||||||
|
if use_PIL:
|
||||||
|
if content_type not in ('image/jpeg', 'image/png', 'image/webp'):
|
||||||
|
web.app.logger.error("Only jpg/jpeg/png/webp files are supported as coverfile")
|
||||||
|
return False
|
||||||
|
# convert to jpg because calibre only supports jpg
|
||||||
|
if content_type in ('image/png', 'image/webp'):
|
||||||
|
if hasattr(img,'stream'):
|
||||||
|
imgc = Image.open(img.stream)
|
||||||
|
else:
|
||||||
|
imgc = Image.open(io.BytesIO(img.content))
|
||||||
|
im = imgc.convert('RGB')
|
||||||
|
tmp_bytesio = io.BytesIO()
|
||||||
|
im.save(tmp_bytesio, format='JPEG')
|
||||||
|
img._content = tmp_bytesio.getvalue()
|
||||||
|
else:
|
||||||
|
if content_type not in ('image/jpeg'):
|
||||||
|
web.app.logger.error("Only jpg/jpeg files are supported as coverfile")
|
||||||
|
return False
|
||||||
|
|
||||||
if ub.config.config_use_google_drive:
|
if ub.config.config_use_google_drive:
|
||||||
tmpDir = gettempdir()
|
tmpDir = gettempdir()
|
||||||
f = open(os.path.join(tmpDir, "uploaded_cover.jpg"), "wb")
|
if save_cover_from_filestorage(tmpDir, "uploaded_cover.jpg", img) is True:
|
||||||
f.write(img.content)
|
gd.uploadFileToEbooksFolder(os.path.join(book_path, 'cover.jpg'),
|
||||||
f.close()
|
os.path.join(tmpDir, "uploaded_cover.jpg"))
|
||||||
gd.uploadFileToEbooksFolder(os.path.join(book_path, 'cover.jpg'), os.path.join(tmpDir, f.name))
|
web.app.logger.info("Cover is saved on Google Drive")
|
||||||
web.app.logger.info("Cover is saved on Google Drive")
|
return True
|
||||||
return True
|
else:
|
||||||
|
return False
|
||||||
|
else:
|
||||||
|
return save_cover_from_filestorage(os.path.join(ub.config.config_calibre_dir, book_path), "cover.jpg", img)
|
||||||
|
|
||||||
f = open(os.path.join(ub.config.config_calibre_dir, book_path, "cover.jpg"), "wb")
|
|
||||||
f.write(img.content)
|
|
||||||
f.close()
|
|
||||||
web.app.logger.info("Cover is saved")
|
|
||||||
return True
|
|
||||||
|
|
||||||
|
|
||||||
def do_download_file(book, book_format, data, headers):
|
def do_download_file(book, book_format, data, headers):
|
||||||
@@ -487,7 +536,6 @@ def do_download_file(book, book_format, data, headers):
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
def check_unrar(unrarLocation):
|
def check_unrar(unrarLocation):
|
||||||
error = False
|
error = False
|
||||||
if os.path.exists(unrarLocation):
|
if os.path.exists(unrarLocation):
|
||||||
@@ -520,17 +568,13 @@ def json_serial(obj):
|
|||||||
return obj.isoformat()
|
return obj.isoformat()
|
||||||
raise TypeError ("Type %s not serializable" % type(obj))
|
raise TypeError ("Type %s not serializable" % type(obj))
|
||||||
|
|
||||||
|
# helper function to apply localize status information in tasklist entries
|
||||||
def render_task_status(tasklist):
|
def render_task_status(tasklist):
|
||||||
#helper function to apply localize status information in tasklist entries
|
|
||||||
renderedtasklist=list()
|
renderedtasklist=list()
|
||||||
# task2 = task
|
|
||||||
for task in tasklist:
|
for task in tasklist:
|
||||||
if task['user'] == current_user.nickname or current_user.role_admin():
|
if task['user'] == current_user.nickname or current_user.role_admin():
|
||||||
# task2 = copy.deepcopy(task) # = task
|
|
||||||
if task['formStarttime']:
|
if task['formStarttime']:
|
||||||
task['starttime'] = format_datetime(task['formStarttime'], format='short', locale=web.get_locale())
|
task['starttime'] = format_datetime(task['formStarttime'], format='short', locale=web.get_locale())
|
||||||
# task2['formStarttime'] = ""
|
|
||||||
else:
|
else:
|
||||||
if 'starttime' not in task:
|
if 'starttime' not in task:
|
||||||
task['starttime'] = ""
|
task['starttime'] = ""
|
||||||
|
|||||||
@@ -0,0 +1,43 @@
|
|||||||
|
#!/usr/bin/env python
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
|
# This file is part of the Calibre-Web (https://github.com/janeczku/calibre-web)
|
||||||
|
# Copyright (C) 2019 pwr
|
||||||
|
#
|
||||||
|
# This program is free software: you can redistribute it and/or modify
|
||||||
|
# it under the terms of the GNU General Public License as published by
|
||||||
|
# the Free Software Foundation, either version 3 of the License, or
|
||||||
|
# (at your option) any later version.
|
||||||
|
#
|
||||||
|
# This program is distributed in the hope that it will be useful,
|
||||||
|
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
# GNU General Public License for more details.
|
||||||
|
#
|
||||||
|
# You should have received a copy of the GNU General Public License
|
||||||
|
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
try:
|
||||||
|
from iso639 import languages, __version__
|
||||||
|
get = languages.get
|
||||||
|
except ImportError:
|
||||||
|
from pycountry import languages as pyc_languages
|
||||||
|
try:
|
||||||
|
import pkg_resources
|
||||||
|
__version__ = pkg_resources.get_distribution('pycountry').version + ' (PyCountry)'
|
||||||
|
del pkg_resources
|
||||||
|
except (ImportError, Exception):
|
||||||
|
__version__ = "? (PyCountry)"
|
||||||
|
|
||||||
|
def _copy_fields(l):
|
||||||
|
l.part1 = l.alpha_2
|
||||||
|
l.part3 = l.alpha_3
|
||||||
|
return l
|
||||||
|
|
||||||
|
def get(name=None, part1=None, part3=None):
|
||||||
|
if (part3 is not None):
|
||||||
|
return _copy_fields(pyc_languages.get(alpha_3=part3))
|
||||||
|
if (part1 is not None):
|
||||||
|
return _copy_fields(pyc_languages.get(alpha_2=part1))
|
||||||
|
if (name is not None):
|
||||||
|
return _copy_fields(pyc_languages.get(name=name))
|
||||||
@@ -1,21 +1,41 @@
|
|||||||
#!/usr/bin/env python
|
#!/usr/bin/env python
|
||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
# This file is part of the Calibre-Web (https://github.com/janeczku/calibre-web)
|
# Flask License
|
||||||
# Copyright (C) 2018 cervinko, janeczku, OzzieIsaacs
|
|
||||||
#
|
#
|
||||||
# This program is free software: you can redistribute it and/or modify
|
# Copyright © 2010 by the Pallets team, cervinko, janeczku, OzzieIsaacs
|
||||||
# it under the terms of the GNU General Public License as published by
|
|
||||||
# the Free Software Foundation, either version 3 of the License, or
|
|
||||||
# (at your option) any later version.
|
|
||||||
#
|
#
|
||||||
# This program is distributed in the hope that it will be useful,
|
# Some rights reserved.
|
||||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
||||||
# GNU General Public License for more details.
|
|
||||||
#
|
#
|
||||||
# You should have received a copy of the GNU General Public License
|
# Redistribution and use in source and binary forms of the software as
|
||||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
# well as documentation, with or without modification, are permitted
|
||||||
|
# provided that the following conditions are met:
|
||||||
|
#
|
||||||
|
# * Redistributions of source code must retain the above copyright notice,
|
||||||
|
# this list of conditions and the following disclaimer.
|
||||||
|
#
|
||||||
|
# * Redistributions in binary form must reproduce the above copyright
|
||||||
|
# notice, this list of conditions and the following disclaimer in the
|
||||||
|
# documentation and/or other materials provided with the distribution.
|
||||||
|
#
|
||||||
|
# * Neither the name of the copyright holder nor the names of its
|
||||||
|
# contributors may be used to endorse or promote products derived from
|
||||||
|
# this software without specific prior written permission.
|
||||||
|
#
|
||||||
|
# THIS SOFTWARE AND DOCUMENTATION IS PROVIDED BY THE COPYRIGHT HOLDERS AND
|
||||||
|
# CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING,
|
||||||
|
# BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
|
||||||
|
# FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
|
||||||
|
# COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
|
||||||
|
# INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
|
||||||
|
# NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
|
||||||
|
# USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
|
||||||
|
# ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||||
|
# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
|
||||||
|
# THIS SOFTWARE AND DOCUMENTATION, EVEN IF ADVISED OF THE POSSIBILITY OF
|
||||||
|
# SUCH DAMAGE.
|
||||||
|
#
|
||||||
|
# Inspired by http://flask.pocoo.org/snippets/35/
|
||||||
|
|
||||||
class ReverseProxied(object):
|
class ReverseProxied(object):
|
||||||
"""Wrap the application in this middleware and configure the
|
"""Wrap the application in this middleware and configure the
|
||||||
@@ -49,7 +69,7 @@ class ReverseProxied(object):
|
|||||||
scheme = environ.get('HTTP_X_SCHEME', '')
|
scheme = environ.get('HTTP_X_SCHEME', '')
|
||||||
if scheme:
|
if scheme:
|
||||||
environ['wsgi.url_scheme'] = scheme
|
environ['wsgi.url_scheme'] = scheme
|
||||||
servr = environ.get('HTTP_X_FORWARDED_SERVER', '')
|
servr = environ.get('HTTP_X_FORWARDED_HOST', '')
|
||||||
if servr:
|
if servr:
|
||||||
environ['HTTP_HOST'] = servr
|
environ['HTTP_HOST'] = servr
|
||||||
return self.app(environ, start_response)
|
return self.app(environ, start_response)
|
||||||
|
|||||||
@@ -152,11 +152,11 @@ body {
|
|||||||
max-width: 70%;
|
max-width: 70%;
|
||||||
}
|
}
|
||||||
|
|
||||||
#prev {
|
#left {
|
||||||
left: 40px;
|
left: 40px;
|
||||||
}
|
}
|
||||||
|
|
||||||
#next {
|
#right {
|
||||||
right: 40px;
|
right: 40px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
|
Before Width: | Height: | Size: 415 B After Width: | Height: | Size: 415 B |
|
Before Width: | Height: | Size: 883 B After Width: | Height: | Size: 883 B |
|
Before Width: | Height: | Size: 2.1 KiB After Width: | Height: | Size: 2.1 KiB |
|
Before Width: | Height: | Size: 408 B After Width: | Height: | Size: 408 B |
|
Before Width: | Height: | Size: 1.4 KiB After Width: | Height: | Size: 1.4 KiB |
|
Before Width: | Height: | Size: 426 B After Width: | Height: | Size: 426 B |
|
Before Width: | Height: | Size: 158 B After Width: | Height: | Size: 158 B |
|
Before Width: | Height: | Size: 1.0 KiB After Width: | Height: | Size: 1.0 KiB |
|
Before Width: | Height: | Size: 1.1 KiB After Width: | Height: | Size: 1.1 KiB |
|
Before Width: | Height: | Size: 199 B After Width: | Height: | Size: 199 B |
|
Before Width: | Height: | Size: 304 B After Width: | Height: | Size: 304 B |
|
Before Width: | Height: | Size: 193 B After Width: | Height: | Size: 193 B |
|
Before Width: | Height: | Size: 296 B After Width: | Height: | Size: 296 B |
|
Before Width: | Height: | Size: 193 B After Width: | Height: | Size: 193 B |
|
Before Width: | Height: | Size: 296 B After Width: | Height: | Size: 296 B |
|
Before Width: | Height: | Size: 199 B After Width: | Height: | Size: 199 B |
|
Before Width: | Height: | Size: 304 B After Width: | Height: | Size: 304 B |
|
Before Width: | Height: | Size: 326 B After Width: | Height: | Size: 326 B |
|
Before Width: | Height: | Size: 326 B After Width: | Height: | Size: 326 B |
|
Before Width: | Height: | Size: 2.5 KiB After Width: | Height: | Size: 2.5 KiB |
|
Before Width: | Height: | Size: 7.2 KiB After Width: | Height: | Size: 7.2 KiB |
|
Before Width: | Height: | Size: 16 KiB After Width: | Height: | Size: 16 KiB |
|
Before Width: | Height: | Size: 403 B After Width: | Height: | Size: 403 B |
|
Before Width: | Height: | Size: 933 B After Width: | Height: | Size: 933 B |
|
Before Width: | Height: | Size: 179 B After Width: | Height: | Size: 179 B |
|
Before Width: | Height: | Size: 266 B After Width: | Height: | Size: 266 B |
|
Before Width: | Height: | Size: 301 B After Width: | Height: | Size: 301 B |
|
Before Width: | Height: | Size: 583 B After Width: | Height: | Size: 583 B |
|
Before Width: | Height: | Size: 175 B After Width: | Height: | Size: 175 B |
|
Before Width: | Height: | Size: 276 B After Width: | Height: | Size: 276 B |
|
Before Width: | Height: | Size: 360 B After Width: | Height: | Size: 360 B |
|
Before Width: | Height: | Size: 731 B After Width: | Height: | Size: 731 B |
|
Before Width: | Height: | Size: 359 B After Width: | Height: | Size: 359 B |
|
Before Width: | Height: | Size: 714 B After Width: | Height: | Size: 714 B |
|
After Width: | Height: | Size: 218 B |
|
After Width: | Height: | Size: 332 B |
|
After Width: | Height: | Size: 228 B |
|
After Width: | Height: | Size: 349 B |
|
After Width: | Height: | Size: 297 B |
|
After Width: | Height: | Size: 490 B |
|
After Width: | Height: | Size: 461 B |
|
After Width: | Height: | Size: 1.0 KiB |
|
After Width: | Height: | Size: 347 B |
|
After Width: | Height: | Size: 694 B |
|
After Width: | Height: | Size: 179 B |
|
After Width: | Height: | Size: 261 B |
|
After Width: | Height: | Size: 344 B |
|
After Width: | Height: | Size: 621 B |
|
Before Width: | Height: | Size: 290 B After Width: | Height: | Size: 290 B |
|
Before Width: | Height: | Size: 2.4 KiB After Width: | Height: | Size: 2.4 KiB |
|
Before Width: | Height: | Size: 174 B After Width: | Height: | Size: 174 B |
|
Before Width: | Height: | Size: 260 B After Width: | Height: | Size: 260 B |
|
Before Width: | Height: | Size: 259 B After Width: | Height: | Size: 259 B |
|
Before Width: | Height: | Size: 425 B After Width: | Height: | Size: 425 B |
|
Before Width: | Height: | Size: 107 B After Width: | Height: | Size: 107 B |
|
Before Width: | Height: | Size: 152 B After Width: | Height: | Size: 152 B |
|
Before Width: | Height: | Size: 295 B After Width: | Height: | Size: 295 B |
|
Before Width: | Height: | Size: 550 B After Width: | Height: | Size: 550 B |
|
Before Width: | Height: | Size: 242 B After Width: | Height: | Size: 242 B |
|
Before Width: | Height: | Size: 398 B After Width: | Height: | Size: 398 B |
|
Before Width: | Height: | Size: 238 B After Width: | Height: | Size: 238 B |
|
Before Width: | Height: | Size: 396 B After Width: | Height: | Size: 396 B |
|
Before Width: | Height: | Size: 245 B After Width: | Height: | Size: 245 B |
|
Before Width: | Height: | Size: 405 B After Width: | Height: | Size: 405 B |
|
Before Width: | Height: | Size: 246 B After Width: | Height: | Size: 246 B |
|
Before Width: | Height: | Size: 403 B After Width: | Height: | Size: 403 B |
|
Before Width: | Height: | Size: 321 B After Width: | Height: | Size: 321 B |
|
Before Width: | Height: | Size: 586 B After Width: | Height: | Size: 586 B |
|
Before Width: | Height: | Size: 257 B After Width: | Height: | Size: 257 B |
|
Before Width: | Height: | Size: 464 B After Width: | Height: | Size: 464 B |
|
Before Width: | Height: | Size: 309 B After Width: | Height: | Size: 309 B |
|
Before Width: | Height: | Size: 653 B After Width: | Height: | Size: 653 B |
|
Before Width: | Height: | Size: 246 B After Width: | Height: | Size: 246 B |
|
Before Width: | Height: | Size: 456 B After Width: | Height: | Size: 456 B |
|
Before Width: | Height: | Size: 243 B After Width: | Height: | Size: 243 B |
|
Before Width: | Height: | Size: 458 B After Width: | Height: | Size: 458 B |
|
Before Width: | Height: | Size: 225 B After Width: | Height: | Size: 225 B |
|
Before Width: | Height: | Size: 344 B After Width: | Height: | Size: 344 B |
|
Before Width: | Height: | Size: 225 B After Width: | Height: | Size: 225 B |
|
Before Width: | Height: | Size: 331 B After Width: | Height: | Size: 331 B |
|
Before Width: | Height: | Size: 384 B After Width: | Height: | Size: 384 B |
|
Before Width: | Height: | Size: 859 B After Width: | Height: | Size: 859 B |
|
Before Width: | Height: | Size: 177 B After Width: | Height: | Size: 177 B |
|
Before Width: | Height: | Size: 394 B After Width: | Height: | Size: 394 B |
|
Before Width: | Height: | Size: 178 B After Width: | Height: | Size: 178 B |
|
Before Width: | Height: | Size: 331 B After Width: | Height: | Size: 331 B |