Commit 204cd504 authored by OzzieIsaacs's avatar OzzieIsaacs

- Completly new setup of GDrive (every data is taken from client_secret.json file)

- If cover on Google Drive is missing, the generic cover will be used
- Better error handling during configuration of google drive (missing hook callback verification, missing files)
- less errors on console during use of Gdrive, more log file output in case of error
- removed standalone flask development mode
- removed gdrive template file and vendor setup script
parent 43a391d1
...@@ -21,27 +21,23 @@ except ImportError: ...@@ -21,27 +21,23 @@ except ImportError:
gevent_present = False gevent_present = False
if __name__ == '__main__': if __name__ == '__main__':
if web.ub.DEVELOPMENT: if gevent_present:
web.app.run(port=web.ub.config.config_port, debug=True) web.app.logger.info('Attempting to start gevent')
web.start_gevent()
else: else:
if gevent_present: web.app.logger.info('Starting Tornado webserver')
web.app.logger.info('Attempting to start gevent') # Max Buffersize set to 200MB
web.start_gevent() if web.ub.config.get_config_certfile() and web.ub.config.get_config_keyfile():
ssl={"certfile": web.ub.config.get_config_certfile(),
"keyfile": web.ub.config.get_config_keyfile()}
else: else:
web.app.logger.info('Falling back to Tornado') ssl=None
# Max Buffersize set to 200MB http_server = HTTPServer(WSGIContainer(web.app),
if web.ub.config.get_config_certfile() and web.ub.config.get_config_keyfile(): max_buffer_size = 209700000,
ssl={"certfile": web.ub.config.get_config_certfile(), ssl_options=ssl)
"keyfile": web.ub.config.get_config_keyfile()} http_server.listen(web.ub.config.config_port)
else: IOLoop.instance().start()
ssl=None IOLoop.instance().close(True)
http_server = HTTPServer(WSGIContainer(web.app),
max_buffer_size = 209700000,
ssl_options=ssl)
http_server.listen(web.ub.config.config_port)
IOLoop.instance().start()
IOLoop.instance().close(True)
if web.helper.global_task == 0: if web.helper.global_task == 0:
web.app.logger.info("Performing restart of Calibre-web") web.app.logger.info("Performing restart of Calibre-web")
......
...@@ -68,7 +68,6 @@ if not os.path.exists(cli.gdpath): ...@@ -68,7 +68,6 @@ if not os.path.exists(cli.gdpath):
Base.metadata.create_all(engine) Base.metadata.create_all(engine)
except Exception: except Exception:
raise raise
migrate() migrate()
...@@ -80,7 +79,10 @@ def getDrive(drive=None, gauth=None): ...@@ -80,7 +79,10 @@ def getDrive(drive=None, gauth=None):
gauth.LoadCredentialsFile("gdrive_credentials") gauth.LoadCredentialsFile("gdrive_credentials")
if gauth.access_token_expired: if gauth.access_token_expired:
# Refresh them if expired # Refresh them if expired
gauth.Refresh() try:
gauth.Refresh()
except:
web.app.logger.error("GDrive gdrive_credentials file not present, reauthenticate in config section")
else: else:
# Initialize the saved creds # Initialize the saved creds
gauth.Authorize() gauth.Authorize()
...@@ -90,12 +92,24 @@ def getDrive(drive=None, gauth=None): ...@@ -90,12 +92,24 @@ def getDrive(drive=None, gauth=None):
drive.auth.Refresh() drive.auth.Refresh()
return drive return drive
def listRootFolders(drive=None):
drive = getDrive(drive)
folder = "'root' in parents and mimeType = 'application/vnd.google-apps.folder' and trashed = false"
fileList = drive.ListFile({'q': folder}).GetList()
return fileList
def getEbooksFolder(drive=None): def getEbooksFolder(drive=None):
drive = getDrive(drive) return getFolderInFolder('root',config.config_google_drive_folder,drive)
ebooksFolder = "title = '%s' and 'root' in parents and mimeType = 'application/vnd.google-apps.folder' and trashed = false" % config.config_google_drive_folder
fileList = drive.ListFile({'q': ebooksFolder}).GetList()
def getFolderInFolder(parentId, folderName,drive=None):
drive = getDrive(drive)
query=""
if folderName:
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()
return fileList[0] return fileList[0]
...@@ -105,20 +119,17 @@ def getEbooksFolderId(drive=None): ...@@ -105,20 +119,17 @@ def getEbooksFolderId(drive=None):
return storedPathName.gdrive_id return storedPathName.gdrive_id
else: else:
gDriveId = GdriveId() gDriveId = GdriveId()
gDriveId.gdrive_id = getEbooksFolder(drive)['id'] try:
gDriveId.gdrive_id = getEbooksFolder(drive)['id']
except:
pass
# ToDo Path not exisiting
gDriveId.path = '/' gDriveId.path = '/'
session.merge(gDriveId) session.merge(gDriveId)
session.commit() session.commit()
return return
def getFolderInFolder(parentId, folderName, drive=None):
drive = getDrive(drive)
folder = "title = '%s' and '%s' in parents and mimeType = 'application/vnd.google-apps.folder' and trashed = false" % (folderName.replace("'", "\\'"), parentId)
fileList = drive.ListFile({'q': folder}).GetList()
return fileList[0]
def getFile(pathId, fileName, drive=None): def getFile(pathId, fileName, drive=None):
drive = getDrive(drive) drive = getDrive(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("'", "\\'"))
...@@ -190,11 +201,8 @@ def downloadFile(drive, path, filename, output): ...@@ -190,11 +201,8 @@ def downloadFile(drive, path, filename, output):
def backupCalibreDbAndOptionalDownload(drive, f=None): def backupCalibreDbAndOptionalDownload(drive, f=None):
drive = getDrive(drive) drive = getDrive(drive)
metaDataFile = "'%s' in parents and title = 'metadata.db' and trashed = false" % getEbooksFolderId() metaDataFile = "'%s' in parents and title = 'metadata.db' and trashed = false" % getEbooksFolderId()
fileList = drive.ListFile({'q': metaDataFile}).GetList() fileList = drive.ListFile({'q': metaDataFile}).GetList()
databaseFile = fileList[0] databaseFile = fileList[0]
if f: if f:
databaseFile.GetContentFile(f) databaseFile.GetContentFile(f)
...@@ -343,3 +351,8 @@ def getChangeById (drive, change_id): ...@@ -343,3 +351,8 @@ def getChangeById (drive, change_id):
except (errors.HttpError, error): except (errors.HttpError, error):
web.app.logger.exception(error) web.app.logger.exception(error)
return None return None
# Deletes the local hashes database to force search for new folder names
def deleteDatabaseOnChange():
session.query(GdriveId).delete()
session.commit()
...@@ -7,47 +7,43 @@ ...@@ -7,47 +7,43 @@
<label for="config_calibre_dir">{{_('Location of Calibre database')}}</label> <label for="config_calibre_dir">{{_('Location of Calibre database')}}</label>
<input type="text" class="form-control" name="config_calibre_dir" id="config_calibre_dir" value="{% if content.config_calibre_dir != None %}{{ content.config_calibre_dir }}{% endif %}" autocomplete="off"> <input type="text" class="form-control" name="config_calibre_dir" id="config_calibre_dir" value="{% if content.config_calibre_dir != None %}{{ content.config_calibre_dir }}{% endif %}" autocomplete="off">
</div> </div>
{% if gdrive %}
<div class="form-group required"> <div class="form-group required">
<input type="checkbox" id="config_use_google_drive" name="config_use_google_drive" data-control="gdrive_settings" {% if content.config_use_google_drive %}checked{% endif %} > <input type="checkbox" id="config_use_google_drive" name="config_use_google_drive" data-control="gdrive_settings" {% if content.config_use_google_drive %}checked{% endif %} >
<label for="config_use_google_drive">{{_('Use google drive?')}}</label> <label for="config_use_google_drive">{{_('Use google drive?')}}</label>
</div> </div>
<div data-related="gdrive_settings"> <div data-related="gdrive_settings">
<div class="form-group required"> {% if gdriveError %}
<label for="config_google_drive_client_id">{{_('Client id')}}</label> <div class="form-group">
<input type="text" class="form-control" name="config_google_drive_client_id" id="config_google_drive_client_id" value="{% if content.config_google_drive_client_id %}{{content.config_google_drive_client_id}}{% endif %}" autocomplete="off"> <label>
</div> {{_('Google drive config problem')}}: {{ gdriveError }}
<div class="form-group required"> </label>
<label for="config_google_drive_client_secret">{{_('Client secret')}}</label>
<input type="text" class="form-control" name="config_google_drive_client_secret" id="config_google_drive_client_secret" value="{% if content.config_google_drive_client_secret %}{{content.config_google_drive_client_secret}}{% endif %}" autocomplete="off">
</div>
<div class="form-group required">
<label for="config_google_drive_calibre_url_base">{{_('Calibre-web Base URL')}}</label>
<input type="text" class="form-control" name="config_google_drive_calibre_url_base" id="config_google_drive_calibre_url_base" value="{% if content.config_google_drive_calibre_url_base %}{{content.config_google_drive_calibre_url_base}}{% endif %}" autocomplete="off">
</div>
<div class="form-group required">
<label for="config_google_drive_folder">{{_('Google drive Calibre folder')}}</label>
<input type="text" class="form-control" name="config_google_drive_folder" id="config_google_drive_folder" value="{% if content.config_google_drive_folder %}{{content.config_google_drive_folder}}{% endif %}" autocomplete="off" required>
</div>
{% if show_authenticate_google_drive %}
<div class="form-group required">
<a href="{{ url_for('authenticate_google_drive') }}" class="btn btn-primary">Authenticate Google Drive</a>
</div> </div>
{% else %} {% else %}
{% if content.config_google_drive_watch_changes_response %} {% if show_authenticate_google_drive %}
<label for="config_google_drive_watch_changes_response">{{_('Metadata Watch Channel ID')}}</label> <div class="form-group required">
<div class="form-group input-group required"> <a href="{{ url_for('authenticate_google_drive') }}" class="btn btn-primary">{{_('Authenticate Google Drive')}}</a>
<input type="text" class="form-control" name="config_google_drive_watch_changes_response" id="config_google_drive_watch_changes_response" value="{{ content.config_google_drive_watch_changes_response['id'] }} expires on {{ content.config_google_drive_watch_changes_response['expiration'] | strftime }}" autocomplete="off" disabled=""> </div>
<span class="input-group-btn"> {% else %}
<a href="{{ url_for('revoke_watch_gdrive') }}" class="btn btn-primary">Revoke</a> <div class="form-group required">
</span> <label for="config_google_drive_folder">{{_('Google drive Calibre folder')}}</label>
<select name="config_google_drive_folder" id="config_google_drive_folder" class="form-control">
{% for gdrivefolder in gdrivefolders %}
<option value="{{ gdrivefolder.title }}" {% if gdrivefolder.title == content.config_google_drive_folder %}selected{% endif %}>{{ gdrivefolder.title }}</option>
{% endfor %}
</select>
</div>
{% if content.config_google_drive_watch_changes_response %}
<label for="config_google_drive_watch_changes_response">{{_('Metadata Watch Channel ID')}}</label>
<div class="form-group input-group required">
<input type="text" class="form-control" name="config_google_drive_watch_changes_response" id="config_google_drive_watch_changes_response" value="{{ content.config_google_drive_watch_changes_response['id'] }} expires on {{ content.config_google_drive_watch_changes_response['expiration'] | strftime }}" autocomplete="off" disabled="">
<span class="input-group-btn"><a href="{{ url_for('revoke_watch_gdrive') }}" class="btn btn-primary">{{_('Revoke')}}</a></span>
</div>
{% else %} {% else %}
<a href="{{ url_for('watch_gdrive') }}" class="btn btn-primary">Enable watch of metadata.db</a> <a href="{{ url_for('watch_gdrive') }}" class="btn btn-primary">Enable watch of metadata.db</a>
{% endif %} {% endif %}
</div>
{% endif %} {% endif %}
{% endif %}
</div> </div>
{% endif %}
<div class="form-group"> <div class="form-group">
<label for="config_port">{{_('Server Port')}}</label> <label for="config_port">{{_('Server Port')}}</label>
<input type="number" min="1" max="65535" class="form-control" name="config_port" id="config_port" value="{% if content.config_port != None %}{{ content.config_port }}{% endif %}" autocomplete="off" required> <input type="number" min="1" max="65535" class="form-control" name="config_port" id="config_port" value="{% if content.config_port != None %}{{ content.config_port }}{% endif %}" autocomplete="off" required>
......
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
...@@ -291,10 +291,10 @@ class Settings(Base): ...@@ -291,10 +291,10 @@ class Settings(Base):
config_default_show = Column(SmallInteger, default=2047) config_default_show = Column(SmallInteger, default=2047)
config_columns_to_ignore = Column(String) config_columns_to_ignore = Column(String)
config_use_google_drive = Column(Boolean) config_use_google_drive = Column(Boolean)
config_google_drive_client_id = Column(String) # config_google_drive_client_id = Column(String)
config_google_drive_client_secret = Column(String) # config_google_drive_client_secret = Column(String)
config_google_drive_folder = Column(String) config_google_drive_folder = Column(String)
config_google_drive_calibre_url_base = Column(String) # config_google_drive_calibre_url_base = Column(String)
config_google_drive_watch_changes_response = Column(String) config_google_drive_watch_changes_response = Column(String)
config_remote_login = Column(Boolean) config_remote_login = Column(Boolean)
config_use_goodreads = Column(Boolean) config_use_goodreads = Column(Boolean)
...@@ -352,9 +352,9 @@ class Config: ...@@ -352,9 +352,9 @@ class Config:
self.config_default_show = data.config_default_show self.config_default_show = data.config_default_show
self.config_columns_to_ignore = data.config_columns_to_ignore self.config_columns_to_ignore = data.config_columns_to_ignore
self.config_use_google_drive = data.config_use_google_drive self.config_use_google_drive = data.config_use_google_drive
self.config_google_drive_client_id = data.config_google_drive_client_id # self.config_google_drive_client_id = data.config_google_drive_client_id
self.config_google_drive_client_secret = data.config_google_drive_client_secret # self.config_google_drive_client_secret = data.config_google_drive_client_secret
self.config_google_drive_calibre_url_base = data.config_google_drive_calibre_url_base # self.config_google_drive_calibre_url_base = data.config_google_drive_calibre_url_base
self.config_google_drive_folder = data.config_google_drive_folder self.config_google_drive_folder = data.config_google_drive_folder
if data.config_google_drive_watch_changes_response: if data.config_google_drive_watch_changes_response:
self.config_google_drive_watch_changes_response = json.loads(data.config_google_drive_watch_changes_response) self.config_google_drive_watch_changes_response = json.loads(data.config_google_drive_watch_changes_response)
...@@ -550,9 +550,9 @@ def migrate_Database(): ...@@ -550,9 +550,9 @@ def migrate_Database():
except exc.OperationalError: except exc.OperationalError:
conn = engine.connect() conn = engine.connect()
conn.execute("ALTER TABLE Settings ADD column `config_use_google_drive` INTEGER DEFAULT 0") conn.execute("ALTER TABLE Settings ADD column `config_use_google_drive` INTEGER DEFAULT 0")
conn.execute("ALTER TABLE Settings ADD column `config_google_drive_client_id` String DEFAULT ''") # conn.execute("ALTER TABLE Settings ADD column `config_google_drive_client_id` String DEFAULT ''")
conn.execute("ALTER TABLE Settings ADD column `config_google_drive_client_secret` String DEFAULT ''") # conn.execute("ALTER TABLE Settings ADD column `config_google_drive_client_secret` String DEFAULT ''")
conn.execute("ALTER TABLE Settings ADD column `config_google_drive_calibre_url_base` INTEGER DEFAULT 0") # conn.execute("ALTER TABLE Settings ADD column `config_google_drive_calibre_url_base` INTEGER DEFAULT 0")
conn.execute("ALTER TABLE Settings ADD column `config_google_drive_folder` String DEFAULT ''") conn.execute("ALTER TABLE Settings ADD column `config_google_drive_folder` String DEFAULT ''")
conn.execute("ALTER TABLE Settings ADD column `config_google_drive_watch_changes_response` String DEFAULT ''") conn.execute("ALTER TABLE Settings ADD column `config_google_drive_watch_changes_response` String DEFAULT ''")
try: try:
......
This diff is collapsed.
client_config_backend: settings
client_config:
client_id: %(client_id)s
client_secret: %(client_secret)s
redirect_uri: %(redirect_uri)s
save_credentials: True
save_credentials_backend: file
save_credentials_file: gdrive_credentials
get_refresh_token: True
oauth_scope:
- https://www.googleapis.com/auth/drive
#!/bin/bash -e
pip install --target ./vendor -r requirements.txt
This diff is collapsed.
gevent==1.2.1 # GDrive Integration
google-api-python-client==1.6.1 google-api-python-client==1.6.1
gevent==1.2.1
greenlet==0.4.12 greenlet==0.4.12
httplib2==0.9.2 httplib2==0.9.2
lxml==3.7.2
oauth2client==4.0.0 oauth2client==4.0.0
uritemplate==3.0.0
pyasn1-modules==0.0.8 pyasn1-modules==0.0.8
pyasn1==0.1.9 pyasn1==0.1.9
PyDrive==1.3.1 PyDrive==1.3.1
PyYAML==3.12 PyYAML==3.12
rsa==3.4.2 rsa==3.4.2
six==1.10.0 six==1.10.0
uritemplate==3.0.0 # goodreads
goodreads>=0.3.2 goodreads>=0.3.2
python-Levenshtein>=0.12.0 python-Levenshtein>=0.12.0
# other
lxml==3.7.2
...@@ -88,22 +88,22 @@ Once a project has been created, we need to create a client ID and a client secr ...@@ -88,22 +88,22 @@ Once a project has been created, we need to create a client ID and a client secr
The Drive API should now be setup and ready to use, so we need to integrate it into Calibre-Web. This is done as below: - The Drive API should now be setup and ready to use, so we need to integrate it into Calibre-Web. This is done as below: -
1. Open config page 1. Open config page
2. Enter the location that will be used to store the metadata.db file, and to temporary store uploaded books and other temporary files for upload 2. Enter the location that will be used to store the metadata.db file locally, and to temporary store uploaded books and other temporary files for upload ("Location of Calibre database")
2. Tick Use Google Drive 2. Tick Use Google Drive
3. Enter Client Secret and Client Key as provided via previous steps 3. Click the "Submit" button
4. Enter the folder that is the root of your calibre library 4. Now select Authenticate Google Drive
5. Enter base URL for calibre-web (used for google callbacks) 5. This should redirect you to Google to allow it top use your Drive, and then redirect you back to the config page
6. Click the "Submit" button 6. Select the folder that is the root of your calibre library on Gdrive ("Google drive Calibre folder")
7. Come back to the configuration form 7. Click the "Submit" button
8. Now select Authenticate Google Drive 8. Google Drive should now be connected and be used to get images and download Epubs. The metadata.db is stored in the calibre library location
9. This should redirect you to google to allow it top use your Drive, and then redirect you back to the config page
10. Google Drive should now be connected and be used to get images and download Epubs. The metadata.db is stored in the calibre library location
### Optional ### Optional
If your calibre web is using https, it is possible to add a "watch" to the drive. This will inform us if the metadata.db file is updated and allow us to update our calibre library accordingly. If your calibre web is using https, it is possible to add a "watch" to the drive. This will inform us if the metadata.db file is updated and allow us to update our calibre library accordingly.
Additionally the public adress your server uses (e.g.https://example.com) has to be verified in the Google developer console. After this is done, please wait a few minutes.
11. Click enable watch of metadata.db 9. Open config page
12. Note that this expires after a week, so will need to be manually refresh 10. Click enable watch of metadata.db
11. Note that this expires after a week, so will need to be manually refresh
## Docker image ## Docker image
......
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