Commit a8040ad3 authored by OzzieIsaacs's avatar OzzieIsaacs

#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
parent 413b10c5
......@@ -5,9 +5,9 @@ try:
except ImportError:
pass
import os
from ub import config
import cli
import shutil
from sqlalchemy import *
from sqlalchemy.ext.declarative import declarative_base
......@@ -16,6 +16,57 @@ from sqlalchemy.orm import *
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)
Base = declarative_base()
......@@ -110,9 +161,12 @@ def getFolderInFolder(parentId, folderName,drive=None):
query = "title = '%s' and " % folderName.replace("'", "\\'")
folder = query + "'%s' in parents and mimeType = 'application/vnd.google-apps.folder' and trashed = false" % parentId
fileList = drive.ListFile({'q': folder}).GetList()
if fileList.__len__() == 0:
return None
else:
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):
storedPathName = session.query(GdriveId).filter(GdriveId.path == '/').first()
if storedPathName:
......@@ -131,10 +185,13 @@ def getEbooksFolderId(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("'", "\\'"))
fileList = drive.ListFile({'q': metaDataFile}).GetList()
if fileList.__len__() == 0:
return None
else:
return fileList[0]
......@@ -156,12 +213,17 @@ def getFolderId(path, drive=None):
if storedPathName:
currentFolderId = storedPathName.gdrive_id
else:
currentFolderId = getFolderInFolder(currentFolderId, x, drive)['id']
currentFolder = getFolderInFolder(currentFolderId, x, drive)
if currentFolder:
gDriveId = GdriveId()
gDriveId.gdrive_id = currentFolderId
gDriveId.gdrive_id = currentFolder['id']
gDriveId.path = currentPath
session.merge(gDriveId)
dbChange = True
currentFolderId = currentFolder['id']
else:
currentFolderId= None
break
if dbChange:
session.commit()
else:
......@@ -169,15 +231,17 @@ def getFolderId(path, drive=None):
return currentFolderId
def getFileFromEbooksFolder(drive, path, fileName):
drive = getDrive(drive)
def getFileFromEbooksFolder(path, fileName):
drive = getDrive(Gdrive.Instance().drive)
if path:
# sqlCheckPath=path if path[-1] =='/' else path + '/'
folderId = getFolderId(path, drive)
else:
folderId = getEbooksFolderId(drive)
if folderId:
return getFile(folderId, fileName, drive)
else:
return None
def copyDriveFileRemote(drive, origin_file_id, copy_title):
......@@ -192,9 +256,9 @@ def copyDriveFileRemote(drive, origin_file_id, copy_title):
return None
def downloadFile(drive, path, filename, output):
drive = getDrive(drive)
f = getFileFromEbooksFolder(drive, path, filename)
def downloadFile(path, filename, output):
# drive = getDrive(drive)
f = getFileFromEbooksFolder(path, filename)
f.GetContentFile(output)
......@@ -238,8 +302,8 @@ def copyToDrive(drive, uploadFile, createRoot, replaceFiles,
driveFile.Upload()
def uploadFileToEbooksFolder(drive, destFile, f):
drive = getDrive(drive)
def uploadFileToEbooksFolder(destFile, f):
drive = getDrive(Gdrive.Instance().drive)
parent = getEbooksFolder(drive)
splitDir = destFile.split('/')
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()
return change
except (errors.HttpError) as error:
web.app.logger.exception(error)
app.logger.info(error.message)
return None
# Deletes the local hashes database to force search for new folder names
def deleteDatabaseOnChange():
session.query(GdriveId).delete()
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
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):
error_message = None
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):
"""Get file as MIMEBase message"""
try:
file_ = open(file_path, 'rb')
attachment = MIMEBase('application', 'octet-stream')
......@@ -306,7 +295,7 @@ def get_sorted_author(value):
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
if "/" in book.path:
path = os.path.join(calibrepath, book.path)
......@@ -314,16 +303,8 @@ def delete_book(book, calibrepath):
else:
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(book_id, calibrepath):
def update_dir_stucture_file(book_id, calibrepath):
localbook = db.session.query(db.Books).filter(db.Books.id == book_id).first()
path = os.path.join(calibrepath, localbook.path)
......@@ -372,18 +353,64 @@ def update_dir_structure_gdrive(book_id):
if titledir != new_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.Upload()
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:
gFile = gd.getFileFromEbooksFolder(web.Gdrive.Instance().drive, None, authordir)
gFile['title'] = new_authordir
gFile = gd.getFileFromEbooksFolder(os.path.dirname(book.path), titledir)
# 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()
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]
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
# 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):
......
......@@ -7,7 +7,6 @@ import sys
import os
try:
from gevent.pywsgi import WSGIServer
from gevent import monkey
from gevent.pool import Pool
from gevent import __version__ as geventVersion
gevent_present = True
......@@ -52,7 +51,6 @@ class server:
if gevent_present:
web.app.logger.info('Starting Gevent server')
# leave subprocess out to allow forking for fetchers and processors
monkey.patch_all(subprocess=False)
self.start_gevent()
else:
web.app.logger.info('Starting Tornado server')
......
......@@ -691,6 +691,20 @@ def get_mail_settings():
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
def create_anonymous_user():
......
This diff is collapsed.
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment