Commit 53578671 authored by Michael Shavit's avatar Michael Shavit

Add initial support for Kobo device Sync endpoint.

- Supports /v1/library/sync call to get list of books
- Supports /v1/library/metadata call to get metadata for a given book
  + Assumes books are stored on Backblaze for metadata call
- Changes to helper.py so that we can return no cover instead of a blank
image.
parent 0c40e40d
......@@ -32,3 +32,4 @@ gdrive_credentials
vendor
client_secrets.json
b2_secrets.json
......@@ -41,6 +41,8 @@ from cps.shelf import shelf
from cps.admin import admi
from cps.gdrive import gdrive
from cps.editbooks import editbook
from cps.kobo import kobo
try:
from cps.oauth_bb import oauth
oauth_available = True
......@@ -58,6 +60,7 @@ def main():
app.register_blueprint(admi)
app.register_blueprint(gdrive)
app.register_blueprint(editbook)
app.register_blueprint(kobo)
if oauth_available:
app.register_blueprint(oauth)
success = web_server.start()
......
......@@ -294,6 +294,8 @@ def _configuration_update_helper():
reboot_required |= _config_string("config_certfile")
if config.config_certfile and not os.path.isfile(config.config_certfile):
return _configuration_result('Certfile location is not valid, please enter correct path', gdriveError)
_config_string("config_server_url")
_config_checkbox_int("config_uploading")
_config_checkbox_int("config_anonbrowse")
......
......@@ -49,6 +49,7 @@ class _Settings(_Base):
config_port = Column(Integer, default=constants.DEFAULT_PORT)
config_certfile = Column(String)
config_keyfile = Column(String)
config_server_url = Column(String, default='')
config_calibre_web_title = Column(String, default=u'Calibre-Web')
config_books_per_page = Column(Integer, default=60)
......
......@@ -25,13 +25,13 @@ import ast
from sqlalchemy import create_engine
from sqlalchemy import Table, Column, ForeignKey
from sqlalchemy import String, Integer, Boolean
from sqlalchemy import String, Integer, Boolean, TIMESTAMP
from sqlalchemy.orm import relationship, sessionmaker, scoped_session
from sqlalchemy.ext.declarative import declarative_base
session = None
cc_exceptions = ['datetime', 'comments', 'float', 'composite', 'series']
cc_exceptions = ['comments', 'float', 'composite', 'series']
cc_classes = {}
......@@ -251,10 +251,10 @@ class Books(Base):
title = Column(String)
sort = Column(String)
author_sort = Column(String)
timestamp = Column(String)
timestamp = Column(TIMESTAMP)
pubdate = Column(String)
series_index = Column(String)
last_modified = Column(String)
last_modified = Column(TIMESTAMP)
path = Column(String)
has_cover = Column(Integer)
uuid = Column(String)
......@@ -353,7 +353,7 @@ def setup_db(config):
# conn.connection.create_function('upper', 1, ucase)
if not cc_classes:
cc = conn.execute("SELECT id, datatype FROM custom_columns")
cc = conn.execute("SELECT id, datatype, normalized FROM custom_columns")
cc_ids = []
books_custom_column_links = {}
......@@ -366,7 +366,7 @@ def setup_db(config):
ForeignKey('custom_column_' + str(row.id) + '.id'),
primary_key=True)
)
cc_ids.append([row.id, row.datatype])
cc_ids.append([row.id, row.datatype, row.normalized])
if row.datatype == 'bool':
ccdict = {'__tablename__': 'custom_column_' + str(row.id),
'id': Column(Integer, primary_key=True),
......@@ -377,6 +377,11 @@ def setup_db(config):
'id': Column(Integer, primary_key=True),
'book': Column(Integer, ForeignKey('books.id')),
'value': Column(Integer)}
elif not row.normalized:
ccdict = {'__tablename__': 'custom_column_' + str(row.id),
'id': Column(Integer, primary_key=True),
'book': Column(Integer, ForeignKey('books.id')),
'value': Column(String)}
else:
ccdict = {'__tablename__': 'custom_column_' + str(row.id),
'id': Column(Integer, primary_key=True),
......@@ -384,7 +389,8 @@ def setup_db(config):
cc_classes[row.id] = type(str('Custom_Column_' + str(row.id)), (Base,), ccdict)
for cc_id in cc_ids:
if (cc_id[1] == 'bool') or (cc_id[1] == 'int'):
normalized = cc_id[2]
if (not normalized):
setattr(Books, 'custom_column_' + str(cc_id[0]), relationship(cc_classes[cc_id[0]],
primaryjoin=(
Books.id == cc_classes[cc_id[0]].book),
......@@ -393,6 +399,16 @@ def setup_db(config):
setattr(Books, 'custom_column_' + str(cc_id[0]), relationship(cc_classes[cc_id[0]],
secondary=books_custom_column_links[cc_id[0]],
backref='books'))
#for cc_id in cc_ids:
# if (cc_id[1] == 'bool') or (cc_id[1] == 'int'):
# setattr(Books, 'custom_column_' + str(cc_id[2]), relationship(cc_classes[cc_id[0]],
# primaryjoin=(
# Books.id == cc_classes[cc_id[0]].book),
# backref='books'))
# else:
# setattr(Books, 'custom_column_' + str(cc_id[2]), relationship(cc_classes[cc_id[0]],
# secondary=books_custom_column_links[cc_id[0]],
# backref='books'))
global session
......
......@@ -428,32 +428,46 @@ def delete_book(book, calibrepath, book_format):
return delete_book_file(book, calibrepath, book_format)
def get_cover_on_failure(use_generic_cover):
if use_generic_cover:
return send_from_directory(_STATIC_DIR, "generic_cover.jpg")
else:
return None
def get_book_cover(book_id):
book = db.session.query(db.Books).filter(db.Books.id == book_id).first()
if book.has_cover:
return get_book_cover_internal(book, False)
def get_book_cover_with_uuid(book_uuid,
use_generic_cover_on_failure=True):
book = db.session.query(db.Books).filter(db.Books.uuid == book_uuid).first()
return get_book_cover_internal(book, use_generic_cover_on_failure)
def get_book_cover_internal(book,
use_generic_cover_on_failure):
if book.has_cover:
if config.config_use_google_drive:
try:
if not gd.is_gdrive_ready():
return send_from_directory(_STATIC_DIR, "generic_cover.jpg")
return get_cover_on_failure(use_generic_cover_on_failure)
path=gd.get_cover_via_gdrive(book.path)
if path:
return redirect(path)
else:
log.error('%s/cover.jpg not found on Google Drive', book.path)
return send_from_directory(_STATIC_DIR, "generic_cover.jpg")
return get_cover_on_failure(use_generic_cover_on_failure)
except Exception as e:
log.exception(e)
# traceback.print_exc()
return send_from_directory(_STATIC_DIR,"generic_cover.jpg")
return get_cover_on_failure(use_generic_cover_on_failure)
else:
cover_file_path = os.path.join(config.config_calibre_dir, book.path)
if os.path.isfile(os.path.join(cover_file_path, "cover.jpg")):
return send_from_directory(cover_file_path, "cover.jpg")
else:
return send_from_directory(_STATIC_DIR,"generic_cover.jpg")
return get_cover_on_failure(use_generic_cover_on_failure)
else:
return send_from_directory(_STATIC_DIR,"generic_cover.jpg")
return get_cover_on_failure(use_generic_cover_on_failure)
# saves book cover from url
......
This diff is collapsed.
......@@ -104,6 +104,10 @@
<!--option-- value="3" {% if config.config_updatechannel == 3 %}selected{% endif %}>{{_('Nightly (Automatic)')}}</option-->
</select>
</div>
<div class="form-group">
<label for="config_server_url">{{_('Server Url. This is only used for the (experimental) Kobo device library sync')}}</label>
<input type="text" class="form-control" name="config_server_url" id="config_server_url" value="{% if config.config_server_url != None %}{{ config.config_server_url }}{% endif %}" autocomplete="off">
</div>
</div>
</div>
</div>
......
......@@ -13,3 +13,5 @@ SQLAlchemy>=1.1.0
tornado>=4.1
Wand>=0.4.4
unidecode>=0.04.19
b2sdk>=1.0.2,<2.0.0
jsonschema>=3.2.0
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