#538:
-Refactoring gdrive and file handling -Improved error handling for gdrive -bugfix "gdrive stopping after a while" - Renaming book title working - Still Bugs in upload file to gdrive and renaming author
This commit is contained in:
parent
413b10c58e
commit
a8040ad3fa
|
@ -5,9 +5,9 @@ try:
|
||||||
except ImportError:
|
except ImportError:
|
||||||
pass
|
pass
|
||||||
import os
|
import os
|
||||||
|
|
||||||
from ub import config
|
from ub import config
|
||||||
import cli
|
import cli
|
||||||
|
import shutil
|
||||||
|
|
||||||
from sqlalchemy import *
|
from sqlalchemy import *
|
||||||
from sqlalchemy.ext.declarative import declarative_base
|
from sqlalchemy.ext.declarative import declarative_base
|
||||||
|
@ -16,6 +16,57 @@ from sqlalchemy.orm import *
|
||||||
|
|
||||||
import web
|
import web
|
||||||
|
|
||||||
|
class Singleton:
|
||||||
|
"""
|
||||||
|
A non-thread-safe helper class to ease implementing singletons.
|
||||||
|
This should be used as a decorator -- not a metaclass -- to the
|
||||||
|
class that should be a singleton.
|
||||||
|
|
||||||
|
The decorated class can define one `__init__` function that
|
||||||
|
takes only the `self` argument. Also, the decorated class cannot be
|
||||||
|
inherited from. Other than that, there are no restrictions that apply
|
||||||
|
to the decorated class.
|
||||||
|
|
||||||
|
To get the singleton instance, use the `Instance` method. Trying
|
||||||
|
to use `__call__` will result in a `TypeError` being raised.
|
||||||
|
|
||||||
|
"""
|
||||||
|
|
||||||
|
def __init__(self, decorated):
|
||||||
|
self._decorated = decorated
|
||||||
|
|
||||||
|
def Instance(self):
|
||||||
|
"""
|
||||||
|
Returns the singleton instance. Upon its first call, it creates a
|
||||||
|
new instance of the decorated class and calls its `__init__` method.
|
||||||
|
On all subsequent calls, the already created instance is returned.
|
||||||
|
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
return self._instance
|
||||||
|
except AttributeError:
|
||||||
|
self._instance = self._decorated()
|
||||||
|
return self._instance
|
||||||
|
|
||||||
|
def __call__(self):
|
||||||
|
raise TypeError('Singletons must be accessed through `Instance()`.')
|
||||||
|
|
||||||
|
def __instancecheck__(self, inst):
|
||||||
|
return isinstance(inst, self._decorated)
|
||||||
|
|
||||||
|
|
||||||
|
@Singleton
|
||||||
|
class Gauth:
|
||||||
|
def __init__(self):
|
||||||
|
self.auth = GoogleAuth(settings_file=os.path.join(config.get_main_dir,'settings.yaml'))
|
||||||
|
|
||||||
|
|
||||||
|
@Singleton
|
||||||
|
class Gdrive:
|
||||||
|
def __init__(self):
|
||||||
|
self.drive = getDrive(gauth=Gauth.Instance().auth)
|
||||||
|
|
||||||
|
|
||||||
engine = create_engine('sqlite:///{0}'.format(cli.gdpath), echo=False)
|
engine = create_engine('sqlite:///{0}'.format(cli.gdpath), echo=False)
|
||||||
Base = declarative_base()
|
Base = declarative_base()
|
||||||
|
|
||||||
|
@ -110,9 +161,12 @@ def getFolderInFolder(parentId, folderName,drive=None):
|
||||||
query = "title = '%s' and " % folderName.replace("'", "\\'")
|
query = "title = '%s' and " % folderName.replace("'", "\\'")
|
||||||
folder = query + "'%s' in parents and mimeType = 'application/vnd.google-apps.folder' and trashed = false" % parentId
|
folder = query + "'%s' in parents and mimeType = 'application/vnd.google-apps.folder' and trashed = false" % parentId
|
||||||
fileList = drive.ListFile({'q': folder}).GetList()
|
fileList = drive.ListFile({'q': folder}).GetList()
|
||||||
|
if fileList.__len__() == 0:
|
||||||
|
return None
|
||||||
|
else:
|
||||||
return fileList[0]
|
return fileList[0]
|
||||||
|
|
||||||
|
# Search for id of root folder in gdrive database, if not found request from gdrive and store in internal database
|
||||||
def getEbooksFolderId(drive=None):
|
def getEbooksFolderId(drive=None):
|
||||||
storedPathName = session.query(GdriveId).filter(GdriveId.path == '/').first()
|
storedPathName = session.query(GdriveId).filter(GdriveId.path == '/').first()
|
||||||
if storedPathName:
|
if storedPathName:
|
||||||
|
@ -131,10 +185,13 @@ def getEbooksFolderId(drive=None):
|
||||||
|
|
||||||
|
|
||||||
def getFile(pathId, fileName, drive=None):
|
def getFile(pathId, fileName, drive=None):
|
||||||
drive = getDrive(drive)
|
drive = getDrive(Gdrive.Instance().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("'", "\\'"))
|
||||||
|
|
||||||
fileList = drive.ListFile({'q': metaDataFile}).GetList()
|
fileList = drive.ListFile({'q': metaDataFile}).GetList()
|
||||||
|
if fileList.__len__() == 0:
|
||||||
|
return None
|
||||||
|
else:
|
||||||
return fileList[0]
|
return fileList[0]
|
||||||
|
|
||||||
|
|
||||||
|
@ -156,12 +213,17 @@ def getFolderId(path, drive=None):
|
||||||
if storedPathName:
|
if storedPathName:
|
||||||
currentFolderId = storedPathName.gdrive_id
|
currentFolderId = storedPathName.gdrive_id
|
||||||
else:
|
else:
|
||||||
currentFolderId = getFolderInFolder(currentFolderId, x, drive)['id']
|
currentFolder = getFolderInFolder(currentFolderId, x, drive)
|
||||||
|
if currentFolder:
|
||||||
gDriveId = GdriveId()
|
gDriveId = GdriveId()
|
||||||
gDriveId.gdrive_id = currentFolderId
|
gDriveId.gdrive_id = currentFolder['id']
|
||||||
gDriveId.path = currentPath
|
gDriveId.path = currentPath
|
||||||
session.merge(gDriveId)
|
session.merge(gDriveId)
|
||||||
dbChange = True
|
dbChange = True
|
||||||
|
currentFolderId = currentFolder['id']
|
||||||
|
else:
|
||||||
|
currentFolderId= None
|
||||||
|
break
|
||||||
if dbChange:
|
if dbChange:
|
||||||
session.commit()
|
session.commit()
|
||||||
else:
|
else:
|
||||||
|
@ -169,15 +231,17 @@ def getFolderId(path, drive=None):
|
||||||
return currentFolderId
|
return currentFolderId
|
||||||
|
|
||||||
|
|
||||||
def getFileFromEbooksFolder(drive, path, fileName):
|
def getFileFromEbooksFolder(path, fileName):
|
||||||
drive = getDrive(drive)
|
drive = getDrive(Gdrive.Instance().drive)
|
||||||
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)
|
||||||
|
if folderId:
|
||||||
return getFile(folderId, fileName, drive)
|
return getFile(folderId, fileName, drive)
|
||||||
|
else:
|
||||||
|
return None
|
||||||
|
|
||||||
|
|
||||||
def copyDriveFileRemote(drive, origin_file_id, copy_title):
|
def copyDriveFileRemote(drive, origin_file_id, copy_title):
|
||||||
|
@ -192,9 +256,9 @@ def copyDriveFileRemote(drive, origin_file_id, copy_title):
|
||||||
return None
|
return None
|
||||||
|
|
||||||
|
|
||||||
def downloadFile(drive, path, filename, output):
|
def downloadFile(path, filename, output):
|
||||||
drive = getDrive(drive)
|
# drive = getDrive(drive)
|
||||||
f = getFileFromEbooksFolder(drive, path, filename)
|
f = getFileFromEbooksFolder(path, filename)
|
||||||
f.GetContentFile(output)
|
f.GetContentFile(output)
|
||||||
|
|
||||||
|
|
||||||
|
@ -238,8 +302,8 @@ def copyToDrive(drive, uploadFile, createRoot, replaceFiles,
|
||||||
driveFile.Upload()
|
driveFile.Upload()
|
||||||
|
|
||||||
|
|
||||||
def uploadFileToEbooksFolder(drive, destFile, f):
|
def uploadFileToEbooksFolder(destFile, f):
|
||||||
drive = getDrive(drive)
|
drive = getDrive(Gdrive.Instance().drive)
|
||||||
parent = getEbooksFolder(drive)
|
parent = getEbooksFolder(drive)
|
||||||
splitDir = destFile.split('/')
|
splitDir = destFile.split('/')
|
||||||
for i, x in enumerate(splitDir):
|
for i, x in enumerate(splitDir):
|
||||||
|
@ -349,10 +413,24 @@ def getChangeById (drive, change_id):
|
||||||
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) as error:
|
except (errors.HttpError) as error:
|
||||||
web.app.logger.exception(error)
|
app.logger.info(error.message)
|
||||||
return None
|
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():
|
||||||
session.query(GdriveId).delete()
|
session.query(GdriveId).delete()
|
||||||
session.commit()
|
session.commit()
|
||||||
|
|
||||||
|
def updateGdriveCalibreFromLocal():
|
||||||
|
backupCalibreDbAndOptionalDownload(Gdrive.Instance().drive)
|
||||||
|
copyToDrive(Gdrive.Instance().drive, config.config_calibre_dir, False, True)
|
||||||
|
for x in os.listdir(config.config_calibre_dir):
|
||||||
|
if os.path.isdir(os.path.join(config.config_calibre_dir, x)):
|
||||||
|
shutil.rmtree(os.path.join(config.config_calibre_dir, x))
|
||||||
|
|
||||||
|
# update gdrive.db on edit of books title
|
||||||
|
def updateDatabaseOnEdit(ID,newPath):
|
||||||
|
storedPathName = session.query(GdriveId).filter(GdriveId.gdrive_id == ID).first()
|
||||||
|
if storedPathName:
|
||||||
|
storedPathName.path = newPath
|
||||||
|
session.commit()
|
||||||
|
|
|
@ -56,16 +56,6 @@ RET_SUCCESS = 1
|
||||||
RET_FAIL = 0
|
RET_FAIL = 0
|
||||||
|
|
||||||
|
|
||||||
def update_download(book_id, user_id):
|
|
||||||
check = ub.session.query(ub.Downloads).filter(ub.Downloads.user_id == user_id).filter(ub.Downloads.book_id ==
|
|
||||||
book_id).first()
|
|
||||||
|
|
||||||
if not check:
|
|
||||||
new_download = ub.Downloads(user_id=user_id, book_id=book_id)
|
|
||||||
ub.session.add(new_download)
|
|
||||||
ub.session.commit()
|
|
||||||
|
|
||||||
|
|
||||||
def make_mobi(book_id, calibrepath):
|
def make_mobi(book_id, calibrepath):
|
||||||
error_message = None
|
error_message = None
|
||||||
vendorpath = os.path.join(os.path.normpath(os.path.dirname(os.path.realpath(__file__)) +
|
vendorpath = os.path.join(os.path.normpath(os.path.dirname(os.path.realpath(__file__)) +
|
||||||
|
@ -243,7 +233,6 @@ def send_mail(book_id, kindle_mail, calibrepath):
|
||||||
|
|
||||||
def get_attachment(file_path):
|
def get_attachment(file_path):
|
||||||
"""Get file as MIMEBase message"""
|
"""Get file as MIMEBase message"""
|
||||||
|
|
||||||
try:
|
try:
|
||||||
file_ = open(file_path, 'rb')
|
file_ = open(file_path, 'rb')
|
||||||
attachment = MIMEBase('application', 'octet-stream')
|
attachment = MIMEBase('application', 'octet-stream')
|
||||||
|
@ -306,7 +295,7 @@ def get_sorted_author(value):
|
||||||
return value2
|
return value2
|
||||||
|
|
||||||
|
|
||||||
def delete_book(book, calibrepath):
|
def delete_book_file(book, calibrepath):
|
||||||
# check that path is 2 elements deep, check that target path has no subfolders
|
# check that path is 2 elements deep, check that target path has no subfolders
|
||||||
if "/" in book.path:
|
if "/" in book.path:
|
||||||
path = os.path.join(calibrepath, book.path)
|
path = os.path.join(calibrepath, book.path)
|
||||||
|
@ -314,16 +303,8 @@ def delete_book(book, calibrepath):
|
||||||
else:
|
else:
|
||||||
logging.getLogger('cps.web').error("Deleting book " + str(book.id) + " failed, book path value: "+ book.path)
|
logging.getLogger('cps.web').error("Deleting book " + str(book.id) + " failed, book path value: "+ book.path)
|
||||||
|
|
||||||
# ToDo: Implement delete book on gdrive
|
|
||||||
def delete_book_gdrive(book):
|
|
||||||
# delete book and path of book in gdrive.db
|
|
||||||
# delete book and path of book on gdrive
|
|
||||||
#gFile = gd.getFileFromEbooksFolder(web.Gdrive.Instance().drive, os.path.dirname(book.path), titledir)
|
|
||||||
#gFile.Trash()
|
|
||||||
pass
|
|
||||||
|
|
||||||
|
def update_dir_stucture_file(book_id, calibrepath):
|
||||||
def update_dir_stucture(book_id, calibrepath):
|
|
||||||
localbook = db.session.query(db.Books).filter(db.Books.id == book_id).first()
|
localbook = db.session.query(db.Books).filter(db.Books.id == book_id).first()
|
||||||
path = os.path.join(calibrepath, localbook.path)
|
path = os.path.join(calibrepath, localbook.path)
|
||||||
|
|
||||||
|
@ -372,18 +353,64 @@ def update_dir_structure_gdrive(book_id):
|
||||||
|
|
||||||
if titledir != new_titledir:
|
if titledir != new_titledir:
|
||||||
# print (titledir)
|
# print (titledir)
|
||||||
gFile = gd.getFileFromEbooksFolder(web.Gdrive.Instance().drive, os.path.dirname(book.path), titledir)
|
gFile = gd.getFileFromEbooksFolder(os.path.dirname(book.path), titledir)
|
||||||
|
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] + '/' + new_titledir
|
||||||
|
gd.updateDatabaseOnEdit(gFile['id'], book.path) # only child folder affected
|
||||||
|
else:
|
||||||
|
error = _(u'File %s not found on gdrive' % book.path) # file not found
|
||||||
|
|
||||||
if authordir != new_authordir:
|
if authordir != new_authordir:
|
||||||
gFile = gd.getFileFromEbooksFolder(web.Gdrive.Instance().drive, None, authordir)
|
gFile = gd.getFileFromEbooksFolder(os.path.dirname(book.path), titledir)
|
||||||
gFile['title'] = new_authordir
|
# gFileDirOrig = gd.getFileFromEbooksFolder(None, authordir)
|
||||||
|
if gFile:
|
||||||
|
# check if authordir exisits
|
||||||
|
gFileDirOrig = gd.getFileFromEbooksFolder(None, authordir)
|
||||||
|
if gFileDirOrig:
|
||||||
|
gFile['parents'].append({"id": gFileDirOrig['id']})
|
||||||
gFile.Upload()
|
gFile.Upload()
|
||||||
|
else:
|
||||||
|
# Folder is not exisiting
|
||||||
|
#parent = drive.CreateFile({'title': authordir, 'parents': [{"kind": "drive#fileLink", 'id': root folder id}],
|
||||||
|
# "mimeType": "application/vnd.google-apps.folder"})
|
||||||
|
parent.Upload()
|
||||||
|
# gFile['title'] = new_authordir
|
||||||
|
# gFile.Upload()
|
||||||
book.path = new_authordir + '/' + book.path.split('/')[1]
|
book.path = new_authordir + '/' + book.path.split('/')[1]
|
||||||
|
gd.updateDatabaseOnEdit(gFile['id'], book.path)
|
||||||
|
# Todo last element from parent folder moved to different folder, what to do with parent folder?
|
||||||
|
# parent folder affected
|
||||||
|
else:
|
||||||
|
error = _(u'File %s not found on gdrive' % authordir) # file not found
|
||||||
return error
|
return error
|
||||||
|
|
||||||
|
# ToDo: Implement delete book on gdrive
|
||||||
|
def delete_book_gdrive(book):
|
||||||
|
# delete book and path of book in gdrive.db
|
||||||
|
# delete book and path of book on gdrive
|
||||||
|
#gFile = gd.getFileFromEbooksFolder(os.path.dirname(book.path), titledir)
|
||||||
|
#gFile.Trash()
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
################################## External interface
|
||||||
|
|
||||||
|
def update_dir_stucture(book_id, calibrepath):
|
||||||
|
if ub.config.config_use_google_drive:
|
||||||
|
return update_dir_structure_gdrive(book_id)
|
||||||
|
else:
|
||||||
|
return update_dir_stucture_file(book_id, calibrepath)
|
||||||
|
|
||||||
|
def delete_book(book, calibrepath):
|
||||||
|
if ub.config.config_use_google_drive:
|
||||||
|
return delete_book_file(book, calibrepath)
|
||||||
|
else:
|
||||||
|
return delete_book_gdrive(book)
|
||||||
|
##################################
|
||||||
|
|
||||||
|
|
||||||
class Updater(threading.Thread):
|
class Updater(threading.Thread):
|
||||||
|
|
||||||
|
|
|
@ -7,7 +7,6 @@ import sys
|
||||||
import os
|
import os
|
||||||
try:
|
try:
|
||||||
from gevent.pywsgi import WSGIServer
|
from gevent.pywsgi import WSGIServer
|
||||||
from gevent import monkey
|
|
||||||
from gevent.pool import Pool
|
from gevent.pool import Pool
|
||||||
from gevent import __version__ as geventVersion
|
from gevent import __version__ as geventVersion
|
||||||
gevent_present = True
|
gevent_present = True
|
||||||
|
@ -52,7 +51,6 @@ class server:
|
||||||
if gevent_present:
|
if gevent_present:
|
||||||
web.app.logger.info('Starting Gevent server')
|
web.app.logger.info('Starting Gevent server')
|
||||||
# leave subprocess out to allow forking for fetchers and processors
|
# leave subprocess out to allow forking for fetchers and processors
|
||||||
monkey.patch_all(subprocess=False)
|
|
||||||
self.start_gevent()
|
self.start_gevent()
|
||||||
else:
|
else:
|
||||||
web.app.logger.info('Starting Tornado server')
|
web.app.logger.info('Starting Tornado server')
|
||||||
|
|
14
cps/ub.py
14
cps/ub.py
|
@ -691,6 +691,20 @@ def get_mail_settings():
|
||||||
|
|
||||||
return data
|
return data
|
||||||
|
|
||||||
|
# Save downloaded books per user in calibre-web's own database
|
||||||
|
def update_download(book_id, user_id):
|
||||||
|
check = session.query(Downloads).filter(Downloads.user_id == user_id).filter(Downloads.book_id ==
|
||||||
|
book_id).first()
|
||||||
|
|
||||||
|
if not check:
|
||||||
|
new_download = Downloads(user_id=user_id, book_id=book_id)
|
||||||
|
session.add(new_download)
|
||||||
|
session.commit()
|
||||||
|
|
||||||
|
# Delete non exisiting downloaded books in calibre-web's own database
|
||||||
|
def delete_download(book_id):
|
||||||
|
session.query(Downloads).filter(book_id == Downloads.book_id).delete()
|
||||||
|
session.commit()
|
||||||
|
|
||||||
# Generate user Guest (translated text), as anoymous user, no rights
|
# Generate user Guest (translated text), as anoymous user, no rights
|
||||||
def create_anonymous_user():
|
def create_anonymous_user():
|
||||||
|
|
166
cps/web.py
166
cps/web.py
|
@ -50,7 +50,7 @@ from flask_principal import __version__ as flask_principalVersion
|
||||||
from flask_babel import Babel
|
from flask_babel import Babel
|
||||||
from flask_babel import gettext as _
|
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 werkzeug.datastructures import Headers
|
||||||
from babel import Locale as LC
|
from babel import Locale as LC
|
||||||
|
@ -77,7 +77,6 @@ import tempfile
|
||||||
import hashlib
|
import hashlib
|
||||||
from redirect import redirect_back, is_safe_url
|
from redirect import redirect_back, is_safe_url
|
||||||
|
|
||||||
|
|
||||||
try:
|
try:
|
||||||
from urllib.parse import quote
|
from urllib.parse import quote
|
||||||
from imp import reload
|
from imp import reload
|
||||||
|
@ -109,57 +108,6 @@ def md5(fname):
|
||||||
return hash_md5.hexdigest()
|
return hash_md5.hexdigest()
|
||||||
|
|
||||||
|
|
||||||
class Singleton:
|
|
||||||
"""
|
|
||||||
A non-thread-safe helper class to ease implementing singletons.
|
|
||||||
This should be used as a decorator -- not a metaclass -- to the
|
|
||||||
class that should be a singleton.
|
|
||||||
|
|
||||||
The decorated class can define one `__init__` function that
|
|
||||||
takes only the `self` argument. Also, the decorated class cannot be
|
|
||||||
inherited from. Other than that, there are no restrictions that apply
|
|
||||||
to the decorated class.
|
|
||||||
|
|
||||||
To get the singleton instance, use the `Instance` method. Trying
|
|
||||||
to use `__call__` will result in a `TypeError` being raised.
|
|
||||||
|
|
||||||
"""
|
|
||||||
|
|
||||||
def __init__(self, decorated):
|
|
||||||
self._decorated = decorated
|
|
||||||
|
|
||||||
def Instance(self):
|
|
||||||
"""
|
|
||||||
Returns the singleton instance. Upon its first call, it creates a
|
|
||||||
new instance of the decorated class and calls its `__init__` method.
|
|
||||||
On all subsequent calls, the already created instance is returned.
|
|
||||||
|
|
||||||
"""
|
|
||||||
try:
|
|
||||||
return self._instance
|
|
||||||
except AttributeError:
|
|
||||||
self._instance = self._decorated()
|
|
||||||
return self._instance
|
|
||||||
|
|
||||||
def __call__(self):
|
|
||||||
raise TypeError('Singletons must be accessed through `Instance()`.')
|
|
||||||
|
|
||||||
def __instancecheck__(self, inst):
|
|
||||||
return isinstance(inst, self._decorated)
|
|
||||||
|
|
||||||
|
|
||||||
@Singleton
|
|
||||||
class Gauth:
|
|
||||||
def __init__(self):
|
|
||||||
self.auth = GoogleAuth(settings_file=os.path.join(config.get_main_dir,'settings.yaml'))
|
|
||||||
|
|
||||||
|
|
||||||
@Singleton
|
|
||||||
class Gdrive:
|
|
||||||
def __init__(self):
|
|
||||||
self.drive = gdriveutils.getDrive(gauth=Gauth.Instance().auth)
|
|
||||||
|
|
||||||
|
|
||||||
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
|
||||||
|
@ -305,14 +253,6 @@ def authenticate():
|
||||||
{'WWW-Authenticate': 'Basic realm="Login Required"'})
|
{'WWW-Authenticate': 'Basic realm="Login Required"'})
|
||||||
|
|
||||||
|
|
||||||
def updateGdriveCalibreFromLocal():
|
|
||||||
gdriveutils.backupCalibreDbAndOptionalDownload(Gdrive.Instance().drive)
|
|
||||||
gdriveutils.copyToDrive(Gdrive.Instance().drive, config.config_calibre_dir, False, True)
|
|
||||||
for x in os.listdir(config.config_calibre_dir):
|
|
||||||
if os.path.isdir(os.path.join(config.config_calibre_dir, x)):
|
|
||||||
shutil.rmtree(os.path.join(config.config_calibre_dir, x))
|
|
||||||
|
|
||||||
|
|
||||||
def requires_basic_auth_if_no_ano(f):
|
def requires_basic_auth_if_no_ano(f):
|
||||||
@wraps(f)
|
@wraps(f)
|
||||||
def decorated(*args, **kwargs):
|
def decorated(*args, **kwargs):
|
||||||
|
@ -736,8 +676,9 @@ def feed_hot():
|
||||||
.filter(db.Books.id == book.Downloads.book_id).first()
|
.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.delete_download(book.Downloads.book_id)
|
||||||
ub.session.commit()
|
# ub.session.query(ub.Downloads).filter(book.Downloads.book_id == ub.Downloads.book_id).delete()
|
||||||
|
# ub.session.commit()
|
||||||
numBooks = entries.__len__()
|
numBooks = entries.__len__()
|
||||||
pagination = Pagination((int(off) / (int(config.config_books_per_page)) + 1), config.config_books_per_page, numBooks)
|
pagination = Pagination((int(off) / (int(config.config_books_per_page)) + 1), config.config_books_per_page, numBooks)
|
||||||
xml = render_title_template('feed.xml', entries=entries, pagination=pagination)
|
xml = render_title_template('feed.xml', entries=entries, pagination=pagination)
|
||||||
|
@ -920,7 +861,7 @@ def get_opds_download_link(book_id, book_format):
|
||||||
data = db.session.query(db.Data).filter(db.Data.book == book.id).filter(db.Data.format == book_format.upper()).first()
|
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))
|
ub.update_download(book_id, int(current_user.id))
|
||||||
file_name = book.title
|
file_name = book.title
|
||||||
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
|
||||||
|
@ -935,7 +876,7 @@ def get_opds_download_link(book_id, book_format):
|
||||||
startTime = time.time()
|
startTime = time.time()
|
||||||
if config.config_use_google_drive:
|
if config.config_use_google_drive:
|
||||||
app.logger.info(time.time() - startTime)
|
app.logger.info(time.time() - startTime)
|
||||||
df = gdriveutils.getFileFromEbooksFolder(Gdrive.Instance().drive, book.path, data.name + "." + book_format)
|
df = gdriveutils.getFileFromEbooksFolder(book.path, 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 + "." + book_format))
|
response = make_response(send_from_directory(os.path.join(config.config_calibre_dir, book.path), data.name + "." + book_format))
|
||||||
|
@ -1158,8 +1099,9 @@ def hot_books(page):
|
||||||
if downloadBook:
|
if downloadBook:
|
||||||
entries.append(downloadBook)
|
entries.append(downloadBook)
|
||||||
else:
|
else:
|
||||||
ub.session.query(ub.Downloads).filter(book.Downloads.book_id == ub.Downloads.book_id).delete()
|
ub.delete_download(book.Downloads.book_id)
|
||||||
ub.session.commit()
|
# ub.session.query(ub.Downloads).filter(book.Downloads.book_id == ub.Downloads.book_id).delete()
|
||||||
|
# ub.session.commit()
|
||||||
numBooks = entries.__len__()
|
numBooks = entries.__len__()
|
||||||
pagination = Pagination(page, config.config_books_per_page, numBooks)
|
pagination = Pagination(page, config.config_books_per_page, numBooks)
|
||||||
return render_title_template('index.html', random=random, entries=entries, pagination=pagination,
|
return render_title_template('index.html', random=random, entries=entries, pagination=pagination,
|
||||||
|
@ -1486,12 +1428,11 @@ def delete_book(book_id):
|
||||||
# delete book from Shelfs, Downloads, Read list
|
# delete book from Shelfs, Downloads, Read list
|
||||||
ub.session.query(ub.BookShelf).filter(ub.BookShelf.book_id == book_id).delete()
|
ub.session.query(ub.BookShelf).filter(ub.BookShelf.book_id == book_id).delete()
|
||||||
ub.session.query(ub.ReadBook).filter(ub.ReadBook.book_id == book_id).delete()
|
ub.session.query(ub.ReadBook).filter(ub.ReadBook.book_id == book_id).delete()
|
||||||
ub.session.query(ub.Downloads).filter(ub.Downloads.book_id == book_id).delete()
|
# ToDo check Downloads.book right
|
||||||
|
ub.delete_download(book_id)
|
||||||
|
# ub.session.query(ub.Downloads).filter(ub.Downloads.book_id == book_id).delete()
|
||||||
ub.session.commit()
|
ub.session.commit()
|
||||||
|
|
||||||
if config.config_use_google_drive:
|
|
||||||
helper.delete_book_gdrive(book) # ToDo really delete file
|
|
||||||
else:
|
|
||||||
helper.delete_book(book, config.config_calibre_dir)
|
helper.delete_book(book, config.config_calibre_dir)
|
||||||
# check if only this book links to:
|
# check if only this book links to:
|
||||||
# author, language, series, tags, custom columns
|
# author, language, series, tags, custom columns
|
||||||
|
@ -1560,7 +1501,7 @@ def watch_gdrive():
|
||||||
address = '%s/gdrive/watch/callback' % filedata['web']['redirect_uris'][0]
|
address = '%s/gdrive/watch/callback' % filedata['web']['redirect_uris'][0]
|
||||||
notification_id = str(uuid4())
|
notification_id = str(uuid4())
|
||||||
try:
|
try:
|
||||||
result = gdriveutils.watchChange(Gdrive.Instance().drive, notification_id,
|
result = gdriveutils.watchChange(gdriveutils.Gdrive.Instance().drive, notification_id,
|
||||||
'web_hook', address, gdrive_watch_callback_token, current_milli_time() + 604800*1000)
|
'web_hook', address, gdrive_watch_callback_token, current_milli_time() + 604800*1000)
|
||||||
settings = ub.session.query(ub.Settings).first()
|
settings = ub.session.query(ub.Settings).first()
|
||||||
settings.config_google_drive_watch_changes_response = json.dumps(result)
|
settings.config_google_drive_watch_changes_response = json.dumps(result)
|
||||||
|
@ -1585,7 +1526,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:
|
||||||
gdriveutils.stopChannel(Gdrive.Instance().drive, last_watch_response['id'], last_watch_response['resourceId'])
|
gdriveutils.stopChannel(gdriveutils.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()
|
||||||
|
@ -1611,7 +1552,7 @@ def on_received_watch_confirmation():
|
||||||
try:
|
try:
|
||||||
j = json.loads(data)
|
j = json.loads(data)
|
||||||
app.logger.info('Getting change details')
|
app.logger.info('Getting change details')
|
||||||
response = gdriveutils.getChangeById(Gdrive.Instance().drive, j['id'])
|
response = gdriveutils.getChangeById(gdriveutils.Gdrive.Instance().drive, j['id'])
|
||||||
app.logger.debug(response)
|
app.logger.debug(response)
|
||||||
if response:
|
if response:
|
||||||
dbpath = os.path.join(config.config_calibre_dir, "metadata.db")
|
dbpath = os.path.join(config.config_calibre_dir, "metadata.db")
|
||||||
|
@ -1620,14 +1561,14 @@ def on_received_watch_confirmation():
|
||||||
app.logger.info('Database file updated')
|
app.logger.info('Database file updated')
|
||||||
copyfile(dbpath, os.path.join(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", os.path.join(tmpDir, "tmp_metadata.db"))
|
gdriveutils.downloadFile(None, "metadata.db", os.path.join(tmpDir, "tmp_metadata.db"))
|
||||||
app.logger.info('Setting up new DB')
|
app.logger.info('Setting up new DB')
|
||||||
# prevent error on windows, as os.rename does on exisiting files
|
# prevent error on windows, as os.rename does on exisiting files
|
||||||
shutil.move(os.path.join(tmpDir, "tmp_metadata.db"), dbpath)
|
shutil.move(os.path.join(tmpDir, "tmp_metadata.db"), dbpath)
|
||||||
db.setup_db()
|
db.setup_db()
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
app.logger.exception(e)
|
app.logger.info(e.message)
|
||||||
|
# app.logger.exception(e)
|
||||||
updateMetaData()
|
updateMetaData()
|
||||||
return ''
|
return ''
|
||||||
|
|
||||||
|
@ -1793,7 +1734,8 @@ 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(cover_path, 'cover.jpg')
|
||||||
|
if df:
|
||||||
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():
|
||||||
df.GetPermissions()
|
df.GetPermissions()
|
||||||
df.InsertPermission({
|
df.InsertPermission({
|
||||||
|
@ -1806,6 +1748,8 @@ def get_cover_via_gdrive(cover_path):
|
||||||
gdriveutils.session.add(permissionAdded)
|
gdriveutils.session.add(permissionAdded)
|
||||||
gdriveutils.session.commit()
|
gdriveutils.session.commit()
|
||||||
return df.metadata.get('webContentLink')
|
return df.metadata.get('webContentLink')
|
||||||
|
else:
|
||||||
|
return None
|
||||||
|
|
||||||
|
|
||||||
@app.route("/cover/<path:cover_path>")
|
@app.route("/cover/<path:cover_path>")
|
||||||
|
@ -1813,9 +1757,15 @@ def get_cover_via_gdrive(cover_path):
|
||||||
def get_cover(cover_path):
|
def get_cover(cover_path):
|
||||||
if config.config_use_google_drive:
|
if config.config_use_google_drive:
|
||||||
try:
|
try:
|
||||||
return redirect(get_cover_via_gdrive(cover_path))
|
path=get_cover_via_gdrive(cover_path)
|
||||||
except:
|
if path:
|
||||||
app.logger.error(cover_path + '/cover.jpg ' + 'not found on GDrive')
|
return redirect(path)
|
||||||
|
else:
|
||||||
|
app.logger.error(cover_path + '/cover.jpg not found on GDrive')
|
||||||
|
return send_from_directory(os.path.join(os.path.dirname(__file__), "static"), "generic_cover.jpg")
|
||||||
|
except Exception as e:
|
||||||
|
app.logger.error("Message "+e.message)
|
||||||
|
# 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")
|
||||||
else:
|
else:
|
||||||
return send_from_directory(os.path.join(config.config_calibre_dir, cover_path), "cover.jpg")
|
return send_from_directory(os.path.join(config.config_calibre_dir, cover_path), "cover.jpg")
|
||||||
|
@ -1834,7 +1784,7 @@ def serve_book(book_id, book_format):
|
||||||
headers["Content-Type"] = mimetypes.types_map['.' + book_format]
|
headers["Content-Type"] = mimetypes.types_map['.' + book_format]
|
||||||
except KeyError:
|
except KeyError:
|
||||||
headers["Content-Type"] = "application/octet-stream"
|
headers["Content-Type"] = "application/octet-stream"
|
||||||
df = gdriveutils.getFileFromEbooksFolder(Gdrive.Instance().drive, book.path, data.name + "." + book_format)
|
df = gdriveutils.getFileFromEbooksFolder(book.path, data.name + "." + book_format)
|
||||||
return do_gdrive_download(df, headers)
|
return do_gdrive_download(df, headers)
|
||||||
else:
|
else:
|
||||||
return send_from_directory(os.path.join(config.config_calibre_dir, book.path), data.name + "." + book_format)
|
return send_from_directory(os.path.join(config.config_calibre_dir, book.path), data.name + "." + book_format)
|
||||||
|
@ -1956,7 +1906,7 @@ def get_download_link(book_id, book_format):
|
||||||
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:
|
||||||
helper.update_download(book_id, int(current_user.id))
|
ub.update_download(book_id, int(current_user.id))
|
||||||
file_name = book.title
|
file_name = book.title
|
||||||
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
|
||||||
|
@ -1968,8 +1918,11 @@ def get_download_link(book_id, book_format):
|
||||||
headers["Content-Type"] = "application/octet-stream"
|
headers["Content-Type"] = "application/octet-stream"
|
||||||
headers["Content-Disposition"] = "attachment; filename*=UTF-8''%s.%s" % (quote(file_name.encode('utf-8')), book_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, book_format))
|
df = gdriveutils.getFileFromEbooksFolder(book.path, '%s.%s' % (data.name, book_format))
|
||||||
|
if df:
|
||||||
return do_gdrive_download(df, headers)
|
return do_gdrive_download(df, headers)
|
||||||
|
else:
|
||||||
|
abort(404)
|
||||||
else:
|
else:
|
||||||
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 + "." + book_format))
|
||||||
response.headers = headers
|
response.headers = headers
|
||||||
|
@ -2150,7 +2103,7 @@ def send_to_kindle(book_id):
|
||||||
if result is None:
|
if result is None:
|
||||||
flash(_(u"Book successfully send to %(kindlemail)s", kindlemail=current_user.kindle_mail),
|
flash(_(u"Book successfully send to %(kindlemail)s", kindlemail=current_user.kindle_mail),
|
||||||
category="success")
|
category="success")
|
||||||
helper.update_download(book_id, int(current_user.id))
|
ub.update_download(book_id, int(current_user.id))
|
||||||
else:
|
else:
|
||||||
flash(_(u"There was an error sending this book: %(res)s", res=result), category="error")
|
flash(_(u"There was an error sending this book: %(res)s", res=result), category="error")
|
||||||
else:
|
else:
|
||||||
|
@ -2398,8 +2351,9 @@ def profile():
|
||||||
if downloadBook:
|
if downloadBook:
|
||||||
downloads.append(db.session.query(db.Books).filter(db.Books.id == book.book_id).first())
|
downloads.append(db.session.query(db.Books).filter(db.Books.id == book.book_id).first())
|
||||||
else:
|
else:
|
||||||
ub.session.query(ub.Downloads).filter(book.book_id == ub.Downloads.book_id).delete()
|
ub.delete_download(book.book_id)
|
||||||
ub.session.commit()
|
# ub.session.query(ub.Downloads).filter(book.book_id == ub.Downloads.book_id).delete()
|
||||||
|
# ub.session.commit()
|
||||||
if request.method == "POST":
|
if request.method == "POST":
|
||||||
to_save = request.form.to_dict()
|
to_save = request.form.to_dict()
|
||||||
content.random_books = 0
|
content.random_books = 0
|
||||||
|
@ -2671,7 +2625,7 @@ def configuration_helper(origin):
|
||||||
reboot_required = True
|
reboot_required = True
|
||||||
try:
|
try:
|
||||||
if content.config_use_google_drive and is_gdrive_ready() and not os.path.exists(config.config_calibre_dir + "/metadata.db"):
|
if content.config_use_google_drive and is_gdrive_ready() and not os.path.exists(config.config_calibre_dir + "/metadata.db"):
|
||||||
gdriveutils.downloadFile(Gdrive.Instance().drive, None, "metadata.db", config.config_calibre_dir + "/metadata.db")
|
gdriveutils.downloadFile(None, "metadata.db", config.config_calibre_dir + "/metadata.db")
|
||||||
if db_change:
|
if db_change:
|
||||||
if config.db_configured:
|
if config.db_configured:
|
||||||
db.session.close()
|
db.session.close()
|
||||||
|
@ -2842,8 +2796,9 @@ def edit_user(user_id):
|
||||||
if downloadBook:
|
if downloadBook:
|
||||||
downloads.append(db.session.query(db.Books).filter(db.Books.id == book.book_id).first())
|
downloads.append(db.session.query(db.Books).filter(db.Books.id == book.book_id).first())
|
||||||
else:
|
else:
|
||||||
ub.session.query(ub.Downloads).filter(book.book_id == ub.Downloads.book_id).delete()
|
ub.delete_download(book.book_id)
|
||||||
ub.session.commit()
|
# ub.session.query(ub.Downloads).filter(book.book_id == ub.Downloads.book_id).delete()
|
||||||
|
# ub.session.commit()
|
||||||
if request.method == "POST":
|
if request.method == "POST":
|
||||||
to_save = request.form.to_dict()
|
to_save = request.form.to_dict()
|
||||||
if "delete" in to_save:
|
if "delete" in to_save:
|
||||||
|
@ -3051,16 +3006,14 @@ def edit_book(book_id):
|
||||||
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 config.config_use_google_drive:
|
||||||
|
gdriveutils.updateGdriveCalibreFromLocal()
|
||||||
|
|
||||||
error = False
|
error = False
|
||||||
for b in edited_books_id:
|
for b in edited_books_id:
|
||||||
if config.config_use_google_drive:
|
|
||||||
error = helper.update_dir_structure_gdrive(b)
|
|
||||||
else:
|
|
||||||
error = helper.update_dir_stucture(b, config.config_calibre_dir)
|
error = helper.update_dir_stucture(b, config.config_calibre_dir)
|
||||||
if error: # stop on error
|
if error: # stop on error
|
||||||
break
|
break
|
||||||
if config.config_use_google_drive:
|
|
||||||
updateGdriveCalibreFromLocal()
|
|
||||||
|
|
||||||
if not error:
|
if not error:
|
||||||
if to_save["cover_url"]:
|
if to_save["cover_url"]:
|
||||||
|
@ -3232,7 +3185,7 @@ def save_cover(url, book_path):
|
||||||
f = open(os.path.join(tmpDir, "uploaded_cover.jpg"), "wb")
|
f = open(os.path.join(tmpDir, "uploaded_cover.jpg"), "wb")
|
||||||
f.write(img.content)
|
f.write(img.content)
|
||||||
f.close()
|
f.close()
|
||||||
gdriveutils.uploadFileToEbooksFolder(Gdrive.Instance().drive, os.path.join(book_path, 'cover.jpg'), os.path.join(tmpDir, f.name))
|
gdriveutils.uploadFileToEbooksFolder(os.path.join(book_path, 'cover.jpg'), os.path.join(tmpDir, f.name))
|
||||||
app.logger.info("Cover is saved on gdrive")
|
app.logger.info("Cover is saved on gdrive")
|
||||||
return true
|
return true
|
||||||
|
|
||||||
|
@ -3344,29 +3297,30 @@ def upload():
|
||||||
|
|
||||||
db.session.add(db_book)
|
db.session.add(db_book)
|
||||||
db.session.flush() # flush content get db_book.id avalible
|
db.session.flush() # flush content get db_book.id avalible
|
||||||
# ToDo: Book should be moved to foldername with id in it
|
|
||||||
if config.config_use_google_drive:
|
|
||||||
error = helper.update_dir_structure_gdrive(db_book.id)
|
|
||||||
else:
|
|
||||||
error = helper.update_dir_stucture(db_book.id, config.config_calibre_dir)
|
|
||||||
# ToDo: Handle error
|
|
||||||
# add comment
|
# add comment
|
||||||
upload_comment = Markup(meta.description).unescape()
|
upload_comment = Markup(meta.description).unescape()
|
||||||
if upload_comment != "":
|
if upload_comment != "":
|
||||||
db.session.add(db.Comments(upload_comment, db_book.id))
|
db.session.add(db.Comments(upload_comment, db_book.id))
|
||||||
db.session.commit()
|
|
||||||
|
|
||||||
input_tags = tags.split(',')
|
input_tags = tags.split(',')
|
||||||
input_tags = list(map(lambda it: it.strip(), input_tags))
|
input_tags = list(map(lambda it: it.strip(), input_tags))
|
||||||
if input_tags[0] !="":
|
if input_tags[0] !="":
|
||||||
modify_database_object(input_tags, db_book.tags, db.Tags, db.session, 'tags')
|
modify_database_object(input_tags, db_book.tags, db.Tags, db.session, 'tags')
|
||||||
|
|
||||||
|
db.session.commit()
|
||||||
|
|
||||||
|
if config.config_use_google_drive:
|
||||||
|
gdriveutils.updateGdriveCalibreFromLocal()
|
||||||
|
error = helper.update_dir_stucture(db_book.id, config.config_calibre_dir)
|
||||||
|
# ToDo: Handle error
|
||||||
|
if error:
|
||||||
|
pass
|
||||||
|
|
||||||
if db_language is not None: # display Full name instead of iso639.part3
|
if db_language is not None: # display Full name instead of iso639.part3
|
||||||
db_book.languages[0].language_name = _(meta.languages)
|
db_book.languages[0].language_name = _(meta.languages)
|
||||||
author_names = []
|
author_names = []
|
||||||
for author in db_book.authors:
|
for author in db_book.authors:
|
||||||
author_names.append(author.name)
|
author_names.append(author.name)
|
||||||
if config.config_use_google_drive:
|
|
||||||
updateGdriveCalibreFromLocal()
|
|
||||||
if len(request.files.getlist("btn-upload")) < 2:
|
if len(request.files.getlist("btn-upload")) < 2:
|
||||||
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():
|
||||||
|
|
Loading…
Reference in New Issue
Block a user