Add support for book 'deletion' (i.e archiving) from a Kobo device.

This commit is contained in:
Michael Shavit 2020-01-24 00:04:16 -05:00
parent cd9bb56db5
commit e404da4192
2 changed files with 50 additions and 4 deletions

View File

@ -21,6 +21,7 @@ import sys
import uuid import uuid
from datetime import datetime from datetime import datetime
from time import gmtime, strftime from time import gmtime, strftime
try: try:
from urllib import unquote from urllib import unquote
except ImportError: except ImportError:
@ -35,12 +36,12 @@ from flask import (
url_for, url_for,
redirect, redirect,
) )
from flask_login import login_required from flask_login import login_required, current_user
from werkzeug.datastructures import Headers from werkzeug.datastructures import Headers
from sqlalchemy import func from sqlalchemy import func
import requests import requests
from . import config, logger, kobo_auth, db, helper from . import config, logger, kobo_auth, db, helper, ub
from .services import SyncToken as SyncToken from .services import SyncToken as SyncToken
from .web import download_required from .web import download_required
@ -53,6 +54,7 @@ kobo_auth.register_url_value_preprocessor(kobo)
log = logger.create() log = logger.create()
def get_store_url_for_current_request(): def get_store_url_for_current_request():
# Programmatically modify the current url to point to the official Kobo store # Programmatically modify the current url to point to the official Kobo store
base, sep, request_path_with_auth_token = request.full_path.rpartition("/kobo/") base, sep, request_path_with_auth_token = request.full_path.rpartition("/kobo/")
@ -114,6 +116,14 @@ def HandleSyncRequest():
# in case of external changes (e.g: adding a book through Calibre). # in case of external changes (e.g: adding a book through Calibre).
db.reconnect_db(config) db.reconnect_db(config)
archived_books = (
ub.session.query(ub.ArchivedBook)
.filter(ub.ArchivedBook.user_id == int(current_user.id))
.filter(ub.ArchivedBook.is_archived == True)
.all()
)
archived_book_ids = [archived_book.book_id for archived_book in archived_books]
# sqlite gives unexpected results when performing the last_modified comparison without the datetime cast. # sqlite gives unexpected results when performing the last_modified comparison without the datetime cast.
# It looks like it's treating the db.Books.last_modified field as a string and may fail # It looks like it's treating the db.Books.last_modified field as a string and may fail
# the comparison because of the +00:00 suffix. # the comparison because of the +00:00 suffix.
@ -122,6 +132,7 @@ def HandleSyncRequest():
.join(db.Data) .join(db.Data)
.filter(func.datetime(db.Books.last_modified) != sync_token.books_last_modified) .filter(func.datetime(db.Books.last_modified) != sync_token.books_last_modified)
.filter(db.Data.format.in_(KOBO_FORMATS)) .filter(db.Data.format.in_(KOBO_FORMATS))
.filter(db.Books.id.notin_(archived_book_ids))
.all() .all()
) )
for book in changed_entries: for book in changed_entries:
@ -342,13 +353,37 @@ def TopLevelEndpoint():
return make_response(jsonify({})) return make_response(jsonify({}))
@kobo.route("/v1/library/<book_uuid>", methods=["DELETE"])
@login_required
def HandleBookDeletionRequest(book_uuid):
log.info("Kobo book deletion request received for book %s" % book_uuid)
book = db.session.query(db.Books).filter(db.Books.uuid == book_uuid).first()
if not book:
log.info(u"Book %s not found in database", book_uuid)
return redirect_or_proxy_request()
book_id = book.id
archived_book = (
ub.session.query(ub.ArchivedBook)
.filter(ub.ArchivedBook.book_id == book_id)
.first()
)
if not archived_book:
archived_book = ub.ArchivedBook(user_id=current_user.id, book_id=book_id)
archived_book.book_id = book_id
archived_book.is_archived = True
ub.session.merge(archived_book)
ub.session.commit()
return ("", 204)
# TODO: Implement the following routes # TODO: Implement the following routes
@kobo.route("/v1/library/<dummy>", methods=["DELETE", "GET"])
@kobo.route("/v1/library/<book_uuid>/state", methods=["PUT"]) @kobo.route("/v1/library/<book_uuid>/state", methods=["PUT"])
@kobo.route("/v1/library/tags", methods=["POST"]) @kobo.route("/v1/library/tags", methods=["POST"])
@kobo.route("/v1/library/tags/<shelf_name>", methods=["POST"]) @kobo.route("/v1/library/tags/<shelf_name>", methods=["POST"])
@kobo.route("/v1/library/tags/<tag_id>", methods=["DELETE"]) @kobo.route("/v1/library/tags/<tag_id>", methods=["DELETE"])
def HandleUnimplementedRequest(dummy=None, book_uuid=None, shelf_name=None, tag_id=None): def HandleUnimplementedRequest(book_uuid=None, shelf_name=None, tag_id=None):
return redirect_or_proxy_request() return redirect_or_proxy_request()

View File

@ -300,6 +300,15 @@ class Bookmark(Base):
format = Column(String(collation='NOCASE')) format = Column(String(collation='NOCASE'))
bookmark_key = Column(String) bookmark_key = Column(String)
# Baseclass representing books that are archived on the user's Kobo device.
class ArchivedBook(Base):
__tablename__ = 'archived_book'
id = Column(Integer, primary_key=True)
user_id = Column(Integer, ForeignKey('user.id'))
book_id = Column(Integer)
is_archived = Column(Boolean, unique=False)
# Baseclass representing Downloads from calibre-web in app.db # Baseclass representing Downloads from calibre-web in app.db
class Downloads(Base): class Downloads(Base):
@ -353,6 +362,8 @@ def migrate_Database(session):
ReadBook.__table__.create(bind=engine) ReadBook.__table__.create(bind=engine)
if not engine.dialect.has_table(engine.connect(), "bookmark"): if not engine.dialect.has_table(engine.connect(), "bookmark"):
Bookmark.__table__.create(bind=engine) Bookmark.__table__.create(bind=engine)
if not engine.dialect.has_table(engine.connect(), "archived_book"):
ArchivedBook.__table__.create(bind=engine)
if not engine.dialect.has_table(engine.connect(), "registration"): if not engine.dialect.has_table(engine.connect(), "registration"):
ReadBook.__table__.create(bind=engine) ReadBook.__table__.create(bind=engine)
conn = engine.connect() conn = engine.connect()