Skip to content
Projects
Groups
Snippets
Help
Loading...
Help
Contribute to GitLab
Sign in / Register
Toggle navigation
D
douban-api-proxy
Project
Project
Details
Activity
Cycle Analytics
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Charts
Issues
0
Issues
0
List
Board
Labels
Milestones
Merge Requests
0
Merge Requests
0
CI / CD
CI / CD
Pipelines
Jobs
Schedules
Charts
Wiki
Wiki
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Charts
Create a new issue
Jobs
Commits
Issue Boards
Open sidebar
captainwong
douban-api-proxy
Commits
d597e05f
Commit
d597e05f
authored
Mar 12, 2020
by
Ozzieisaacs
Browse files
Options
Browse Files
Download
Plain Diff
Merge remote-tracking branch 'reading_state' into Develop
# Conflicts: # cps/kobo.py
parents
98dc9913
8e1641da
Expand all
Show whitespace changes
Inline
Side-by-side
Showing
4 changed files
with
300 additions
and
47 deletions
+300
-47
kobo.py
cps/kobo.py
+189
-29
SyncToken.py
cps/services/SyncToken.py
+18
-7
ub.py
cps/ub.py
+79
-4
web.py
cps/web.py
+14
-7
No files found.
cps/kobo.py
View file @
d597e05f
This diff is collapsed.
Click to expand it.
cps/services/SyncToken.py
View file @
d597e05f
...
...
@@ -42,6 +42,13 @@ def to_epoch_timestamp(datetime_object):
return
(
datetime_object
-
datetime
(
1970
,
1
,
1
))
.
total_seconds
()
def
get_datetime_from_json
(
json_object
,
field_name
):
try
:
return
datetime
.
utcfromtimestamp
(
json_object
[
field_name
])
except
KeyError
:
return
datetime
.
min
class
SyncToken
():
""" The SyncToken is used to persist state accross requests.
When serialized over the response headers, the Kobo device will propagate the token onto following requests to the service.
...
...
@@ -53,7 +60,8 @@ class SyncToken():
"""
SYNC_TOKEN_HEADER
=
"x-kobo-synctoken"
VERSION
=
"1-0-0"
VERSION
=
"1-1-0"
LAST_MODIFIED_ADDED_VERSION
=
"1-1-0"
MIN_VERSION
=
"1-0-0"
token_schema
=
{
...
...
@@ -68,6 +76,7 @@ class SyncToken():
"raw_kobo_store_token"
:
{
"type"
:
"string"
},
"books_last_modified"
:
{
"type"
:
"string"
},
"books_last_created"
:
{
"type"
:
"string"
},
"reading_state_last_modified"
:
{
"type"
:
"string"
},
},
}
...
...
@@ -76,10 +85,13 @@ class SyncToken():
raw_kobo_store_token
=
""
,
books_last_created
=
datetime
.
min
,
books_last_modified
=
datetime
.
min
,
reading_state_last_modified
=
datetime
.
min
,
):
self
.
raw_kobo_store_token
=
raw_kobo_store_token
self
.
books_last_created
=
books_last_created
self
.
books_last_modified
=
books_last_modified
self
.
reading_state_last_modified
=
reading_state_last_modified
@
staticmethod
def
from_headers
(
headers
):
...
...
@@ -109,12 +121,9 @@ class SyncToken():
raw_kobo_store_token
=
data_json
[
"raw_kobo_store_token"
]
try
:
books_last_modified
=
datetime
.
utcfromtimestamp
(
data_json
[
"books_last_modified"
]
)
books_last_created
=
datetime
.
utcfromtimestamp
(
data_json
[
"books_last_created"
]
)
books_last_modified
=
get_datetime_from_json
(
data_json
,
"books_last_modified"
)
books_last_created
=
get_datetime_from_json
(
data_json
,
"books_last_created"
)
reading_state_last_modified
=
get_datetime_from_json
(
data_json
,
"reading_state_last_modified"
)
except
TypeError
:
log
.
error
(
"SyncToken timestamps don't parse to a datetime."
)
return
SyncToken
(
raw_kobo_store_token
=
raw_kobo_store_token
)
...
...
@@ -123,6 +132,7 @@ class SyncToken():
raw_kobo_store_token
=
raw_kobo_store_token
,
books_last_created
=
books_last_created
,
books_last_modified
=
books_last_modified
,
reading_state_last_modified
=
reading_state_last_modified
)
def
set_kobo_store_header
(
self
,
store_headers
):
...
...
@@ -143,6 +153,7 @@ class SyncToken():
"raw_kobo_store_token"
:
self
.
raw_kobo_store_token
,
"books_last_modified"
:
to_epoch_timestamp
(
self
.
books_last_modified
),
"books_last_created"
:
to_epoch_timestamp
(
self
.
books_last_created
),
"reading_state_last_modified"
:
to_epoch_timestamp
(
self
.
reading_state_last_modified
)
},
}
return
b64encode_json
(
token
)
cps/ub.py
View file @
d597e05f
...
...
@@ -20,6 +20,7 @@
from
__future__
import
division
,
print_function
,
unicode_literals
import
os
import
datetime
import
itertools
from
binascii
import
hexlify
from
flask
import
g
...
...
@@ -31,11 +32,12 @@ try:
oauth_support
=
True
except
ImportError
:
oauth_support
=
False
from
sqlalchemy
import
create_engine
,
exc
,
exists
from
sqlalchemy
import
create_engine
,
exc
,
exists
,
event
from
sqlalchemy
import
Column
,
ForeignKey
from
sqlalchemy
import
String
,
Integer
,
SmallInteger
,
Boolean
,
DateTime
from
sqlalchemy.orm
import
relationship
,
sessionmaker
from
sqlalchemy
import
String
,
Integer
,
SmallInteger
,
Boolean
,
DateTime
,
Float
from
sqlalchemy.orm
import
backref
,
foreign
,
relationship
,
remote
,
sessionmaker
,
Session
from
sqlalchemy.ext.declarative
import
declarative_base
from
sqlalchemy.sql.expression
import
and_
from
werkzeug.security
import
generate_password_hash
from
.
import
constants
# , config
...
...
@@ -284,10 +286,19 @@ class BookShelf(Base):
class
ReadBook
(
Base
):
__tablename__
=
'book_read_link'
STATUS_UNREAD
=
0
STATUS_FINISHED
=
1
STATUS_IN_PROGRESS
=
2
id
=
Column
(
Integer
,
primary_key
=
True
)
book_id
=
Column
(
Integer
,
unique
=
False
)
user_id
=
Column
(
Integer
,
ForeignKey
(
'user.id'
),
unique
=
False
)
is_read
=
Column
(
Boolean
,
unique
=
False
)
read_status
=
Column
(
Integer
,
unique
=
False
,
default
=
STATUS_UNREAD
,
nullable
=
False
)
kobo_reading_state
=
relationship
(
"KoboReadingState"
,
uselist
=
False
,
primaryjoin
=
"and_(ReadBook.user_id == foreign(KoboReadingState.user_id), "
"ReadBook.book_id == foreign(KoboReadingState.book_id))"
,
cascade
=
"all"
,
backref
=
backref
(
"book_read_link"
,
uselist
=
False
))
last_modified
=
Column
(
DateTime
,
default
=
datetime
.
datetime
.
utcnow
,
onupdate
=
datetime
.
datetime
.
utcnow
)
last_time_started_reading
=
Column
(
DateTime
,
nullable
=
True
)
times_started_reading
=
Column
(
Integer
,
default
=
0
,
nullable
=
False
)
class
Bookmark
(
Base
):
...
...
@@ -300,6 +311,54 @@ class Bookmark(Base):
bookmark_key
=
Column
(
String
)
# The Kobo ReadingState API keeps track of 4 timestamped entities:
# ReadingState, StatusInfo, Statistics, CurrentBookmark
# Which we map to the following 4 tables:
# KoboReadingState, ReadBook, KoboStatistics and KoboBookmark
class
KoboReadingState
(
Base
):
__tablename__
=
'kobo_reading_state'
id
=
Column
(
Integer
,
primary_key
=
True
,
autoincrement
=
True
)
user_id
=
Column
(
Integer
,
ForeignKey
(
'user.id'
))
book_id
=
Column
(
Integer
)
last_modified
=
Column
(
DateTime
,
default
=
datetime
.
datetime
.
utcnow
,
onupdate
=
datetime
.
datetime
.
utcnow
)
priority_timestamp
=
Column
(
DateTime
,
default
=
datetime
.
datetime
.
utcnow
,
onupdate
=
datetime
.
datetime
.
utcnow
)
current_bookmark
=
relationship
(
"KoboBookmark"
,
uselist
=
False
,
backref
=
"kobo_reading_state"
,
cascade
=
"all"
)
statistics
=
relationship
(
"KoboStatistics"
,
uselist
=
False
,
backref
=
"kobo_reading_state"
,
cascade
=
"all"
)
class
KoboBookmark
(
Base
):
__tablename__
=
'kobo_bookmark'
id
=
Column
(
Integer
,
primary_key
=
True
)
kobo_reading_state_id
=
Column
(
Integer
,
ForeignKey
(
'kobo_reading_state.id'
))
last_modified
=
Column
(
DateTime
,
default
=
datetime
.
datetime
.
utcnow
,
onupdate
=
datetime
.
datetime
.
utcnow
)
location_source
=
Column
(
String
)
location_type
=
Column
(
String
)
location_value
=
Column
(
String
)
progress_percent
=
Column
(
Float
)
content_source_progress_percent
=
Column
(
Float
)
class
KoboStatistics
(
Base
):
__tablename__
=
'kobo_statistics'
id
=
Column
(
Integer
,
primary_key
=
True
)
kobo_reading_state_id
=
Column
(
Integer
,
ForeignKey
(
'kobo_reading_state.id'
))
last_modified
=
Column
(
DateTime
,
default
=
datetime
.
datetime
.
utcnow
,
onupdate
=
datetime
.
datetime
.
utcnow
)
remaining_time_minutes
=
Column
(
Integer
)
spent_reading_minutes
=
Column
(
Integer
)
# Updates the last_modified timestamp in the KoboReadingState table if any of its children tables are modified.
@
event
.
listens_for
(
Session
,
'before_flush'
)
def
receive_before_flush
(
session
,
flush_context
,
instances
):
for
change
in
itertools
.
chain
(
session
.
new
,
session
.
dirty
):
if
isinstance
(
change
,
(
ReadBook
,
KoboStatistics
,
KoboBookmark
)):
if
change
.
kobo_reading_state
:
change
.
kobo_reading_state
.
last_modified
=
datetime
.
datetime
.
utcnow
()
# Baseclass representing Downloads from calibre-web in app.db
class
Downloads
(
Base
):
__tablename__
=
'downloads'
...
...
@@ -352,6 +411,12 @@ def migrate_Database(session):
ReadBook
.
__table__
.
create
(
bind
=
engine
)
if
not
engine
.
dialect
.
has_table
(
engine
.
connect
(),
"bookmark"
):
Bookmark
.
__table__
.
create
(
bind
=
engine
)
if
not
engine
.
dialect
.
has_table
(
engine
.
connect
(),
"kobo_reading_state"
):
KoboReadingState
.
__table__
.
create
(
bind
=
engine
)
if
not
engine
.
dialect
.
has_table
(
engine
.
connect
(),
"kobo_bookmark"
):
KoboBookmark
.
__table__
.
create
(
bind
=
engine
)
if
not
engine
.
dialect
.
has_table
(
engine
.
connect
(),
"kobo_statistics"
):
KoboStatistics
.
__table__
.
create
(
bind
=
engine
)
if
not
engine
.
dialect
.
has_table
(
engine
.
connect
(),
"registration"
):
ReadBook
.
__table__
.
create
(
bind
=
engine
)
conn
=
engine
.
connect
()
...
...
@@ -373,6 +438,16 @@ def migrate_Database(session):
conn
.
execute
(
"ALTER TABLE remote_auth_token ADD column 'token_type' INTEGER DEFAULT 0"
)
conn
.
execute
(
"update remote_auth_token set 'token_type' = 0"
)
session
.
commit
()
try
:
session
.
query
(
exists
()
.
where
(
ReadBook
.
read_status
))
.
scalar
()
except
exc
.
OperationalError
:
conn
=
engine
.
connect
()
conn
.
execute
(
"ALTER TABLE book_read_link ADD column 'read_status' INTEGER DEFAULT 0"
)
conn
.
execute
(
"UPDATE book_read_link SET 'read_status' = 1 WHERE is_read"
)
conn
.
execute
(
"ALTER TABLE book_read_link ADD column 'last_modified' DATETIME"
)
conn
.
execute
(
"ALTER TABLE book_read_link ADD column 'last_time_started_reading' DATETIME"
)
conn
.
execute
(
"ALTER TABLE book_read_link ADD column 'times_started_reading' INTEGER DEFAULT 0"
)
session
.
commit
()
# Handle table exists, but no content
cnt
=
session
.
query
(
Registration
)
.
count
()
...
...
cps/web.py
View file @
d597e05f
...
...
@@ -314,13 +314,19 @@ def toggle_read(book_id):
book
=
ub
.
session
.
query
(
ub
.
ReadBook
)
.
filter
(
and_
(
ub
.
ReadBook
.
user_id
==
int
(
current_user
.
id
),
ub
.
ReadBook
.
book_id
==
book_id
))
.
first
()
if
book
:
book
.
is_read
=
not
book
.
is_read
if
book
.
read_status
==
ub
.
ReadBook
.
STATUS_FINISHED
:
book
.
read_status
=
ub
.
ReadBook
.
STATUS_UNREAD
else
:
readBook
=
ub
.
ReadBook
()
readBook
.
user_id
=
int
(
current_user
.
id
)
readBook
.
book_id
=
book_id
readBook
.
is_read
=
True
book
.
read_status
=
ub
.
ReadBook
.
STATUS_FINISHED
else
:
readBook
=
ub
.
ReadBook
(
user_id
=
current_user
.
id
,
book_id
=
book_id
)
readBook
.
read_status
=
ub
.
ReadBook
.
STATUS_FINISHED
book
=
readBook
if
not
book
.
kobo_reading_state
:
kobo_reading_state
=
ub
.
KoboReadingState
(
user_id
=
current_user
.
id
,
book_id
=
book_id
)
kobo_reading_state
.
current_bookmark
=
ub
.
KoboBookmark
()
kobo_reading_state
.
statistics
=
ub
.
KoboStatistics
()
book
.
kobo_reading_state
=
kobo_reading_state
ub
.
session
.
merge
(
book
)
ub
.
session
.
commit
()
else
:
...
...
@@ -980,7 +986,7 @@ def render_read_books(page, are_read, as_xml=False, order=None, *args, **kwargs)
order
=
order
or
[]
if
not
config
.
config_read_column
:
readBooks
=
ub
.
session
.
query
(
ub
.
ReadBook
)
.
filter
(
ub
.
ReadBook
.
user_id
==
int
(
current_user
.
id
))
\
.
filter
(
ub
.
ReadBook
.
is_read
==
True
)
.
all
()
.
filter
(
ub
.
ReadBook
.
read_status
==
ub
.
ReadBook
.
STATUS_FINISHED
)
.
all
()
readBookIds
=
[
x
.
book_id
for
x
in
readBooks
]
else
:
try
:
...
...
@@ -1448,7 +1454,8 @@ def show_book(book_id):
if
not
config
.
config_read_column
:
matching_have_read_book
=
ub
.
session
.
query
(
ub
.
ReadBook
)
.
\
filter
(
and_
(
ub
.
ReadBook
.
user_id
==
int
(
current_user
.
id
),
ub
.
ReadBook
.
book_id
==
book_id
))
.
all
()
have_read
=
len
(
matching_have_read_book
)
>
0
and
matching_have_read_book
[
0
]
.
is_read
have_read
=
len
(
matching_have_read_book
)
>
0
and
matching_have_read_book
[
0
]
.
read_status
==
ub
.
ReadBook
.
STATUS_FINISHED
else
:
try
:
matching_have_read_book
=
getattr
(
entries
,
'custom_column_'
+
str
(
config
.
config_read_column
))
...
...
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment