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
Hide 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):
...
@@ -42,6 +42,13 @@ def to_epoch_timestamp(datetime_object):
return
(
datetime_object
-
datetime
(
1970
,
1
,
1
))
.
total_seconds
()
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
():
class
SyncToken
():
""" The SyncToken is used to persist state accross requests.
""" 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.
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():
...
@@ -53,7 +60,8 @@ class SyncToken():
"""
"""
SYNC_TOKEN_HEADER
=
"x-kobo-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"
MIN_VERSION
=
"1-0-0"
token_schema
=
{
token_schema
=
{
...
@@ -68,6 +76,7 @@ class SyncToken():
...
@@ -68,6 +76,7 @@ class SyncToken():
"raw_kobo_store_token"
:
{
"type"
:
"string"
},
"raw_kobo_store_token"
:
{
"type"
:
"string"
},
"books_last_modified"
:
{
"type"
:
"string"
},
"books_last_modified"
:
{
"type"
:
"string"
},
"books_last_created"
:
{
"type"
:
"string"
},
"books_last_created"
:
{
"type"
:
"string"
},
"reading_state_last_modified"
:
{
"type"
:
"string"
},
},
},
}
}
...
@@ -76,10 +85,13 @@ class SyncToken():
...
@@ -76,10 +85,13 @@ class SyncToken():
raw_kobo_store_token
=
""
,
raw_kobo_store_token
=
""
,
books_last_created
=
datetime
.
min
,
books_last_created
=
datetime
.
min
,
books_last_modified
=
datetime
.
min
,
books_last_modified
=
datetime
.
min
,
reading_state_last_modified
=
datetime
.
min
,
):
):
self
.
raw_kobo_store_token
=
raw_kobo_store_token
self
.
raw_kobo_store_token
=
raw_kobo_store_token
self
.
books_last_created
=
books_last_created
self
.
books_last_created
=
books_last_created
self
.
books_last_modified
=
books_last_modified
self
.
books_last_modified
=
books_last_modified
self
.
reading_state_last_modified
=
reading_state_last_modified
@
staticmethod
@
staticmethod
def
from_headers
(
headers
):
def
from_headers
(
headers
):
...
@@ -109,12 +121,9 @@ class SyncToken():
...
@@ -109,12 +121,9 @@ class SyncToken():
raw_kobo_store_token
=
data_json
[
"raw_kobo_store_token"
]
raw_kobo_store_token
=
data_json
[
"raw_kobo_store_token"
]
try
:
try
:
books_last_modified
=
datetime
.
utcfromtimestamp
(
books_last_modified
=
get_datetime_from_json
(
data_json
,
"books_last_modified"
)
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"
)
books_last_created
=
datetime
.
utcfromtimestamp
(
data_json
[
"books_last_created"
]
)
except
TypeError
:
except
TypeError
:
log
.
error
(
"SyncToken timestamps don't parse to a datetime."
)
log
.
error
(
"SyncToken timestamps don't parse to a datetime."
)
return
SyncToken
(
raw_kobo_store_token
=
raw_kobo_store_token
)
return
SyncToken
(
raw_kobo_store_token
=
raw_kobo_store_token
)
...
@@ -123,6 +132,7 @@ class SyncToken():
...
@@ -123,6 +132,7 @@ class SyncToken():
raw_kobo_store_token
=
raw_kobo_store_token
,
raw_kobo_store_token
=
raw_kobo_store_token
,
books_last_created
=
books_last_created
,
books_last_created
=
books_last_created
,
books_last_modified
=
books_last_modified
,
books_last_modified
=
books_last_modified
,
reading_state_last_modified
=
reading_state_last_modified
)
)
def
set_kobo_store_header
(
self
,
store_headers
):
def
set_kobo_store_header
(
self
,
store_headers
):
...
@@ -143,6 +153,7 @@ class SyncToken():
...
@@ -143,6 +153,7 @@ class SyncToken():
"raw_kobo_store_token"
:
self
.
raw_kobo_store_token
,
"raw_kobo_store_token"
:
self
.
raw_kobo_store_token
,
"books_last_modified"
:
to_epoch_timestamp
(
self
.
books_last_modified
),
"books_last_modified"
:
to_epoch_timestamp
(
self
.
books_last_modified
),
"books_last_created"
:
to_epoch_timestamp
(
self
.
books_last_created
),
"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
)
return
b64encode_json
(
token
)
cps/ub.py
View file @
d597e05f
...
@@ -20,6 +20,7 @@
...
@@ -20,6 +20,7 @@
from
__future__
import
division
,
print_function
,
unicode_literals
from
__future__
import
division
,
print_function
,
unicode_literals
import
os
import
os
import
datetime
import
datetime
import
itertools
from
binascii
import
hexlify
from
binascii
import
hexlify
from
flask
import
g
from
flask
import
g
...
@@ -31,11 +32,12 @@ try:
...
@@ -31,11 +32,12 @@ try:
oauth_support
=
True
oauth_support
=
True
except
ImportError
:
except
ImportError
:
oauth_support
=
False
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
Column
,
ForeignKey
from
sqlalchemy
import
String
,
Integer
,
SmallInteger
,
Boolean
,
DateTime
from
sqlalchemy
import
String
,
Integer
,
SmallInteger
,
Boolean
,
DateTime
,
Float
from
sqlalchemy.orm
import
relationship
,
sessionmaker
from
sqlalchemy.orm
import
backref
,
foreign
,
relationship
,
remote
,
sessionmaker
,
Session
from
sqlalchemy.ext.declarative
import
declarative_base
from
sqlalchemy.ext.declarative
import
declarative_base
from
sqlalchemy.sql.expression
import
and_
from
werkzeug.security
import
generate_password_hash
from
werkzeug.security
import
generate_password_hash
from
.
import
constants
# , config
from
.
import
constants
# , config
...
@@ -284,10 +286,19 @@ class BookShelf(Base):
...
@@ -284,10 +286,19 @@ class BookShelf(Base):
class
ReadBook
(
Base
):
class
ReadBook
(
Base
):
__tablename__
=
'book_read_link'
__tablename__
=
'book_read_link'
STATUS_UNREAD
=
0
STATUS_FINISHED
=
1
STATUS_IN_PROGRESS
=
2
id
=
Column
(
Integer
,
primary_key
=
True
)
id
=
Column
(
Integer
,
primary_key
=
True
)
book_id
=
Column
(
Integer
,
unique
=
False
)
book_id
=
Column
(
Integer
,
unique
=
False
)
user_id
=
Column
(
Integer
,
ForeignKey
(
'user.id'
),
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
):
class
Bookmark
(
Base
):
...
@@ -300,6 +311,54 @@ class Bookmark(Base):
...
@@ -300,6 +311,54 @@ class Bookmark(Base):
bookmark_key
=
Column
(
String
)
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
# Baseclass representing Downloads from calibre-web in app.db
class
Downloads
(
Base
):
class
Downloads
(
Base
):
__tablename__
=
'downloads'
__tablename__
=
'downloads'
...
@@ -352,6 +411,12 @@ def migrate_Database(session):
...
@@ -352,6 +411,12 @@ 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
(),
"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"
):
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
()
...
@@ -373,6 +438,16 @@ def migrate_Database(session):
...
@@ -373,6 +438,16 @@ def migrate_Database(session):
conn
.
execute
(
"ALTER TABLE remote_auth_token ADD column 'token_type' INTEGER DEFAULT 0"
)
conn
.
execute
(
"ALTER TABLE remote_auth_token ADD column 'token_type' INTEGER DEFAULT 0"
)
conn
.
execute
(
"update remote_auth_token set 'token_type' = 0"
)
conn
.
execute
(
"update remote_auth_token set 'token_type' = 0"
)
session
.
commit
()
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
# Handle table exists, but no content
cnt
=
session
.
query
(
Registration
)
.
count
()
cnt
=
session
.
query
(
Registration
)
.
count
()
...
...
cps/web.py
View file @
d597e05f
...
@@ -314,13 +314,19 @@ def toggle_read(book_id):
...
@@ -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
),
book
=
ub
.
session
.
query
(
ub
.
ReadBook
)
.
filter
(
and_
(
ub
.
ReadBook
.
user_id
==
int
(
current_user
.
id
),
ub
.
ReadBook
.
book_id
==
book_id
))
.
first
()
ub
.
ReadBook
.
book_id
==
book_id
))
.
first
()
if
book
:
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
:
book
.
read_status
=
ub
.
ReadBook
.
STATUS_FINISHED
else
:
else
:
readBook
=
ub
.
ReadBook
()
readBook
=
ub
.
ReadBook
(
user_id
=
current_user
.
id
,
book_id
=
book_id
)
readBook
.
user_id
=
int
(
current_user
.
id
)
readBook
.
read_status
=
ub
.
ReadBook
.
STATUS_FINISHED
readBook
.
book_id
=
book_id
readBook
.
is_read
=
True
book
=
readBook
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
.
merge
(
book
)
ub
.
session
.
commit
()
ub
.
session
.
commit
()
else
:
else
:
...
@@ -980,7 +986,7 @@ def render_read_books(page, are_read, as_xml=False, order=None, *args, **kwargs)
...
@@ -980,7 +986,7 @@ def render_read_books(page, are_read, as_xml=False, order=None, *args, **kwargs)
order
=
order
or
[]
order
=
order
or
[]
if
not
config
.
config_read_column
:
if
not
config
.
config_read_column
:
readBooks
=
ub
.
session
.
query
(
ub
.
ReadBook
)
.
filter
(
ub
.
ReadBook
.
user_id
==
int
(
current_user
.
id
))
\
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
]
readBookIds
=
[
x
.
book_id
for
x
in
readBooks
]
else
:
else
:
try
:
try
:
...
@@ -1448,7 +1454,8 @@ def show_book(book_id):
...
@@ -1448,7 +1454,8 @@ def show_book(book_id):
if
not
config
.
config_read_column
:
if
not
config
.
config_read_column
:
matching_have_read_book
=
ub
.
session
.
query
(
ub
.
ReadBook
)
.
\
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
()
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
:
else
:
try
:
try
:
matching_have_read_book
=
getattr
(
entries
,
'custom_column_'
+
str
(
config
.
config_read_column
))
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