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
725fc658
Commit
725fc658
authored
Mar 14, 2021
by
Ozzie Isaacs
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
Some code cosmetics
parent
e4e27662
Hide whitespace changes
Inline
Side-by-side
Showing
21 changed files
with
460 additions
and
410 deletions
+460
-410
comic.py
cps/comic.py
+38
-32
config_sql.py
cps/config_sql.py
+7
-4
constants.py
cps/constants.py
+1
-1
helper.py
cps/helper.py
+56
-48
isoLanguages.py
cps/isoLanguages.py
+1
-1
oauth.py
cps/oauth.py
+125
-129
oauth_bb.py
cps/oauth_bb.py
+4
-1
server.py
cps/server.py
+1
-1
SyncToken.py
cps/services/SyncToken.py
+3
-3
caliBlur_override.css
cps/static/css/caliBlur_override.css
+18
-16
kthoom.css
cps/static/css/kthoom.css
+2
-3
main.css
cps/static/css/main.css
+0
-2
style.css
cps/static/css/style.css
+57
-36
caliBlur.js
cps/static/js/caliBlur.js
+1
-1
filter_list.js
cps/static/js/filter_list.js
+2
-2
main.js
cps/static/js/main.js
+2
-2
table.js
cps/static/js/table.js
+1
-0
upload.py
cps/tasks/upload.py
+0
-1
ub.py
cps/ub.py
+71
-48
usermanagement.py
cps/usermanagement.py
+1
-1
web.py
cps/web.py
+69
-78
No files found.
cps/comic.py
View file @
725fc658
...
...
@@ -74,6 +74,41 @@ def _cover_processing(tmp_file_name, img, extension):
return
tmp_cover_name
def
_extract_Cover_from_archive
(
original_file_extension
,
tmp_file_name
,
rarExecutable
):
cover_data
=
None
if
original_file_extension
.
upper
()
==
'.CBZ'
:
cf
=
zipfile
.
ZipFile
(
tmp_file_name
)
for
name
in
cf
.
namelist
():
ext
=
os
.
path
.
splitext
(
name
)
if
len
(
ext
)
>
1
:
extension
=
ext
[
1
]
.
lower
()
if
extension
in
COVER_EXTENSIONS
:
cover_data
=
cf
.
read
(
name
)
break
elif
original_file_extension
.
upper
()
==
'.CBT'
:
cf
=
tarfile
.
TarFile
(
tmp_file_name
)
for
name
in
cf
.
getnames
():
ext
=
os
.
path
.
splitext
(
name
)
if
len
(
ext
)
>
1
:
extension
=
ext
[
1
]
.
lower
()
if
extension
in
COVER_EXTENSIONS
:
cover_data
=
cf
.
extractfile
(
name
)
.
read
()
break
elif
original_file_extension
.
upper
()
==
'.CBR'
and
use_rarfile
:
try
:
rarfile
.
UNRAR_TOOL
=
rarExecutable
cf
=
rarfile
.
RarFile
(
tmp_file_name
)
for
name
in
cf
.
getnames
():
ext
=
os
.
path
.
splitext
(
name
)
if
len
(
ext
)
>
1
:
extension
=
ext
[
1
]
.
lower
()
if
extension
in
COVER_EXTENSIONS
:
cover_data
=
cf
.
read
(
name
)
break
except
Exception
as
e
:
log
.
debug
(
'Rarfile failed with error:
%
s'
,
e
)
return
cover_data
def
_extractCover
(
tmp_file_name
,
original_file_extension
,
rarExecutable
):
cover_data
=
extension
=
None
...
...
@@ -87,37 +122,7 @@ def _extractCover(tmp_file_name, original_file_extension, rarExecutable):
cover_data
=
archive
.
getPage
(
index
)
break
else
:
if
original_file_extension
.
upper
()
==
'.CBZ'
:
cf
=
zipfile
.
ZipFile
(
tmp_file_name
)
for
name
in
cf
.
namelist
():
ext
=
os
.
path
.
splitext
(
name
)
if
len
(
ext
)
>
1
:
extension
=
ext
[
1
]
.
lower
()
if
extension
in
COVER_EXTENSIONS
:
cover_data
=
cf
.
read
(
name
)
break
elif
original_file_extension
.
upper
()
==
'.CBT'
:
cf
=
tarfile
.
TarFile
(
tmp_file_name
)
for
name
in
cf
.
getnames
():
ext
=
os
.
path
.
splitext
(
name
)
if
len
(
ext
)
>
1
:
extension
=
ext
[
1
]
.
lower
()
if
extension
in
COVER_EXTENSIONS
:
cover_data
=
cf
.
extractfile
(
name
)
.
read
()
break
elif
original_file_extension
.
upper
()
==
'.CBR'
and
use_rarfile
:
try
:
rarfile
.
UNRAR_TOOL
=
rarExecutable
cf
=
rarfile
.
RarFile
(
tmp_file_name
)
for
name
in
cf
.
getnames
():
ext
=
os
.
path
.
splitext
(
name
)
if
len
(
ext
)
>
1
:
extension
=
ext
[
1
]
.
lower
()
if
extension
in
COVER_EXTENSIONS
:
cover_data
=
cf
.
read
(
name
)
break
except
Exception
as
e
:
log
.
debug
(
'Rarfile failed with error:
%
s'
,
e
)
cover_data
=
_extract_Cover_from_archive
(
original_file_extension
,
tmp_file_name
,
rarExecutable
)
return
_cover_processing
(
tmp_file_name
,
cover_data
,
extension
)
...
...
@@ -142,7 +147,8 @@ def get_comic_info(tmp_file_path, original_file_name, original_file_extension, r
file_path
=
tmp_file_path
,
extension
=
original_file_extension
,
title
=
loadedMetadata
.
title
or
original_file_name
,
author
=
" & "
.
join
([
credit
[
"person"
]
for
credit
in
loadedMetadata
.
credits
if
credit
[
"role"
]
==
"Writer"
])
or
u'Unknown'
,
author
=
" & "
.
join
([
credit
[
"person"
]
for
credit
in
loadedMetadata
.
credits
if
credit
[
"role"
]
==
"Writer"
])
or
u'Unknown'
,
cover
=
_extractCover
(
tmp_file_path
,
original_file_extension
,
rarExecutable
),
description
=
loadedMetadata
.
comments
or
""
,
tags
=
""
,
...
...
cps/config_sql.py
View file @
725fc658
...
...
@@ -146,15 +146,16 @@ class _ConfigSQL(object):
self
.
load
()
change
=
False
if
self
.
config_converterpath
==
None
:
if
self
.
config_converterpath
==
None
:
# pylint: disable=access-member-before-definition
change
=
True
self
.
config_converterpath
=
autodetect_calibre_binary
()
if
self
.
config_kepubifypath
==
None
:
if
self
.
config_kepubifypath
==
None
:
# pylint: disable=access-member-before-definition
change
=
True
self
.
config_kepubifypath
=
autodetect_kepubify_binary
()
if
self
.
config_rarfile_location
==
None
:
if
self
.
config_rarfile_location
==
None
:
# pylint: disable=access-member-before-definition
change
=
True
self
.
config_rarfile_location
=
autodetect_unrar_binary
()
if
change
:
...
...
@@ -181,7 +182,8 @@ class _ConfigSQL(object):
return
None
return
self
.
config_keyfile
def
get_config_ipaddress
(
self
):
@
staticmethod
def
get_config_ipaddress
():
return
cli
.
ipadress
or
""
def
_has_role
(
self
,
role_flag
):
...
...
@@ -299,6 +301,7 @@ class _ConfigSQL(object):
have_metadata_db
=
os
.
path
.
isfile
(
db_file
)
self
.
db_configured
=
have_metadata_db
constants
.
EXTENSIONS_UPLOAD
=
[
x
.
lstrip
()
.
rstrip
()
.
lower
()
for
x
in
self
.
config_upload_formats
.
split
(
','
)]
# pylint: disable=access-member-before-definition
logfile
=
logger
.
setup
(
self
.
config_logfile
,
self
.
config_log_level
)
if
logfile
!=
self
.
config_logfile
:
log
.
warning
(
"Log path
%
s not valid, falling back to default"
,
self
.
config_logfile
)
...
...
cps/constants.py
View file @
725fc658
...
...
@@ -104,7 +104,7 @@ LDAP_AUTH_SIMPLE = 0
DEFAULT_MAIL_SERVER
=
"mail.example.org"
DEFAULT_PASSWORD
=
"admin123"
DEFAULT_PASSWORD
=
"admin123"
# nosec # noqa
DEFAULT_PORT
=
8083
env_CALIBRE_PORT
=
os
.
environ
.
get
(
"CALIBRE_PORT"
,
DEFAULT_PORT
)
try
:
...
...
cps/helper.py
View file @
725fc658
...
...
@@ -134,63 +134,71 @@ def send_registration_mail(e_mail, user_name, default_password, resend=False):
taskMessage
=
_
(
u"Registration e-mail for user:
%(name)
s"
,
name
=
user_name
),
text
=
txt
))
return
def
check_send_to_kindle_without_converter
(
entry
):
bookformats
=
list
()
# no converter - only for mobi and pdf formats
for
ele
in
iter
(
entry
.
data
):
if
ele
.
uncompressed_size
<
config
.
mail_size
:
if
'MOBI'
in
ele
.
format
:
bookformats
.
append
({
'format'
:
'Mobi'
,
'convert'
:
0
,
'text'
:
_
(
'Send
%(format)
s to Kindle'
,
format
=
'Mobi'
)})
if
'PDF'
in
ele
.
format
:
bookformats
.
append
({
'format'
:
'Pdf'
,
'convert'
:
0
,
'text'
:
_
(
'Send
%(format)
s to Kindle'
,
format
=
'Pdf'
)})
if
'AZW'
in
ele
.
format
:
bookformats
.
append
({
'format'
:
'Azw'
,
'convert'
:
0
,
'text'
:
_
(
'Send
%(format)
s to Kindle'
,
format
=
'Azw'
)})
return
bookformats
def
check_send_to_kindle_with_converter
(
entry
):
bookformats
=
list
()
formats
=
list
()
for
ele
in
iter
(
entry
.
data
):
if
ele
.
uncompressed_size
<
config
.
mail_size
:
formats
.
append
(
ele
.
format
)
if
'MOBI'
in
formats
:
bookformats
.
append
({
'format'
:
'Mobi'
,
'convert'
:
0
,
'text'
:
_
(
'Send
%(format)
s to Kindle'
,
format
=
'Mobi'
)})
if
'AZW'
in
formats
:
bookformats
.
append
({
'format'
:
'Azw'
,
'convert'
:
0
,
'text'
:
_
(
'Send
%(format)
s to Kindle'
,
format
=
'Azw'
)})
if
'PDF'
in
formats
:
bookformats
.
append
({
'format'
:
'Pdf'
,
'convert'
:
0
,
'text'
:
_
(
'Send
%(format)
s to Kindle'
,
format
=
'Pdf'
)})
if
'EPUB'
in
formats
and
'MOBI'
not
in
formats
:
bookformats
.
append
({
'format'
:
'Mobi'
,
'convert'
:
1
,
'text'
:
_
(
'Convert
%(orig)
s to
%(format)
s and send to Kindle'
,
orig
=
'Epub'
,
format
=
'Mobi'
)})
if
'AZW3'
in
formats
and
not
'MOBI'
in
formats
:
bookformats
.
append
({
'format'
:
'Mobi'
,
'convert'
:
2
,
'text'
:
_
(
'Convert
%(orig)
s to
%(format)
s and send to Kindle'
,
orig
=
'Azw3'
,
format
=
'Mobi'
)})
return
bookformats
def
check_send_to_kindle
(
entry
):
"""
returns all available book formats for sending to Kindle
"""
if
len
(
entry
.
data
):
bookformats
=
list
()
if
not
config
.
config_converterpath
:
# no converter - only for mobi and pdf formats
for
ele
in
iter
(
entry
.
data
):
if
ele
.
uncompressed_size
<
config
.
mail_size
:
if
'MOBI'
in
ele
.
format
:
bookformats
.
append
({
'format'
:
'Mobi'
,
'convert'
:
0
,
'text'
:
_
(
'Send
%(format)
s to Kindle'
,
format
=
'Mobi'
)})
if
'PDF'
in
ele
.
format
:
bookformats
.
append
({
'format'
:
'Pdf'
,
'convert'
:
0
,
'text'
:
_
(
'Send
%(format)
s to Kindle'
,
format
=
'Pdf'
)})
if
'AZW'
in
ele
.
format
:
bookformats
.
append
({
'format'
:
'Azw'
,
'convert'
:
0
,
'text'
:
_
(
'Send
%(format)
s to Kindle'
,
format
=
'Azw'
)})
book_formats
=
check_send_to_kindle_with_converter
(
entry
)
else
:
formats
=
list
()
for
ele
in
iter
(
entry
.
data
):
if
ele
.
uncompressed_size
<
config
.
mail_size
:
formats
.
append
(
ele
.
format
)
if
'MOBI'
in
formats
:
bookformats
.
append
({
'format'
:
'Mobi'
,
'convert'
:
0
,
'text'
:
_
(
'Send
%(format)
s to Kindle'
,
format
=
'Mobi'
)})
if
'AZW'
in
formats
:
bookformats
.
append
({
'format'
:
'Azw'
,
'convert'
:
0
,
'text'
:
_
(
'Send
%(format)
s to Kindle'
,
format
=
'Azw'
)})
if
'PDF'
in
formats
:
bookformats
.
append
({
'format'
:
'Pdf'
,
'convert'
:
0
,
'text'
:
_
(
'Send
%(format)
s to Kindle'
,
format
=
'Pdf'
)})
if
config
.
config_converterpath
:
if
'EPUB'
in
formats
and
'MOBI'
not
in
formats
:
bookformats
.
append
({
'format'
:
'Mobi'
,
'convert'
:
1
,
'text'
:
_
(
'Convert
%(orig)
s to
%(format)
s and send to Kindle'
,
orig
=
'Epub'
,
format
=
'Mobi'
)})
if
'AZW3'
in
formats
and
not
'MOBI'
in
formats
:
bookformats
.
append
({
'format'
:
'Mobi'
,
'convert'
:
2
,
'text'
:
_
(
'Convert
%(orig)
s to
%(format)
s and send to Kindle'
,
orig
=
'Azw3'
,
format
=
'Mobi'
)})
return
bookformats
book_formats
=
check_send_to_kindle_with_converter
(
entry
)
return
book_formats
else
:
log
.
error
(
u'Cannot find book entry
%
d'
,
entry
.
id
)
return
None
...
...
@@ -742,7 +750,7 @@ def format_runtime(runtime):
# helper function to apply localize status information in tasklist entries
def
render_task_status
(
tasklist
):
renderedtasklist
=
list
()
for
num
,
user
,
added
,
task
in
tasklist
:
for
__
,
user
,
added
,
task
in
tasklist
:
if
user
==
current_user
.
nickname
or
current_user
.
role_admin
():
ret
=
{}
if
task
.
start_time
:
...
...
cps/isoLanguages.py
View file @
725fc658
...
...
@@ -71,7 +71,7 @@ def get_valid_language_codes(locale, language_names, remainder=None):
languages
=
list
()
if
""
in
language_names
:
language_names
.
remove
(
""
)
for
k
,
v
in
get_language_names
(
locale
)
.
items
():
for
k
,
__
in
get_language_names
(
locale
)
.
items
():
if
k
in
language_names
:
languages
.
append
(
k
)
language_names
.
remove
(
k
)
...
...
cps/oauth.py
View file @
725fc658
...
...
@@ -19,7 +19,6 @@
from
__future__
import
division
,
print_function
,
unicode_literals
from
flask
import
session
try
:
from
flask_dance.consumer.backend.sqla
import
SQLAlchemyBackend
,
first
,
_get_real_user
from
sqlalchemy.orm.exc
import
NoResultFound
...
...
@@ -34,134 +33,131 @@ except ImportError:
except
ImportError
:
pass
try
:
class
OAuthBackend
(
SQLAlchemyBackend
):
"""
Stores and retrieves OAuth tokens using a relational database through
the `SQLAlchemy`_ ORM.
.. _SQLAlchemy: https://www.sqlalchemy.org/
"""
def
__init__
(
self
,
model
,
session
,
provider_id
,
user
=
None
,
user_id
=
None
,
user_required
=
None
,
anon_user
=
None
,
cache
=
None
):
self
.
provider_id
=
provider_id
super
(
OAuthBackend
,
self
)
.
__init__
(
model
,
session
,
user
,
user_id
,
user_required
,
anon_user
,
cache
)
def
get
(
self
,
blueprint
,
user
=
None
,
user_id
=
None
):
if
self
.
provider_id
+
'_oauth_token'
in
session
and
session
[
self
.
provider_id
+
'_oauth_token'
]
!=
''
:
return
session
[
self
.
provider_id
+
'_oauth_token'
]
# check cache
cache_key
=
self
.
make_cache_key
(
blueprint
=
blueprint
,
user
=
user
,
user_id
=
user_id
)
token
=
self
.
cache
.
get
(
cache_key
)
if
token
:
return
token
# if not cached, make database queries
query
=
(
self
.
session
.
query
(
self
.
model
)
.
filter_by
(
provider
=
self
.
provider_id
)
)
uid
=
first
([
user_id
,
self
.
user_id
,
blueprint
.
config
.
get
(
"user_id"
)])
u
=
first
(
_get_real_user
(
ref
,
self
.
anon_user
)
for
ref
in
(
user
,
self
.
user
,
blueprint
.
config
.
get
(
"user"
)))
use_provider_user_id
=
False
if
self
.
provider_id
+
'_oauth_user_id'
in
session
and
session
[
self
.
provider_id
+
'_oauth_user_id'
]
!=
''
:
query
=
query
.
filter_by
(
provider_user_id
=
session
[
self
.
provider_id
+
'_oauth_user_id'
])
use_provider_user_id
=
True
if
self
.
user_required
and
not
u
and
not
uid
and
not
use_provider_user_id
:
# raise ValueError("Cannot get OAuth token without an associated user")
return
None
# check for user ID
if
hasattr
(
self
.
model
,
"user_id"
)
and
uid
:
query
=
query
.
filter_by
(
user_id
=
uid
)
# check for user (relationship property)
elif
hasattr
(
self
.
model
,
"user"
)
and
u
:
query
=
query
.
filter_by
(
user
=
u
)
# if we have the property, but not value, filter by None
elif
hasattr
(
self
.
model
,
"user_id"
):
query
=
query
.
filter_by
(
user_id
=
None
)
# run query
try
:
token
=
query
.
one
()
.
token
except
NoResultFound
:
token
=
None
# cache the result
self
.
cache
.
set
(
cache_key
,
token
)
class
OAuthBackend
(
SQLAlchemyBackend
):
"""
Stores and retrieves OAuth tokens using a relational database through
the `SQLAlchemy`_ ORM.
.. _SQLAlchemy: https://www.sqlalchemy.org/
"""
def
__init__
(
self
,
model
,
session
,
provider_id
,
user
=
None
,
user_id
=
None
,
user_required
=
None
,
anon_user
=
None
,
cache
=
None
):
self
.
provider_id
=
provider_id
super
(
OAuthBackend
,
self
)
.
__init__
(
model
,
session
,
user
,
user_id
,
user_required
,
anon_user
,
cache
)
def
get
(
self
,
blueprint
,
user
=
None
,
user_id
=
None
):
if
self
.
provider_id
+
'_oauth_token'
in
session
and
session
[
self
.
provider_id
+
'_oauth_token'
]
!=
''
:
return
session
[
self
.
provider_id
+
'_oauth_token'
]
# check cache
cache_key
=
self
.
make_cache_key
(
blueprint
=
blueprint
,
user
=
user
,
user_id
=
user_id
)
token
=
self
.
cache
.
get
(
cache_key
)
if
token
:
return
token
def
set
(
self
,
blueprint
,
token
,
user
=
None
,
user_id
=
None
):
uid
=
first
([
user_id
,
self
.
user_id
,
blueprint
.
config
.
get
(
"user_id"
)])
u
=
first
(
_get_real_user
(
ref
,
self
.
anon_user
)
for
ref
in
(
user
,
self
.
user
,
blueprint
.
config
.
get
(
"user"
)))
if
self
.
user_required
and
not
u
and
not
uid
:
raise
ValueError
(
"Cannot set OAuth token without an associated user"
)
# if there was an existing model, delete it
existing_query
=
(
self
.
session
.
query
(
self
.
model
)
.
filter_by
(
provider
=
self
.
provider_id
)
)
# check for user ID
has_user_id
=
hasattr
(
self
.
model
,
"user_id"
)
if
has_user_id
and
uid
:
existing_query
=
existing_query
.
filter_by
(
user_id
=
uid
)
# check for user (relationship property)
has_user
=
hasattr
(
self
.
model
,
"user"
)
if
has_user
and
u
:
existing_query
=
existing_query
.
filter_by
(
user
=
u
)
# queue up delete query -- won't be run until commit()
existing_query
.
delete
()
# create a new model for this token
kwargs
=
{
"provider"
:
self
.
provider_id
,
"token"
:
token
,
}
if
has_user_id
and
uid
:
kwargs
[
"user_id"
]
=
uid
if
has_user
and
u
:
kwargs
[
"user"
]
=
u
self
.
session
.
add
(
self
.
model
(
**
kwargs
))
# commit to delete and add simultaneously
self
.
session
.
commit
()
# invalidate cache
self
.
cache
.
delete
(
self
.
make_cache_key
(
blueprint
=
blueprint
,
user
=
user
,
user_id
=
user_id
))
def
delete
(
self
,
blueprint
,
user
=
None
,
user_id
=
None
):
query
=
(
self
.
session
.
query
(
self
.
model
)
.
filter_by
(
provider
=
self
.
provider_id
)
)
uid
=
first
([
user_id
,
self
.
user_id
,
blueprint
.
config
.
get
(
"user_id"
)])
u
=
first
(
_get_real_user
(
ref
,
self
.
anon_user
)
for
ref
in
(
user
,
self
.
user
,
blueprint
.
config
.
get
(
"user"
)))
if
self
.
user_required
and
not
u
and
not
uid
:
raise
ValueError
(
"Cannot delete OAuth token without an associated user"
)
# check for user ID
if
hasattr
(
self
.
model
,
"user_id"
)
and
uid
:
query
=
query
.
filter_by
(
user_id
=
uid
)
# check for user (relationship property)
elif
hasattr
(
self
.
model
,
"user"
)
and
u
:
query
=
query
.
filter_by
(
user
=
u
)
# if we have the property, but not value, filter by None
elif
hasattr
(
self
.
model
,
"user_id"
):
query
=
query
.
filter_by
(
user_id
=
None
)
# run query
query
.
delete
()
self
.
session
.
commit
()
# invalidate cache
self
.
cache
.
delete
(
self
.
make_cache_key
(
blueprint
=
blueprint
,
user
=
user
,
user_id
=
user_id
,
))
except
Exception
:
pass
# if not cached, make database queries
query
=
(
self
.
session
.
query
(
self
.
model
)
.
filter_by
(
provider
=
self
.
provider_id
)
)
uid
=
first
([
user_id
,
self
.
user_id
,
blueprint
.
config
.
get
(
"user_id"
)])
u
=
first
(
_get_real_user
(
ref
,
self
.
anon_user
)
for
ref
in
(
user
,
self
.
user
,
blueprint
.
config
.
get
(
"user"
)))
use_provider_user_id
=
False
if
self
.
provider_id
+
'_oauth_user_id'
in
session
and
session
[
self
.
provider_id
+
'_oauth_user_id'
]
!=
''
:
query
=
query
.
filter_by
(
provider_user_id
=
session
[
self
.
provider_id
+
'_oauth_user_id'
])
use_provider_user_id
=
True
if
self
.
user_required
and
not
u
and
not
uid
and
not
use_provider_user_id
:
# raise ValueError("Cannot get OAuth token without an associated user")
return
None
# check for user ID
if
hasattr
(
self
.
model
,
"user_id"
)
and
uid
:
query
=
query
.
filter_by
(
user_id
=
uid
)
# check for user (relationship property)
elif
hasattr
(
self
.
model
,
"user"
)
and
u
:
query
=
query
.
filter_by
(
user
=
u
)
# if we have the property, but not value, filter by None
elif
hasattr
(
self
.
model
,
"user_id"
):
query
=
query
.
filter_by
(
user_id
=
None
)
# run query
try
:
token
=
query
.
one
()
.
token
except
NoResultFound
:
token
=
None
# cache the result
self
.
cache
.
set
(
cache_key
,
token
)
return
token
def
set
(
self
,
blueprint
,
token
,
user
=
None
,
user_id
=
None
):
uid
=
first
([
user_id
,
self
.
user_id
,
blueprint
.
config
.
get
(
"user_id"
)])
u
=
first
(
_get_real_user
(
ref
,
self
.
anon_user
)
for
ref
in
(
user
,
self
.
user
,
blueprint
.
config
.
get
(
"user"
)))
if
self
.
user_required
and
not
u
and
not
uid
:
raise
ValueError
(
"Cannot set OAuth token without an associated user"
)
# if there was an existing model, delete it
existing_query
=
(
self
.
session
.
query
(
self
.
model
)
.
filter_by
(
provider
=
self
.
provider_id
)
)
# check for user ID
has_user_id
=
hasattr
(
self
.
model
,
"user_id"
)
if
has_user_id
and
uid
:
existing_query
=
existing_query
.
filter_by
(
user_id
=
uid
)
# check for user (relationship property)
has_user
=
hasattr
(
self
.
model
,
"user"
)
if
has_user
and
u
:
existing_query
=
existing_query
.
filter_by
(
user
=
u
)
# queue up delete query -- won't be run until commit()
existing_query
.
delete
()
# create a new model for this token
kwargs
=
{
"provider"
:
self
.
provider_id
,
"token"
:
token
,
}
if
has_user_id
and
uid
:
kwargs
[
"user_id"
]
=
uid
if
has_user
and
u
:
kwargs
[
"user"
]
=
u
self
.
session
.
add
(
self
.
model
(
**
kwargs
))
# commit to delete and add simultaneously
self
.
session
.
commit
()
# invalidate cache
self
.
cache
.
delete
(
self
.
make_cache_key
(
blueprint
=
blueprint
,
user
=
user
,
user_id
=
user_id
))
def
delete
(
self
,
blueprint
,
user
=
None
,
user_id
=
None
):
query
=
(
self
.
session
.
query
(
self
.
model
)
.
filter_by
(
provider
=
self
.
provider_id
)
)
uid
=
first
([
user_id
,
self
.
user_id
,
blueprint
.
config
.
get
(
"user_id"
)])
u
=
first
(
_get_real_user
(
ref
,
self
.
anon_user
)
for
ref
in
(
user
,
self
.
user
,
blueprint
.
config
.
get
(
"user"
)))
if
self
.
user_required
and
not
u
and
not
uid
:
raise
ValueError
(
"Cannot delete OAuth token without an associated user"
)
# check for user ID
if
hasattr
(
self
.
model
,
"user_id"
)
and
uid
:
query
=
query
.
filter_by
(
user_id
=
uid
)
# check for user (relationship property)
elif
hasattr
(
self
.
model
,
"user"
)
and
u
:
query
=
query
.
filter_by
(
user
=
u
)
# if we have the property, but not value, filter by None
elif
hasattr
(
self
.
model
,
"user_id"
):
query
=
query
.
filter_by
(
user_id
=
None
)
# run query
query
.
delete
()
self
.
session
.
commit
()
# invalidate cache
self
.
cache
.
delete
(
self
.
make_cache_key
(
blueprint
=
blueprint
,
user
=
user
,
user_id
=
user_id
,
))
cps/oauth_bb.py
View file @
725fc658
...
...
@@ -35,7 +35,10 @@ from sqlalchemy.orm.exc import NoResultFound
from
.
import
constants
,
logger
,
config
,
app
,
ub
from
.oauth
import
OAuthBackend
,
backend_resultcode
try
:
from
.oauth
import
OAuthBackend
,
backend_resultcode
except
NameError
:
pass
oauth_check
=
{}
...
...
cps/server.py
View file @
725fc658
...
...
@@ -137,7 +137,7 @@ class WebServer(object):
return
sock
,
_readable_listen_address
(
*
address
)
@
staticmethod
def
_get_args_for_reloading
(
self
):
"""Determine how the script was executed, and return the args needed
to execute it again in a new process.
...
...
cps/services/SyncToken.py
View file @
725fc658
...
...
@@ -64,7 +64,7 @@ class SyncToken:
books_last_modified: Datetime representing the last modified book that the device knows about.
"""
SYNC_TOKEN_HEADER
=
"x-kobo-synctoken"
SYNC_TOKEN_HEADER
=
"x-kobo-synctoken"
# nosec
VERSION
=
"1-1-0"
LAST_MODIFIED_ADDED_VERSION
=
"1-1-0"
MIN_VERSION
=
"1-0-0"
...
...
@@ -91,7 +91,7 @@ class SyncToken:
def
__init__
(
self
,
raw_kobo_store_token
=
""
,
raw_kobo_store_token
=
""
,
# nosec
books_last_created
=
datetime
.
min
,
books_last_modified
=
datetime
.
min
,
archive_last_modified
=
datetime
.
min
,
...
...
@@ -110,7 +110,7 @@ class SyncToken:
@
staticmethod
def
from_headers
(
headers
):
sync_token_header
=
headers
.
get
(
SyncToken
.
SYNC_TOKEN_HEADER
,
""
)
if
sync_token_header
==
""
:
if
sync_token_header
==
""
:
# nosec
return
SyncToken
()
# On the first sync from a Kobo device, we may receive the SyncToken
...
...
cps/static/css/caliBlur_override.css
View file @
725fc658
body
.serieslist.grid-view
div
.container-fluid
>
div
>
div
.col-sm-10
:before
{
display
:
none
;
body
.serieslist.grid-view
div
.container-fluid
>
div
>
div
.col-sm-10
:before
{
display
:
none
;
}
.cover
.badge
{
position
:
absolute
;
top
:
0
;
left
:
0
;
color
:
#fff
;
background-color
:
#cc7b19
;
border-radius
:
0
;
padding
:
0
8px
;
box-shadow
:
0
0
4px
rgba
(
0
,
0
,
0
,
.6
);
line-height
:
24px
;
position
:
absolute
;
top
:
0
;
left
:
0
;
color
:
#fff
;
background-color
:
#cc7b19
;
border-radius
:
0
;
padding
:
0
8px
;
box-shadow
:
0
0
4px
rgba
(
0
,
0
,
0
,
.6
);
line-height
:
24px
;
}
.cover
{
box-shadow
:
0
0
4px
rgba
(
0
,
0
,
0
,
.6
);
.cover
{
box-shadow
:
0
0
4px
rgba
(
0
,
0
,
0
,
.6
);
}
.cover
.read
{
padding
:
0
0px
;
line-height
:
15px
;
.cover
.read
{
padding
:
0px
0px
;
line-height
:
15px
;
}
cps/static/css/kthoom.css
View file @
725fc658
...
...
@@ -33,7 +33,6 @@ body {
position
:
relative
;
cursor
:
pointer
;
padding
:
4px
;
transition
:
all
0.2s
ease
;
}
...
...
@@ -45,7 +44,7 @@ body {
#sidebar
a
.active
,
#sidebar
a
.active
img
+
span
{
background-color
:
#45
B29D
;
background-color
:
#45
b29d
;
}
#sidebar
li
img
{
...
...
@@ -99,7 +98,7 @@ body {
background-color
:
#ccc
;
}
#progress
.bar-read
{
#progress
.bar-read
{
color
:
#fff
;
background-color
:
#45b29d
;
}
...
...
cps/static/css/main.css
View file @
725fc658
...
...
@@ -490,7 +490,6 @@ input:-moz-placeholder { color: #454545; }
position
:
fixed
;
top
:
50%
;
left
:
50%
;
//
width
:
50%
;
width
:
630px
;
height
:
auto
;
z-index
:
2000
;
...
...
@@ -593,7 +592,6 @@ input:-moz-placeholder { color: #454545; }
}
.md-content
>
.closer
{
//
font-size
:
18px
;
position
:
absolute
;
right
:
0
;
top
:
0
;
...
...
cps/static/css/style.css
View file @
725fc658
.tooltip.bottom
.tooltip-inner
{
font-size
:
13px
;
font-family
:
Open
Sans
Semibold
,
Helvetica
Neue
,
Helvetica
,
Arial
,
sans-serif
;
font-family
:
Open
Sans
Semibold
,
Helvetica
Neue
,
Helvetica
,
Arial
,
sans-serif
;
-webkit-font-smoothing
:
antialiased
;
-moz-osx-font-smoothing
:
grayscale
;
padding
:
3px
10px
;
...
...
@@ -48,25 +48,25 @@ body {
body
h2
{
font-weight
:
normal
;
color
:
#444
;
color
:
#444
;
}
a
,
.danger
,
.book-remove
,
.editable-empty
,
.editable-empty
:hover
{
color
:
#45b29d
;
}
a
,
.danger
,
.book-remove
,
.editable-empty
,
.editable-empty
:hover
{
color
:
#45b29d
;
}
.book-remove
:hover
{
color
:
#23527c
;
}
.btn-default
a
{
color
:
#444
;
}
.btn-default
a
:hover
{
color
:
#45b29d
;
text-decoration
:
None
;
color
:
#45b29d
;
text-decoration
:
None
;
}
.btn-default
:hover
{
color
:
#45b29d
;
color
:
#45b29d
;
}
.editable-click
,
a
.editable-click
,
a
.editable-click
:hover
{
border-bottom
:
None
;
}
.editable-click
,
a
.editable-click
,
a
.editable-click
:hover
{
border-bottom
:
None
;
}
.navigation
.nav-head
{
text-transform
:
uppercase
;
...
...
@@ -119,7 +119,7 @@ a, .danger,.book-remove, .editable-empty, .editable-empty:hover { color: #45b29d
max-height
:
100%
;
}
.container-fluid
.discover
{
margin-bottom
:
50px
;
}
.container-fluid
.discover
{
margin-bottom
:
50px
;
}
.container-fluid
.new-books
{
border-top
:
1px
solid
#ccc
;
}
.container-fluid
.new-books
h2
{
margin
:
50px
0
0
0
;
}
.container-fluid
.book
{
...
...
@@ -174,9 +174,10 @@ a, .danger,.book-remove, .editable-empty, .editable-empty:hover { color: #45b29d
.container-fluid
.book
.meta
.rating
{
margin-top
:
5px
;
}
.rating
.glyphicon-star-empty
{
color
:
#444
;
}
.rating
.glyphicon-star.good
{
color
:
#444
;
}
.rating-clear
.glyphicon-remove
{
color
:
#333
}
.rating-clear
.glyphicon-remove
{
color
:
#333
;
}
.container-fluid
.author
.author-hidden
,
.container-fluid
.author
.author-hidden-divider
{
display
:
none
;
}
.container-fluid
.author
.author-hidden
,
.container-fluid
.author
.author-hidden-divider
{
display
:
none
;
}
.navbar-brand
{
font-family
:
'Grand Hotel'
,
cursive
;
...
...
@@ -190,7 +191,7 @@ a, .danger,.book-remove, .editable-empty, .editable-empty:hover { color: #45b29d
border-top
:
1px
solid
#ccc
;
}
.more-stuff
>
li
{
margin-bottom
:
10px
;
}
.more-stuff
>
li
{
margin-bottom
:
10px
;
}
.navbar-collapse.in
.navbar-nav
{
margin
:
0
;
}
span
.glyphicon.glyphicon-tags
{
...
...
@@ -211,17 +212,17 @@ span.glyphicon.glyphicon-tags {
box-shadow
:
0
5px
8px
-6px
#777
;
}
.navbar-default
.navbar-toggle
.icon-bar
{
background-color
:
#000
;
}
.navbar-default
.navbar-toggle
{
border-color
:
#000
;
}
.navbar-default
.navbar-toggle
.icon-bar
{
background-color
:
#000
;
}
.navbar-default
.navbar-toggle
{
border-color
:
#000
;
}
.cover
{
margin-bottom
:
10px
;
}
.cover
.badge
{
position
:
absolute
;
top
:
2px
;
left
:
2px
;
color
:
#000
;
border-radius
:
10px
;
background-color
:
#fff
;
position
:
absolute
;
top
:
2px
;
left
:
2px
;
color
:
#000
;
border-radius
:
10px
;
background-color
:
#fff
;
}
.cover
.read
{
left
:
auto
;
...
...
@@ -231,14 +232,14 @@ span.glyphicon.glyphicon-tags {
display
:
inline-block
;
padding
:
2px
;
}
.cover-height
{
max-height
:
100px
;}
.cover-height
{
max-height
:
100px
;
}
.col-sm-2
a
.cover-small
{
margin
:
5px
;
max-height
:
200px
;
}
.btn-file
{
position
:
relative
;
overflow
:
hidden
;
}
.btn-file
{
position
:
relative
;
overflow
:
hidden
;
}
.btn-file
input
[
type
=
file
]
{
position
:
absolute
;
...
...
@@ -256,22 +257,42 @@ span.glyphicon.glyphicon-tags {
display
:
block
;
}
.btn-toolbar
.btn
,
.discover
.btn
{
margin-bottom
:
5px
;
}
.button-link
{
color
:
#fff
;
}
.btn-primary
:hover
,
.btn-primary
:focus
,
.btn-primary
:active
,
.btn-primary.active
,
.open
.dropdown-toggle.btn-primary
{
background-color
:
#1C5484
;
}
.btn-primary.disabled
,
.btn-primary
[
disabled
],
fieldset
[
disabled
]
.btn-primary
,
.btn-primary.disabled
:hover
,
.btn-primary
[
disabled
]
:hover
,
fieldset
[
disabled
]
.btn-primary
:hover
,
.btn-primary.disabled
:focus
,
.btn-primary
[
disabled
]
:focus
,
fieldset
[
disabled
]
.btn-primary
:focus
,
.btn-primary.disabled
:active
,
.btn-primary
[
disabled
]
:active
,
fieldset
[
disabled
]
.btn-primary
:active
,
.btn-primary.disabled.active
,
.btn-primary
[
disabled
]
.active
,
fieldset
[
disabled
]
.btn-primary.active
{
background-color
:
#89B9E2
;
}
.btn-toolbar
>
.btn
+
.btn
,
.btn-toolbar
>
.btn-group
+
.btn
,
.btn-toolbar
>
.btn
+
.btn-group
,
.btn-toolbar
>
.btn-group
+
.btn-group
{
margin-left
:
0
;
}
.panel-body
{
background-color
:
#f5f5f5
;
}
.spinner
{
margin
:
0
41%
;
}
.spinner2
{
margin
:
0
41%
;
}
.btn-toolbar
.btn
,
.discover
.btn
{
margin-bottom
:
5px
;
}
.button-link
{
color
:
#fff
;
}
.btn-primary
:hover
,
.btn-primary
:focus
,
.btn-primary
:active
,
.btn-primary.active
,
.open
.dropdown-toggle.btn-primary
{
background-color
:
#1c5484
;
}
.btn-primary.disabled
,
.btn-primary
[
disabled
],
fieldset
[
disabled
]
.btn-primary
,
.btn-primary.disabled
:hover
,
.btn-primary
[
disabled
]
:hover
,
fieldset
[
disabled
]
.btn-primary
:hover
,
.btn-primary.disabled
:focus
,
.btn-primary
[
disabled
]
:focus
,
fieldset
[
disabled
]
.btn-primary
:focus
,
.btn-primary.disabled
:active
,
.btn-primary
[
disabled
]
:active
,
fieldset
[
disabled
]
.btn-primary
:active
,
.btn-primary.disabled.active
,
.btn-primary
[
disabled
]
.active
,
fieldset
[
disabled
]
.btn-primary.active
{
background-color
:
#89B9E2
;
}
.btn-toolbar
>
.btn
+
.btn
,
.btn-toolbar
>
.btn-group
+
.btn
,
.btn-toolbar
>
.btn
+
.btn-group
,
.btn-toolbar
>
.btn-group
+
.btn-group
{
margin-left
:
0
;
}
.panel-body
{
background-color
:
#f5f5f5
;
}
.spinner
{
margin
:
0
41%
;
}
.spinner2
{
margin
:
0
41%
;
}
.intend-form
{
margin-left
:
20px
;
}
table
.bg-dark-danger
{
background-color
:
#d9534f
;
color
:
#fff
;
}
table
.bg-dark-danger
a
{
color
:
#fff
;
}
table
.bg-dark-danger
:hover
{
background-color
:
#c9302c
;
}
table
.bg-primary
:hover
{
background-color
:
#1C
5484
;
}
table
.bg-primary
a
{
color
:
#fff
;
}
.block-label
{
display
:
block
;
}
.fake-input
{
position
:
absolute
;
pointer-events
:
none
;
top
:
0
;
}
table
.bg-dark-danger
{
background-color
:
#d9534f
;
color
:
#fff
;
}
table
.bg-dark-danger
a
{
color
:
#fff
;
}
table
.bg-dark-danger
:hover
{
background-color
:
#c9302c
;
}
table
.bg-primary
:hover
{
background-color
:
#1c
5484
;
}
table
.bg-primary
a
{
color
:
#fff
;
}
.block-label
{
display
:
block
;
}
.fake-input
{
position
:
absolute
;
pointer-events
:
none
;
top
:
0
;
}
input
.pill
{
position
:
absolute
;
opacity
:
0
;
}
...
...
cps/static/js/caliBlur.js
View file @
725fc658
...
...
@@ -677,7 +677,7 @@ $(".navbar-collapse.collapse.in").before('<div class="sidebar-backdrop"></div>')
// Get rid of leading white space
recentlyAdded
=
$
(
"#nav_new a:contains('Recently')"
).
text
().
trim
();
$
(
"#nav_new a:contains('Recently')"
).
contents
().
filter
(
function
()
{
return
this
.
nodeType
==
3
return
this
.
nodeType
==
=
3
}).
each
(
function
()
{
this
.
textContent
=
this
.
textContent
.
replace
(
" Recently Added"
,
recentlyAdded
);
});
...
...
cps/static/js/filter_list.js
View file @
725fc658
...
...
@@ -88,7 +88,7 @@ $("#desc").click(function() {
// Find count of middle element
var
count
=
$
(
".row:visible"
).
length
;
if
(
count
>
20
)
{
middle
=
parseInt
(
count
/
2
)
+
(
count
%
2
);
middle
=
parseInt
(
count
/
2
,
10
)
+
(
count
%
2
);
//var middle = parseInt(count / 2) + (count % 2);
// search for the middle of all visible elements
...
...
@@ -135,7 +135,7 @@ $("#asc").click(function() {
// Find count of middle element
var
count
=
$
(
".row:visible"
).
length
;
if
(
count
>
20
)
{
var
middle
=
parseInt
(
count
/
2
)
+
(
count
%
2
);
var
middle
=
parseInt
(
count
/
2
,
10
)
+
(
count
%
2
);
//var middle = parseInt(count / 2) + (count % 2);
// search for the middle of all visible elements
...
...
cps/static/js/main.js
View file @
725fc658
...
...
@@ -38,10 +38,10 @@ $(document).on("change", "input[type=\"checkbox\"][data-control]", function () {
$
(
document
).
on
(
"change"
,
"select[data-control]"
,
function
()
{
var
$this
=
$
(
this
);
var
name
=
$this
.
data
(
"control"
);
var
showOrHide
=
parseInt
(
$this
.
val
());
var
showOrHide
=
parseInt
(
$this
.
val
()
,
10
);
// var showOrHideLast = $("#" + name + " option:last").val()
for
(
var
i
=
0
;
i
<
$
(
this
)[
0
].
length
;
i
++
)
{
var
element
=
parseInt
(
$
(
this
)[
0
][
i
].
value
);
var
element
=
parseInt
(
$
(
this
)[
0
][
i
].
value
,
10
);
if
(
element
===
showOrHide
)
{
$
(
"[data-related^="
+
name
+
"][data-related*=-"
+
element
+
"]"
).
show
();
}
else
{
...
...
cps/static/js/table.js
View file @
725fc658
...
...
@@ -16,6 +16,7 @@
*/
/* exported TableActions, RestrictionActions, EbookActions, responseHandler */
/* global getPath, ConfirmDialog */
var
selections
=
[];
...
...
cps/tasks/upload.py
View file @
725fc658
...
...
@@ -12,7 +12,6 @@ class TaskUpload(CalibreTask):
def
run
(
self
,
worker_thread
):
"""Upload task doesn't have anything to do, it's simply a way to add information to the task list"""
pass
@
property
def
name
(
self
):
...
...
cps/ub.py
View file @
725fc658
...
...
@@ -138,15 +138,15 @@ class UserBase:
mct
=
self
.
allowed_column_value
or
""
return
[
t
.
strip
()
for
t
in
mct
.
split
(
","
)]
def
get_view_property
(
self
,
page
,
prop
erty
):
def
get_view_property
(
self
,
page
,
prop
):
if
not
self
.
view_settings
.
get
(
page
):
return
None
return
self
.
view_settings
[
page
]
.
get
(
prop
erty
)
return
self
.
view_settings
[
page
]
.
get
(
prop
)
def
set_view_property
(
self
,
page
,
prop
erty
,
value
):
def
set_view_property
(
self
,
page
,
prop
,
value
):
if
not
self
.
view_settings
.
get
(
page
):
self
.
view_settings
[
page
]
=
dict
()
self
.
view_settings
[
page
][
prop
erty
]
=
value
self
.
view_settings
[
page
][
prop
]
=
value
try
:
flag_modified
(
self
,
"view_settings"
)
except
AttributeError
:
...
...
@@ -437,11 +437,8 @@ class RemoteAuthToken(Base):
return
'<Token
%
r>'
%
self
.
id
# Migrate database to current version, has to be updated after every database change. Currently migration from
# everywhere to current should work. Migration is done by checking if relevant columns are existing, and than adding
# rows with SQL commands
def
migrate_Database
(
session
):
engine
=
session
.
bind
# Add missing tables during migration of database
def
add_missing_tables
(
engine
,
session
):
if
not
engine
.
dialect
.
has_table
(
engine
.
connect
(),
"book_read_link"
):
ReadBook
.
__table__
.
create
(
bind
=
engine
)
if
not
engine
.
dialect
.
has_table
(
engine
.
connect
(),
"bookmark"
):
...
...
@@ -459,6 +456,10 @@ def migrate_Database(session):
with
engine
.
connect
()
as
conn
:
conn
.
execute
(
"insert into registration (domain, allow) values('
%
.
%
',1)"
)
session
.
commit
()
# migrate all settings missing in registration table
def
migrate_registration_table
(
engine
,
session
):
try
:
session
.
query
(
exists
()
.
where
(
Registration
.
allow
))
.
scalar
()
session
.
commit
()
...
...
@@ -468,27 +469,29 @@ def migrate_Database(session):
conn
.
execute
(
"update registration set 'allow' = 1"
)
session
.
commit
()
try
:
session
.
query
(
exists
()
.
where
(
RemoteAuthToken
.
token_type
))
.
scalar
()
session
.
commit
()
except
exc
.
OperationalError
:
# Database is not compatible, some columns are missing
with
engine
.
connect
()
as
conn
:
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
()
# Handle table exists, but no content
cnt
=
session
.
query
(
Registration
)
.
count
()
if
not
cnt
:
with
engine
.
connect
()
as
conn
:
conn
.
execute
(
"insert into registration (domain, allow) values('
%
.
%
',1)"
)
session
.
commit
()
except
exc
.
OperationalError
:
# Database is not writeable
print
(
'Settings database is not writeable. Exiting...'
)
sys
.
exit
(
2
)
# Remove login capability of user Guest
def
migrate_guest_password
(
engine
,
session
):
try
:
session
.
query
(
exists
()
.
where
(
ReadBook
.
read_status
))
.
scalar
()
except
exc
.
OperationalError
:
with
engine
.
connect
()
as
conn
:
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"
)
conn
.
execute
(
"UPDATE user SET password='' where nickname = 'Guest' and password !=''"
)
session
.
commit
()
test
=
session
.
query
(
ReadBook
)
.
filter
(
ReadBook
.
last_modified
==
None
)
.
all
()
for
book
in
test
:
book
.
last_modified
=
datetime
.
datetime
.
utcnow
()
session
.
commit
()
except
exc
.
OperationalError
:
print
(
'Settings database is not writeable. Exiting...'
)
sys
.
exit
(
2
)
def
migrate_shelfs
(
engine
,
session
):
try
:
session
.
query
(
exists
()
.
where
(
Shelf
.
uuid
))
.
scalar
()
except
exc
.
OperationalError
:
...
...
@@ -504,22 +507,51 @@ def migrate_Database(session):
for
book_shelf
in
session
.
query
(
BookShelf
)
.
all
():
book_shelf
.
date_added
=
datetime
.
datetime
.
now
()
session
.
commit
()
try
:
# Handle table exists, but no content
cnt
=
session
.
query
(
Registration
)
.
count
()
if
not
cnt
:
with
engine
.
connect
()
as
conn
:
conn
.
execute
(
"insert into registration (domain, allow) values('
%
.
%
',1)"
)
session
.
commit
()
except
exc
.
OperationalError
:
# Database is not writeable
print
(
'Settings database is not writeable. Exiting...'
)
sys
.
exit
(
2
)
try
:
session
.
query
(
exists
()
.
where
(
BookShelf
.
order
))
.
scalar
()
except
exc
.
OperationalError
:
# Database is not compatible, some columns are missing
with
engine
.
connect
()
as
conn
:
conn
.
execute
(
"ALTER TABLE book_shelf_link ADD column 'order' INTEGER DEFAULT 1"
)
session
.
commit
()
def
migrate_readBook
(
engine
,
session
):
try
:
session
.
query
(
exists
()
.
where
(
ReadBook
.
read_status
))
.
scalar
()
except
exc
.
OperationalError
:
with
engine
.
connect
()
as
conn
:
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
()
test
=
session
.
query
(
ReadBook
)
.
filter
(
ReadBook
.
last_modified
==
None
)
.
all
()
for
book
in
test
:
book
.
last_modified
=
datetime
.
datetime
.
utcnow
()
session
.
commit
()
def
migrate_remoteAuthToken
(
engine
,
session
):
try
:
session
.
query
(
exists
()
.
where
(
RemoteAuthToken
.
token_type
))
.
scalar
()
session
.
commit
()
except
exc
.
OperationalError
:
# Database is not compatible, some columns are missing
with
engine
.
connect
()
as
conn
:
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
()
# Migrate database to current version, has to be updated after every database change. Currently migration from
# everywhere to current should work. Migration is done by checking if relevant columns are existing, and than adding
# rows with SQL commands
def
migrate_Database
(
session
):
engine
=
session
.
bind
add_missing_tables
(
engine
,
session
)
migrate_registration_table
(
engine
,
session
)
migrate_readBook
(
engine
,
session
)
migrate_remoteAuthToken
(
engine
,
session
)
migrate_shelfs
(
engine
,
session
)
try
:
create
=
False
session
.
query
(
exists
()
.
where
(
User
.
sidebar_view
))
.
scalar
()
...
...
@@ -578,8 +610,7 @@ def migrate_Database(session):
"locale VARCHAR(2),"
"sidebar_view INTEGER,"
"default_language VARCHAR(3),"
# "series_view VARCHAR(10),"
"view_settings VARCHAR,"
"view_settings VARCHAR,"
"UNIQUE (nickname),"
"UNIQUE (email))"
)
conn
.
execute
(
"INSERT INTO user_id(id, nickname, email, role, password, kindle_mail,locale,"
...
...
@@ -590,15 +621,7 @@ def migrate_Database(session):
conn
.
execute
(
"DROP TABLE user"
)
conn
.
execute
(
"ALTER TABLE user_id RENAME TO user"
)
session
.
commit
()
# Remove login capability of user Guest
try
:
with
engine
.
connect
()
as
conn
:
conn
.
execute
(
"UPDATE user SET password='' where nickname = 'Guest' and password !=''"
)
session
.
commit
()
except
exc
.
OperationalError
:
print
(
'Settings database is not writeable. Exiting...'
)
sys
.
exit
(
2
)
migrate_guest_password
(
engine
,
session
)
def
clean_database
(
session
):
...
...
cps/usermanagement.py
View file @
725fc658
...
...
@@ -72,7 +72,7 @@ def load_user_from_request(request):
def
load_user_from_auth_header
(
header_val
):
if
header_val
.
startswith
(
'Basic '
):
header_val
=
header_val
.
replace
(
'Basic '
,
''
,
1
)
basic_username
=
basic_password
=
''
basic_username
=
basic_password
=
''
# nosec
try
:
header_val
=
base64
.
b64decode
(
header_val
)
.
decode
(
'utf-8'
)
basic_username
=
header_val
.
split
(
':'
)[
0
]
...
...
cps/web.py
View file @
725fc658
...
...
@@ -216,7 +216,7 @@ def update_view():
for
param
in
to_save
[
element
]:
current_user
.
set_view_property
(
element
,
param
,
to_save
[
element
][
param
])
except
Exception
as
e
:
log
.
error
(
"Could not save view_settings:
%
r
%
r: e"
,
request
,
to_save
,
e
)
log
.
error
(
"Could not save view_settings:
%
r
%
r:
%
e"
,
request
,
to_save
,
e
)
return
"Invalid request"
,
400
return
"1"
,
200
...
...
@@ -340,7 +340,7 @@ def get_matching_tags():
return
json_dumps
def
render_books_list
(
data
,
sort
,
book_id
,
page
):
def
get_sort_function
(
sort
,
data
):
order
=
[
db
.
Books
.
timestamp
.
desc
()]
if
sort
==
'stored'
:
sort
=
current_user
.
get_view_property
(
data
,
'stored'
)
...
...
@@ -366,6 +366,11 @@ def render_books_list(data, sort, book_id, page):
order
=
[
db
.
Books
.
series_index
.
asc
()]
if
sort
==
'seriesdesc'
:
order
=
[
db
.
Books
.
series_index
.
desc
()]
return
order
def
render_books_list
(
data
,
sort
,
book_id
,
page
):
order
=
get_sort_function
(
sort
,
data
)
if
data
==
"rated"
:
if
current_user
.
check_visibility
(
constants
.
SIDEBAR_BEST_RATED
):
...
...
@@ -453,18 +458,11 @@ def render_hot_books(page):
def
render_downloaded_books
(
page
,
order
):
if
current_user
.
check_visibility
(
constants
.
SIDEBAR_DOWNLOAD
):
# order = order or []
if
current_user
.
show_detail_random
():
random
=
calibre_db
.
session
.
query
(
db
.
Books
)
.
filter
(
calibre_db
.
common_filters
())
\
.
order_by
(
func
.
random
())
.
limit
(
config
.
config_random_books
)
else
:
random
=
false
()
# off = int(int(config.config_books_per_page) * (page - 1))
'''entries, random, pagination = calibre_db.fill_indexpage(page, 0,
db.Books,
db_filter,
order,
ub.ReadBook, db.Books.id==ub.ReadBook.book_id)'''
entries
,
__
,
pagination
=
calibre_db
.
fill_indexpage
(
page
,
0
,
...
...
@@ -748,7 +746,7 @@ def list_books():
search
=
request
.
args
.
get
(
"search"
)
total_count
=
calibre_db
.
session
.
query
(
db
.
Books
)
.
count
()
if
search
:
entries
,
filtered_count
,
pagination
=
calibre_db
.
get_search_results
(
search
,
off
,
order
,
limit
)
entries
,
filtered_count
,
__
=
calibre_db
.
get_search_results
(
search
,
off
,
order
,
limit
)
else
:
entries
,
__
,
__
=
calibre_db
.
fill_indexpage
((
int
(
off
)
/
(
int
(
limit
))
+
1
),
limit
,
db
.
Books
,
True
,
order
)
filtered_count
=
total_count
...
...
@@ -1411,12 +1409,71 @@ def logout():
# ################################### Users own configuration #########################################################
def
change_profile
(
kobo_support
,
local_oauth_check
,
oauth_status
,
translations
,
languages
):
to_save
=
request
.
form
.
to_dict
()
current_user
.
random_books
=
0
if
current_user
.
role_passwd
()
or
current_user
.
role_admin
():
if
"password"
in
to_save
and
to_save
[
"password"
]:
current_user
.
password
=
generate_password_hash
(
to_save
[
"password"
])
if
"kindle_mail"
in
to_save
and
to_save
[
"kindle_mail"
]
!=
current_user
.
kindle_mail
:
current_user
.
kindle_mail
=
to_save
[
"kindle_mail"
]
if
"allowed_tags"
in
to_save
and
to_save
[
"allowed_tags"
]
!=
current_user
.
allowed_tags
:
current_user
.
allowed_tags
=
to_save
[
"allowed_tags"
]
.
strip
()
if
"email"
in
to_save
and
to_save
[
"email"
]
!=
current_user
.
email
:
if
config
.
config_public_reg
and
not
check_valid_domain
(
to_save
[
"email"
]):
flash
(
_
(
u"E-mail is not from valid domain"
),
category
=
"error"
)
return
render_title_template
(
"user_edit.html"
,
content
=
current_user
,
title
=
_
(
u"
%(name)
s's profile"
,
name
=
current_user
.
nickname
),
page
=
"me"
,
kobo_support
=
kobo_support
,
registered_oauth
=
local_oauth_check
,
oauth_status
=
oauth_status
)
current_user
.
email
=
to_save
[
"email"
]
if
"nickname"
in
to_save
and
to_save
[
"nickname"
]
!=
current_user
.
nickname
:
# Query User nickname, if not existing, change
if
not
ub
.
session
.
query
(
ub
.
User
)
.
filter
(
ub
.
User
.
nickname
==
to_save
[
"nickname"
])
.
scalar
():
current_user
.
nickname
=
to_save
[
"nickname"
]
else
:
flash
(
_
(
u"This username is already taken"
),
category
=
"error"
)
return
render_title_template
(
"user_edit.html"
,
translations
=
translations
,
languages
=
languages
,
kobo_support
=
kobo_support
,
new_user
=
0
,
content
=
current_user
,
registered_oauth
=
local_oauth_check
,
title
=
_
(
u"Edit User
%(nick)
s"
,
nick
=
current_user
.
nickname
),
page
=
"edituser"
)
if
"show_random"
in
to_save
and
to_save
[
"show_random"
]
==
"on"
:
current_user
.
random_books
=
1
if
"default_language"
in
to_save
:
current_user
.
default_language
=
to_save
[
"default_language"
]
if
"locale"
in
to_save
:
current_user
.
locale
=
to_save
[
"locale"
]
val
=
0
for
key
,
__
in
to_save
.
items
():
if
key
.
startswith
(
'show'
):
val
+=
int
(
key
[
5
:])
current_user
.
sidebar_view
=
val
if
"Show_detail_random"
in
to_save
:
current_user
.
sidebar_view
+=
constants
.
DETAIL_RANDOM
try
:
ub
.
session
.
commit
()
flash
(
_
(
u"Profile updated"
),
category
=
"success"
)
log
.
debug
(
u"Profile updated"
)
except
IntegrityError
:
ub
.
session
.
rollback
()
flash
(
_
(
u"Found an existing account for this e-mail address."
),
category
=
"error"
)
log
.
debug
(
u"Found an existing account for this e-mail address."
)
except
OperationalError
as
e
:
ub
.
session
.
rollback
()
log
.
error
(
"Database error:
%
s"
,
e
)
flash
(
_
(
u"Database error:
%(error)
s."
,
error
=
e
),
category
=
"error"
)
@
web
.
route
(
"/me"
,
methods
=
[
"GET"
,
"POST"
])
@
login_required
def
profile
():
# downloads = list()
languages
=
calibre_db
.
speaking_language
()
translations
=
babel
.
list_translations
()
+
[
LC
(
'en'
)]
kobo_support
=
feature_support
[
'kobo'
]
and
config
.
config_kobo_sync
...
...
@@ -1427,74 +1484,8 @@ def profile():
oauth_status
=
None
local_oauth_check
=
{}
'''entries, __, pagination = calibre_db.fill_indexpage(page,
0,
db.Books,
ub.Downloads.user_id == int(current_user.id), # True,
[],
ub.Downloads, db.Books.id == ub.Downloads.book_id)'''
if
request
.
method
==
"POST"
:
to_save
=
request
.
form
.
to_dict
()
current_user
.
random_books
=
0
if
current_user
.
role_passwd
()
or
current_user
.
role_admin
():
if
"password"
in
to_save
and
to_save
[
"password"
]:
current_user
.
password
=
generate_password_hash
(
to_save
[
"password"
])
if
"kindle_mail"
in
to_save
and
to_save
[
"kindle_mail"
]
!=
current_user
.
kindle_mail
:
current_user
.
kindle_mail
=
to_save
[
"kindle_mail"
]
if
"allowed_tags"
in
to_save
and
to_save
[
"allowed_tags"
]
!=
current_user
.
allowed_tags
:
current_user
.
allowed_tags
=
to_save
[
"allowed_tags"
]
.
strip
()
if
"email"
in
to_save
and
to_save
[
"email"
]
!=
current_user
.
email
:
if
config
.
config_public_reg
and
not
check_valid_domain
(
to_save
[
"email"
]):
flash
(
_
(
u"E-mail is not from valid domain"
),
category
=
"error"
)
return
render_title_template
(
"user_edit.html"
,
content
=
current_user
,
title
=
_
(
u"
%(name)
s's profile"
,
name
=
current_user
.
nickname
),
page
=
"me"
,
kobo_support
=
kobo_support
,
registered_oauth
=
local_oauth_check
,
oauth_status
=
oauth_status
)
current_user
.
email
=
to_save
[
"email"
]
if
"nickname"
in
to_save
and
to_save
[
"nickname"
]
!=
current_user
.
nickname
:
# Query User nickname, if not existing, change
if
not
ub
.
session
.
query
(
ub
.
User
)
.
filter
(
ub
.
User
.
nickname
==
to_save
[
"nickname"
])
.
scalar
():
current_user
.
nickname
=
to_save
[
"nickname"
]
else
:
flash
(
_
(
u"This username is already taken"
),
category
=
"error"
)
return
render_title_template
(
"user_edit.html"
,
translations
=
translations
,
languages
=
languages
,
kobo_support
=
kobo_support
,
new_user
=
0
,
content
=
current_user
,
registered_oauth
=
local_oauth_check
,
title
=
_
(
u"Edit User
%(nick)
s"
,
nick
=
current_user
.
nickname
),
page
=
"edituser"
)
if
"show_random"
in
to_save
and
to_save
[
"show_random"
]
==
"on"
:
current_user
.
random_books
=
1
if
"default_language"
in
to_save
:
current_user
.
default_language
=
to_save
[
"default_language"
]
if
"locale"
in
to_save
:
current_user
.
locale
=
to_save
[
"locale"
]
val
=
0
for
key
,
__
in
to_save
.
items
():
if
key
.
startswith
(
'show'
):
val
+=
int
(
key
[
5
:])
current_user
.
sidebar_view
=
val
if
"Show_detail_random"
in
to_save
:
current_user
.
sidebar_view
+=
constants
.
DETAIL_RANDOM
try
:
ub
.
session
.
commit
()
flash
(
_
(
u"Profile updated"
),
category
=
"success"
)
log
.
debug
(
u"Profile updated"
)
except
IntegrityError
:
ub
.
session
.
rollback
()
flash
(
_
(
u"Found an existing account for this e-mail address."
),
category
=
"error"
)
log
.
debug
(
u"Found an existing account for this e-mail address."
)
except
OperationalError
as
e
:
ub
.
session
.
rollback
()
log
.
error
(
"Database error:
%
s"
,
e
)
flash
(
_
(
u"Database error:
%(error)
s."
,
error
=
e
),
category
=
"error"
)
change_profile
(
kobo_support
,
local_oauth_check
,
oauth_status
,
translations
,
languages
)
return
render_title_template
(
"user_edit.html"
,
translations
=
translations
,
profile
=
1
,
...
...
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