Fixes Google Drive

Improved user guidance for Google Drive
Improved Errorhandling for Google Drive
Update french translation
Added ukranian translation
Improvements on updater: calibre-web.log1, calibre-web.log2, gdrive_credentials, settings.yaml, gdrive.db, .git are no longer deleted upon update
pull/806/head
Ozzieisaacs 6 years ago
parent d81cb2927a
commit ea98323c06

@ -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:
@ -166,7 +166,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():
@ -454,6 +457,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():

@ -427,6 +427,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 +436,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")

@ -31,15 +31,19 @@
</label> </label>
</div> </div>
{% else %} {% else %}
{% if show_authenticate_google_drive and g.user.is_authenticated %} {% if show_authenticate_google_drive and g.user.is_authenticated and content.config_use_google_drive %}
<div class="form-group required"> <div class="form-group required">
<a href="{{ url_for('authenticate_google_drive') }}" class="btn btn-primary">{{_('Authenticate Google Drive')}}</a> <a href="{{ url_for('authenticate_google_drive') }}" class="btn btn-primary">{{_('Authenticate Google Drive')}}</a>
</div> </div>
{% else %} {% else %}
{% if show_authenticate_google_drive and not g.user.is_authenticated %} {% if show_authenticate_google_drive and g.user.is_authenticated and not content.config_use_google_drive %}
<div >{{_('Please hit submit to continue with setup')}}</div>
{% endif %}
{% if not g.user.is_authenticated %}
<div >{{_('Please finish Google Drive setup after login')}}</div> <div >{{_('Please finish Google Drive setup after login')}}</div>
{% endif %} {% endif %}
{% if not show_authenticate_google_drive %} {% if g.user.is_authenticated %}
{% if not show_authenticate_google_drive %}
<div class="form-group required"> <div class="form-group required">
<label for="config_google_drive_folder">{{_('Google Drive Calibre folder')}}</label> <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"> <select name="config_google_drive_folder" id="config_google_drive_folder" class="form-control">
@ -58,6 +62,7 @@
<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 %}
{% endif %} {% endif %}
{% endif %}
{% endif %} {% endif %}
{% endif %} {% endif %}
</div> </div>

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

@ -72,7 +72,6 @@ class Updater(threading.Thread):
r = requests.get(self._get_request_path(), stream=True) r = requests.get(self._get_request_path(), stream=True)
r.raise_for_status() r.raise_for_status()
fname = re.findall("filename=(.+)", r.headers['content-disposition'])[0]
self.status = 2 self.status = 2
z = zipfile.ZipFile(BytesIO(r.content)) z = zipfile.ZipFile(BytesIO(r.content))
self.status = 3 self.status = 3
@ -189,8 +188,9 @@ class Updater(threading.Thread):
# destination files # destination files
old_list = list() old_list = list()
exclude = ( exclude = (
'vendor' + os.sep + 'kindlegen.exe', 'vendor' + os.sep + 'kindlegen', os.sep + 'app.db', os.sep + 'app.db', os.sep + 'calibre-web.log1', os.sep + 'calibre-web.log2', os.sep + 'gdrive.db',
os.sep + 'vendor', os.sep + 'calibre-web.log') os.sep + 'vendor', os.sep + 'calibre-web.log', os.sep + '.git', os.sep +'client_secrets.json',
os.sep + 'gdrive_credentials', os.sep + 'settings.yaml')
for root, dirs, files in os.walk(destination, topdown=True): for root, dirs, files in os.walk(destination, topdown=True):
for name in files: for name in files:
old_list.append(os.path.join(root, name).replace(destination, '')) old_list.append(os.path.join(root, name).replace(destination, ''))
@ -237,7 +237,7 @@ class Updater(threading.Thread):
return False return False
def _stable_version_info(self): def _stable_version_info(self):
return {'version': '0.6.0'} # Current version return {'version': '0.6.1'} # Current version
def _nightly_available_updates(self, request_method): def _nightly_available_updates(self, request_method):
tz = datetime.timedelta(seconds=time.timezone if (time.localtime().tm_isdst == 0) else time.altzone) tz = datetime.timedelta(seconds=time.timezone if (time.localtime().tm_isdst == 0) else time.altzone)

@ -78,6 +78,7 @@ import time
import server import server
from reverseproxy import ReverseProxied from reverseproxy import ReverseProxied
from updater import updater_thread from updater import updater_thread
import hashlib
try: try:
from googleapiclient.errors import HttpError from googleapiclient.errors import HttpError
@ -961,11 +962,9 @@ def get_email_status_json():
# example SELECT * FROM @TABLE WHERE 'abcdefg' LIKE Name; # example SELECT * FROM @TABLE WHERE 'abcdefg' LIKE Name;
# from https://code.luasoftware.com/tutorials/flask/execute-raw-sql-in-flask-sqlalchemy/ # from https://code.luasoftware.com/tutorials/flask/execute-raw-sql-in-flask-sqlalchemy/
def check_valid_domain(domain_text): def check_valid_domain(domain_text):
# result = session.query(Notification).from_statement(text(sql)).params(id=5).all()
#ToDo: check possible SQL injection
domain_text = domain_text.split('@',1)[-1].lower() domain_text = domain_text.split('@',1)[-1].lower()
sql = "SELECT * FROM registration WHERE '%s' LIKE domain;" % domain_text sql = "SELECT * FROM registration WHERE :domain LIKE domain;"
result = ub.session.query(ub.Registration).from_statement(text(sql)).all() result = ub.session.query(ub.Registration).from_statement(text(sql)).params(domain=domain_text).all()
return len(result) return len(result)
@ -1725,7 +1724,12 @@ def delete_book(book_id, book_format):
@login_required @login_required
@admin_required @admin_required
def authenticate_google_drive(): def authenticate_google_drive():
authUrl = gdriveutils.Gauth.Instance().auth.GetAuthUrl() try:
authUrl = gdriveutils.Gauth.Instance().auth.GetAuthUrl()
except gdriveutils.InvalidConfigError:
flash(_(u'Google Drive setup not completed, try to deactivate and activate Google Drive again'),
category="error")
return redirect(url_for('index'))
return redirect(authUrl) return redirect(authUrl)
@ -1813,7 +1817,7 @@ def on_received_watch_confirmation():
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")
if not response['deleted'] and response['file']['title'] == 'metadata.db' and response['file']['md5Checksum'] != md5(dbpath): if not response['deleted'] and response['file']['title'] == 'metadata.db' and response['file']['md5Checksum'] != hashlib.md5(dbpath):
tmpDir = tempfile.gettempdir() tmpDir = tempfile.gettempdir()
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())))
@ -2909,6 +2913,8 @@ def configuration_helper(origin):
content.config_calibre_dir = to_save["config_calibre_dir"] content.config_calibre_dir = to_save["config_calibre_dir"]
db_change = True db_change = True
# Google drive setup # Google drive setup
if not os.path.isfile(os.path.join(config.get_main_dir, 'settings.yaml')):
content.config_use_google_drive = False
if "config_use_google_drive" in to_save and not content.config_use_google_drive and not gdriveError: if "config_use_google_drive" in to_save and not content.config_use_google_drive and not gdriveError:
if filedata: if filedata:
if filedata['web']['redirect_uris'][0].endswith('/'): if filedata['web']['redirect_uris'][0].endswith('/'):
@ -3030,7 +3036,8 @@ def configuration_helper(origin):
gdrive=gdriveutils.gdrive_support, goodreads=goodreads_support, gdrive=gdriveutils.gdrive_support, goodreads=goodreads_support,
rarfile_support=rar_support, title=_(u"Basic Configuration")) rarfile_support=rar_support, title=_(u"Basic Configuration"))
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(os.path.join(content.config_calibre_dir, "metadata.db")):
gdriveutils.downloadFile(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:
@ -3062,7 +3069,7 @@ def configuration_helper(origin):
app.logger.info('Reboot required, restarting') app.logger.info('Reboot required, restarting')
if origin: if origin:
success = True success = True
if is_gdrive_ready() and gdriveutils.gdrive_support == True and config.config_use_google_drive == True: if is_gdrive_ready() and gdriveutils.gdrive_support == True: # and config.config_use_google_drive == True:
gdrivefolders=gdriveutils.listRootFolders() gdrivefolders=gdriveutils.listRootFolders()
else: else:
gdrivefolders=list() gdrivefolders=list()

File diff suppressed because it is too large Load Diff

@ -4,7 +4,7 @@ Calibre-Web is a web app providing a clean interface for browsing, reading and d
*This software is a fork of [library](https://github.com/mutschler/calibreserver) and licensed under the GPL v3 License.* *This software is a fork of [library](https://github.com/mutschler/calibreserver) and licensed under the GPL v3 License.*
![screenshot](https://raw.githubusercontent.com/janeczku/docker-calibre-web/master/screenshot.png) ![Main screen](../../wiki/images/main_screen.png)
## Features ## Features
@ -12,17 +12,17 @@ Calibre-Web is a web app providing a clean interface for browsing, reading and d
- full graphical setup - full graphical setup
- User management with fine grained per-user permissions - User management with fine grained per-user permissions
- Admin interface - Admin interface
- User Interface in dutch, english, french, german, hungarian, italian, japanese, khmer, polish, russian, simplified chinese, spanish, swedish - User Interface in dutch, english, french, german, hungarian, italian, japanese, khmer, polish, russian, simplified chinese, spanish, swedish, ukrainian
- OPDS feed for eBook reader apps - OPDS feed for eBook reader apps
- Filter and search by titles, authors, tags, series and language - Filter and search by titles, authors, tags, series and language
- Create custom book collection (shelves) - Create custom book collection (shelves)
- Support for editing eBook metadata and deleting eBooks from Calibre library - Support for editing eBook metadata and deleting eBooks from Calibre library
- Support for converting eBooks from EPUB to Kindle format (mobi/azw) - Support for converting eBooks through Calibre binaries
- Restrict eBook download to logged-in users - Restrict eBook download to logged-in users
- Support for public user registration - Support for public user registration
- Send eBooks to Kindle devices with the click of a button - Send eBooks to Kindle devices with the click of a button
- Support for reading eBooks directly in the browser (.txt, .epub, .pdf, .cbr, .cbt, .cbz) - Support for reading eBooks directly in the browser (.txt, .epub, .pdf, .cbr, .cbt, .cbz)
- Upload new books in PDF, epub, fb2 format - Upload new books in many formats
- Support for Calibre custom columns - Support for Calibre custom columns
- Ability to hide content based on categories for certain users - Ability to hide content based on categories for certain users
- Self update capability - Self update capability
@ -44,70 +44,14 @@ Calibre-Web is a web app providing a clean interface for browsing, reading and d
**Issues with Ubuntu:** **Issues with Ubuntu:**
Please note that running the above install command can fail on some versions of Ubuntu, saying `"can't combine user with prefix"`. This is a [known bug](https://github.com/pypa/pip/issues/3826) and can be remedied by using the command `pip install --system --target vendor -r requirements.txt` instead. Please note that running the above install command can fail on some versions of Ubuntu, saying `"can't combine user with prefix"`. This is a [known bug](https://github.com/pypa/pip/issues/3826) and can be remedied by using the command `pip install --system --target vendor -r requirements.txt` instead.
## Runtime Configuration Options
The configuration can be changed as admin in the admin panel under "Configuration"
Server Port:
Changes the port Calibre-Web is listening, changes take effect after pressing submit button
Enable public registration:
Tick to enable public user registration.
Enable anonymous browsing:
Tick to allow not logged in users to browse the catalog, anonymous user permissions can be set as admin ("Guest" user)
Enable uploading:
Tick to enable uploading of PDF, epub, FB2. This requires the imagemagick library to be installed.
Enable remote login ("magic link"):
Tick to enable remote login, i.e. a link that allows user to log in via a different device.
## Requirements ## Requirements
Python 2.7+ Python 2.7+, python 3.x+
Optionally, to enable on-the-fly conversion from EPUB to MOBI when using the send-to-kindle feature: Optionally, to enable on-the-fly conversion from EPUB to MOBI when using the send-to-kindle feature:
[Download](http://www.amazon.com/gp/feature.html?docId=1000765211) Amazon's KindleGen tool for your platform and place the binary named as `kindlegen` in the `vendor` folder. [Download](http://www.amazon.com/gp/feature.html?docId=1000765211) Amazon's KindleGen tool for your platform and place the binary named as `kindlegen` in the `vendor` folder.
## Using Google Drive integration
Calibre Calibre library (metadata.db) can be located on a Google Drive. Additional optional dependencys are necessary to get this work. Please install all optional requirements by executing `pip install --target vendor -r optional-requirements.txt`
To use google drive integration, you have to use the google developer console to create a new app. https://console.developers.google.com
Once a project has been created, we need to create a client ID and a client secret that will be used to enable the OAuth request with google, and enable the Drive API. To do this, follow the steps below: -
1. Open project in developer console
2. Click Enable API, and enable google drive
3. Now on the sidebar, click Credentials
4. Click Create Credentials and OAuth Client ID
5. Select Web Application and then next
6. Give the Credentials a name and enter your callback, which will be CALIBRE_WEB_URL/gdrive/callback
7. Click save
8. Download json file and place it in `calibre-web` directory, with the name `client_secrets.json`
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
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
3. Click the "Submit" button
4. Now select Authenticate Google Drive
5. This should redirect you to Google. After allowing it to use your Drive, it redirects you back to the config page
6. Select the folder that is the root of your calibre library on Gdrive ("Google drive Calibre folder")
7. Click the "Submit" button
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
### 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.
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.
9. Open config page
10. Click enable watch of metadata.db
11. Note that this expires after a week, so will need to be manually refresh
## Docker images ## Docker images
Pre-built Docker images based on Alpine Linux are available in these Docker Hub repositories: Pre-built Docker images based on Alpine Linux are available in these Docker Hub repositories:
@ -122,94 +66,6 @@ Pre-built Docker images based on Alpine Linux are available in these Docker Hub
**aarch64** **aarch64**
+ **linuxserver.io** at [lsioarmhf/calibre-web-aarch64](https://hub.docker.com/r/lsioarmhf/calibre-web-aarch64) + **linuxserver.io** at [lsioarmhf/calibre-web-aarch64](https://hub.docker.com/r/lsioarmhf/calibre-web-aarch64)
## Reverse Proxy # Wiki
Reverse proxy configuration examples for apache and nginx to use Calibre-Web: For further informations, How To's and FAQ please check the ![Wiki](../../wiki)
nginx configuration for a local server listening on port 8080, mapping Calibre-Web to /calibre:
```
http {
upstream calibre {
server 127.0.0.1:8083;
}
server {
client_max_body_size 20M;
location /calibre {
proxy_bind $server_adress;
proxy_pass http://127.0.0.1:8083;
proxy_set_header Host $http_host;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Scheme $scheme;
proxy_set_header X-Script-Name /calibre;
}
}
}
```
*Note: If using SSL in your reverse proxy on a non-standard port (e.g.12345), the following proxy_redirect line may be required:*
```
proxy_redirect http://$host/ https://$host:12345/;
```
Apache 2.4 configuration for a local server listening on port 443, mapping Calibre-Web to /calibre-web:
The following modules have to be activated: headers, proxy, rewrite.
```
Listen 443
<VirtualHost *:443>
SSLEngine on
SSLProxyEngine on
SSLCipherSuite ALL:!ADH:!EXPORT56:RC4+RSA:+HIGH:+MEDIUM:+LOW:+SSLv2:+EXP:+eNULL
SSLCertificateFile "C:\Apache24\conf\ssl\test.crt"
SSLCertificateKeyFile "C:\Apache24\conf\ssl\test.key"
<Location "/calibre-web" >
RequestHeader set X-SCRIPT-NAME /calibre-web
RequestHeader set X-SCHEME https
ProxyPass http://localhost:8083/
ProxyPassReverse http://localhost:8083/
</Location>
</VirtualHost>
```
## (Optional) SSL Configuration
For configuration of calibre-web as SSL Server go to the Config page in the Admin section. Enter the certfile- and keyfile-location, optionally change port to 443 and press submit.
Afterwards the server can only be accessed via SSL. In case of a misconfiguration (wrong/invalid files) both files can be overridden via command line options
-c [certfile location] -k [keyfile location]
By using "" as file locations the server runs as non SSL server again. The correct file path can than be entered on the Config page. After the next restart without command line options the changed file paths are applied.
## Start Calibre-Web as service under Linux
Create a file "cps.service" as root in the folder /etc/systemd/system with the following content:
```[Unit]
Description=Calibre-Web
[Service]
Type=simple
User=[Username]
ExecStart=[path to python] [/PATH/TO/cps.py]
WorkingDirectory=[/PATH/TO/cps.py]
[Install]
WantedBy=multi-user.target
```
Replace the user and ExecStart with your user and foldernames.
`sudo systemctl enable cps.service`
enables the service.
## Command line options
Starting the script with `-h` lists all supported command line options
Currently supported are 2 options, which are both useful for running multiple instances of Calibre-Web
`"-p path"` allows to specify the location of the settings database
`"-g path"` allows to specify the location of the google-drive database
`"-c path"` allows to specify the location of SSL certfile, works only in combination with keyfile
`"-k path"` allows to specify the location of SSL keyfile, works only in combination with certfile

Loading…
Cancel
Save