mirror of
https://github.com/janeczku/calibre-web
synced 2024-12-26 01:50:31 +00:00
Code cosmetics
This commit is contained in:
parent
7bb5afa585
commit
24c743d23d
@ -25,7 +25,7 @@ import ast
|
|||||||
|
|
||||||
from sqlalchemy import create_engine
|
from sqlalchemy import create_engine
|
||||||
from sqlalchemy import Table, Column, ForeignKey
|
from sqlalchemy import Table, Column, ForeignKey
|
||||||
from sqlalchemy import String, Integer, Boolean, TIMESTAMP, Float
|
from sqlalchemy import String, Integer, Boolean, TIMESTAMP, Float, DateTime
|
||||||
from sqlalchemy.orm import relationship, sessionmaker, scoped_session
|
from sqlalchemy.orm import relationship, sessionmaker, scoped_session
|
||||||
from sqlalchemy.ext.declarative import declarative_base
|
from sqlalchemy.ext.declarative import declarative_base
|
||||||
|
|
||||||
|
108
cps/helper.py
108
cps/helper.py
@ -141,36 +141,52 @@ def check_send_to_kindle(entry):
|
|||||||
returns all available book formats for sending to Kindle
|
returns all available book formats for sending to Kindle
|
||||||
"""
|
"""
|
||||||
if len(entry.data):
|
if len(entry.data):
|
||||||
bookformats=list()
|
bookformats = list()
|
||||||
if config.config_ebookconverter == 0:
|
if config.config_ebookconverter == 0:
|
||||||
# no converter - only for mobi and pdf formats
|
# no converter - only for mobi and pdf formats
|
||||||
for ele in iter(entry.data):
|
for ele in iter(entry.data):
|
||||||
if 'MOBI' in ele.format:
|
if 'MOBI' in ele.format:
|
||||||
bookformats.append({'format':'Mobi','convert':0,'text':_('Send %(format)s to Kindle',format='Mobi')})
|
bookformats.append({'format': 'Mobi',
|
||||||
|
'convert': 0,
|
||||||
|
'text': _('Send %(format)s to Kindle', format='Mobi')})
|
||||||
if 'PDF' in ele.format:
|
if 'PDF' in ele.format:
|
||||||
bookformats.append({'format':'Pdf','convert':0,'text':_('Send %(format)s to Kindle',format='Pdf')})
|
bookformats.append({'format': 'Pdf',
|
||||||
|
'convert': 0,
|
||||||
|
'text': _('Send %(format)s to Kindle', format='Pdf')})
|
||||||
if 'AZW' in ele.format:
|
if 'AZW' in ele.format:
|
||||||
bookformats.append({'format':'Azw','convert':0,'text':_('Send %(format)s to Kindle',format='Azw')})
|
bookformats.append({'format': 'Azw',
|
||||||
'''if 'AZW3' in ele.format:
|
'convert': 0,
|
||||||
bookformats.append({'format':'Azw3','convert':0,'text':_('Send %(format)s to Kindle',format='Azw3')})'''
|
'text': _('Send %(format)s to Kindle', format='Azw')})
|
||||||
else:
|
else:
|
||||||
formats = list()
|
formats = list()
|
||||||
for ele in iter(entry.data):
|
for ele in iter(entry.data):
|
||||||
formats.append(ele.format)
|
formats.append(ele.format)
|
||||||
if 'MOBI' in formats:
|
if 'MOBI' in formats:
|
||||||
bookformats.append({'format': 'Mobi','convert':0,'text':_('Send %(format)s to Kindle',format='Mobi')})
|
bookformats.append({'format': 'Mobi',
|
||||||
|
'convert': 0,
|
||||||
|
'text': _('Send %(format)s to Kindle', format='Mobi')})
|
||||||
if 'AZW' in formats:
|
if 'AZW' in formats:
|
||||||
bookformats.append({'format': 'Azw','convert':0,'text':_('Send %(format)s to Kindle',format='Azw')})
|
bookformats.append({'format': 'Azw',
|
||||||
|
'convert': 0,
|
||||||
|
'text': _('Send %(format)s to Kindle', format='Azw')})
|
||||||
if 'PDF' in formats:
|
if 'PDF' in formats:
|
||||||
bookformats.append({'format': 'Pdf','convert':0,'text':_('Send %(format)s to Kindle',format='Pdf')})
|
bookformats.append({'format': 'Pdf',
|
||||||
|
'convert': 0,
|
||||||
|
'text': _('Send %(format)s to Kindle', format='Pdf')})
|
||||||
if config.config_ebookconverter >= 1:
|
if config.config_ebookconverter >= 1:
|
||||||
if 'EPUB' in formats and not 'MOBI' in formats:
|
if 'EPUB' in formats and not 'MOBI' in formats:
|
||||||
bookformats.append({'format': 'Mobi','convert':1,
|
bookformats.append({'format': 'Mobi',
|
||||||
'text':_('Convert %(orig)s to %(format)s and send to Kindle',orig='Epub',format='Mobi')})
|
'convert':1,
|
||||||
|
'text': _('Convert %(orig)s to %(format)s and send to Kindle',
|
||||||
|
orig='Epub',
|
||||||
|
format='Mobi')})
|
||||||
if config.config_ebookconverter == 2:
|
if config.config_ebookconverter == 2:
|
||||||
if 'AZW3' in formats and not 'MOBI' in formats:
|
if 'AZW3' in formats and not 'MOBI' in formats:
|
||||||
bookformats.append({'format': 'Mobi','convert':2,
|
bookformats.append({'format': 'Mobi',
|
||||||
'text':_('Convert %(orig)s to %(format)s and send to Kindle',orig='Azw3',format='Mobi')})
|
'convert': 2,
|
||||||
|
'text': _('Convert %(orig)s to %(format)s and send to Kindle',
|
||||||
|
orig='Azw3',
|
||||||
|
format='Mobi')})
|
||||||
return bookformats
|
return bookformats
|
||||||
else:
|
else:
|
||||||
log.error(u'Cannot find book entry %d', entry.id)
|
log.error(u'Cannot find book entry %d', entry.id)
|
||||||
@ -204,7 +220,6 @@ def send_mail(book_id, book_format, convert, kindle_mail, calibrepath, user_id):
|
|||||||
# returns None if success, otherwise errormessage
|
# returns None if success, otherwise errormessage
|
||||||
return convert_book_format(book_id, calibrepath, u'azw3', book_format.lower(), user_id, kindle_mail)
|
return convert_book_format(book_id, calibrepath, u'azw3', book_format.lower(), user_id, kindle_mail)
|
||||||
|
|
||||||
|
|
||||||
for entry in iter(book.data):
|
for entry in iter(book.data):
|
||||||
if entry.format.upper() == book_format.upper():
|
if entry.format.upper() == book_format.upper():
|
||||||
converted_file_name = entry.name + '.' + book_format.lower()
|
converted_file_name = entry.name + '.' + book_format.lower()
|
||||||
@ -395,7 +410,7 @@ def update_dir_structure_gdrive(book_id, first_author):
|
|||||||
|
|
||||||
|
|
||||||
def delete_book_gdrive(book, book_format):
|
def delete_book_gdrive(book, book_format):
|
||||||
error= False
|
error = False
|
||||||
if book_format:
|
if book_format:
|
||||||
name = ''
|
name = ''
|
||||||
for entry in book.data:
|
for entry in book.data:
|
||||||
@ -403,12 +418,12 @@ def delete_book_gdrive(book, book_format):
|
|||||||
name = entry.name + '.' + book_format
|
name = entry.name + '.' + book_format
|
||||||
gFile = gd.getFileFromEbooksFolder(book.path, name)
|
gFile = gd.getFileFromEbooksFolder(book.path, name)
|
||||||
else:
|
else:
|
||||||
gFile = gd.getFileFromEbooksFolder(os.path.dirname(book.path),book.path.split('/')[1])
|
gFile = gd.getFileFromEbooksFolder(os.path.dirname(book.path), book.path.split('/')[1])
|
||||||
if gFile:
|
if gFile:
|
||||||
gd.deleteDatabaseEntry(gFile['id'])
|
gd.deleteDatabaseEntry(gFile['id'])
|
||||||
gFile.Trash()
|
gFile.Trash()
|
||||||
else:
|
else:
|
||||||
error =_(u'Book path %(path)s not found on Google Drive', path=book.path) # file not found
|
error = _(u'Book path %(path)s not found on Google Drive', path=book.path) # file not found
|
||||||
return error
|
return error
|
||||||
|
|
||||||
|
|
||||||
@ -417,24 +432,25 @@ def reset_password(user_id):
|
|||||||
password = generate_random_password()
|
password = generate_random_password()
|
||||||
existing_user.password = generate_password_hash(password)
|
existing_user.password = generate_password_hash(password)
|
||||||
if not config.get_mail_server_configured():
|
if not config.get_mail_server_configured():
|
||||||
return (2, None)
|
return 2, None
|
||||||
try:
|
try:
|
||||||
ub.session.commit()
|
ub.session.commit()
|
||||||
send_registration_mail(existing_user.email, existing_user.nickname, password, True)
|
send_registration_mail(existing_user.email, existing_user.nickname, password, True)
|
||||||
return (1, existing_user.nickname)
|
return 1, existing_user.nickname
|
||||||
except Exception:
|
except Exception:
|
||||||
ub.session.rollback()
|
ub.session.rollback()
|
||||||
return (0, None)
|
return 0, None
|
||||||
|
|
||||||
|
|
||||||
def generate_random_password():
|
def generate_random_password():
|
||||||
s = "abcdefghijklmnopqrstuvwxyz01234567890ABCDEFGHIJKLMNOPQRSTUVWXYZ!@#$%&*()?"
|
s = "abcdefghijklmnopqrstuvwxyz01234567890ABCDEFGHIJKLMNOPQRSTUVWXYZ!@#$%&*()?"
|
||||||
passlen = 8
|
passlen = 8
|
||||||
return "".join(random.sample(s,passlen ))
|
return "".join(random.sample(s, passlen))
|
||||||
|
|
||||||
################################## External interface
|
################################## External interface
|
||||||
|
|
||||||
def update_dir_stucture(book_id, calibrepath, first_author = None):
|
|
||||||
|
def update_dir_stucture(book_id, calibrepath, first_author=None):
|
||||||
if config.config_use_google_drive:
|
if config.config_use_google_drive:
|
||||||
return update_dir_structure_gdrive(book_id, first_author)
|
return update_dir_structure_gdrive(book_id, first_author)
|
||||||
else:
|
else:
|
||||||
@ -454,15 +470,18 @@ def get_cover_on_failure(use_generic_cover):
|
|||||||
else:
|
else:
|
||||||
return None
|
return None
|
||||||
|
|
||||||
|
|
||||||
def get_book_cover(book_id):
|
def get_book_cover(book_id):
|
||||||
book = db.session.query(db.Books).filter(db.Books.id == book_id).filter(common_filters()).first()
|
book = db.session.query(db.Books).filter(db.Books.id == book_id).filter(common_filters()).first()
|
||||||
return get_book_cover_internal(book, use_generic_cover_on_failure=True)
|
return get_book_cover_internal(book, use_generic_cover_on_failure=True)
|
||||||
|
|
||||||
|
|
||||||
def get_book_cover_with_uuid(book_uuid,
|
def get_book_cover_with_uuid(book_uuid,
|
||||||
use_generic_cover_on_failure=True):
|
use_generic_cover_on_failure=True):
|
||||||
book = db.session.query(db.Books).filter(db.Books.uuid == book_uuid).first()
|
book = db.session.query(db.Books).filter(db.Books.uuid == book_uuid).first()
|
||||||
return get_book_cover_internal(book, use_generic_cover_on_failure)
|
return get_book_cover_internal(book, use_generic_cover_on_failure)
|
||||||
|
|
||||||
|
|
||||||
def get_book_cover_internal(book,
|
def get_book_cover_internal(book,
|
||||||
use_generic_cover_on_failure):
|
use_generic_cover_on_failure):
|
||||||
if book and book.has_cover:
|
if book and book.has_cover:
|
||||||
@ -470,7 +489,7 @@ def get_book_cover_internal(book,
|
|||||||
try:
|
try:
|
||||||
if not gd.is_gdrive_ready():
|
if not gd.is_gdrive_ready():
|
||||||
return get_cover_on_failure(use_generic_cover_on_failure)
|
return get_cover_on_failure(use_generic_cover_on_failure)
|
||||||
path=gd.get_cover_via_gdrive(book.path)
|
path = gd.get_cover_via_gdrive(book.path)
|
||||||
if path:
|
if path:
|
||||||
return redirect(path)
|
return redirect(path)
|
||||||
else:
|
else:
|
||||||
@ -530,7 +549,7 @@ def save_cover(img, book_path):
|
|||||||
return False, _("Only jpg/jpeg/png/webp files are supported as coverfile")
|
return False, _("Only jpg/jpeg/png/webp files are supported as coverfile")
|
||||||
# convert to jpg because calibre only supports jpg
|
# convert to jpg because calibre only supports jpg
|
||||||
if content_type in ('image/png', 'image/webp'):
|
if content_type in ('image/png', 'image/webp'):
|
||||||
if hasattr(img,'stream'):
|
if hasattr(img, 'stream'):
|
||||||
imgc = PILImage.open(img.stream)
|
imgc = PILImage.open(img.stream)
|
||||||
else:
|
else:
|
||||||
imgc = PILImage.open(io.BytesIO(img.content))
|
imgc = PILImage.open(io.BytesIO(img.content))
|
||||||
@ -539,7 +558,7 @@ def save_cover(img, book_path):
|
|||||||
im.save(tmp_bytesio, format='JPEG')
|
im.save(tmp_bytesio, format='JPEG')
|
||||||
img._content = tmp_bytesio.getvalue()
|
img._content = tmp_bytesio.getvalue()
|
||||||
else:
|
else:
|
||||||
if content_type not in ('image/jpeg'):
|
if content_type not in 'image/jpeg':
|
||||||
log.error("Only jpg/jpeg files are supported as coverfile")
|
log.error("Only jpg/jpeg files are supported as coverfile")
|
||||||
return False, _("Only jpg/jpeg files are supported as coverfile")
|
return False, _("Only jpg/jpeg files are supported as coverfile")
|
||||||
|
|
||||||
@ -557,7 +576,6 @@ def save_cover(img, book_path):
|
|||||||
return save_cover_from_filestorage(os.path.join(config.config_calibre_dir, book_path), "cover.jpg", img)
|
return save_cover_from_filestorage(os.path.join(config.config_calibre_dir, book_path), "cover.jpg", img)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
def do_download_file(book, book_format, data, headers):
|
def do_download_file(book, book_format, data, headers):
|
||||||
if config.config_use_google_drive:
|
if config.config_use_google_drive:
|
||||||
startTime = time.time()
|
startTime = time.time()
|
||||||
@ -579,7 +597,6 @@ def do_download_file(book, book_format, data, headers):
|
|||||||
##################################
|
##################################
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
def check_unrar(unrarLocation):
|
def check_unrar(unrarLocation):
|
||||||
if not unrarLocation:
|
if not unrarLocation:
|
||||||
return
|
return
|
||||||
@ -601,13 +618,12 @@ def check_unrar(unrarLocation):
|
|||||||
return 'Error excecuting UnRar'
|
return 'Error excecuting UnRar'
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
def json_serial(obj):
|
def json_serial(obj):
|
||||||
"""JSON serializer for objects not serializable by default json code"""
|
"""JSON serializer for objects not serializable by default json code"""
|
||||||
|
|
||||||
if isinstance(obj, (datetime)):
|
if isinstance(obj, datetime):
|
||||||
return obj.isoformat()
|
return obj.isoformat()
|
||||||
if isinstance(obj, (timedelta)):
|
if isinstance(obj, timedelta):
|
||||||
return {
|
return {
|
||||||
'__type__': 'timedelta',
|
'__type__': 'timedelta',
|
||||||
'days': obj.days,
|
'days': obj.days,
|
||||||
@ -615,7 +631,7 @@ def json_serial(obj):
|
|||||||
'microseconds': obj.microseconds,
|
'microseconds': obj.microseconds,
|
||||||
}
|
}
|
||||||
# return obj.isoformat()
|
# return obj.isoformat()
|
||||||
raise TypeError ("Type %s not serializable" % type(obj))
|
raise TypeError("Type %s not serializable" % type(obj))
|
||||||
|
|
||||||
|
|
||||||
# helper function for displaying the runtime of tasks
|
# helper function for displaying the runtime of tasks
|
||||||
@ -637,7 +653,7 @@ def format_runtime(runtime):
|
|||||||
|
|
||||||
# helper function to apply localize status information in tasklist entries
|
# helper function to apply localize status information in tasklist entries
|
||||||
def render_task_status(tasklist):
|
def render_task_status(tasklist):
|
||||||
renderedtasklist=list()
|
renderedtasklist = list()
|
||||||
for task in tasklist:
|
for task in tasklist:
|
||||||
if task['user'] == current_user.nickname or current_user.role_admin():
|
if task['user'] == current_user.nickname or current_user.role_admin():
|
||||||
if task['formStarttime']:
|
if task['formStarttime']:
|
||||||
@ -653,7 +669,7 @@ def render_task_status(tasklist):
|
|||||||
task['runtime'] = format_runtime(task['formRuntime'])
|
task['runtime'] = format_runtime(task['formRuntime'])
|
||||||
|
|
||||||
# localize the task status
|
# localize the task status
|
||||||
if isinstance( task['stat'], int ):
|
if isinstance( task['stat'], int):
|
||||||
if task['stat'] == STAT_WAITING:
|
if task['stat'] == STAT_WAITING:
|
||||||
task['status'] = _(u'Waiting')
|
task['status'] = _(u'Waiting')
|
||||||
elif task['stat'] == STAT_FAIL:
|
elif task['stat'] == STAT_FAIL:
|
||||||
@ -666,7 +682,7 @@ def render_task_status(tasklist):
|
|||||||
task['status'] = _(u'Unknown Status')
|
task['status'] = _(u'Unknown Status')
|
||||||
|
|
||||||
# localize the task type
|
# localize the task type
|
||||||
if isinstance( task['taskType'], int ):
|
if isinstance( task['taskType'], int):
|
||||||
if task['taskType'] == TASK_EMAIL:
|
if task['taskType'] == TASK_EMAIL:
|
||||||
task['taskMessage'] = _(u'E-mail: ') + task['taskMess']
|
task['taskMessage'] = _(u'E-mail: ') + task['taskMess']
|
||||||
elif task['taskType'] == TASK_CONVERT:
|
elif task['taskType'] == TASK_CONVERT:
|
||||||
@ -733,7 +749,8 @@ def tags_filters():
|
|||||||
# Creates for all stored languages a translated speaking name in the array for the UI
|
# Creates for all stored languages a translated speaking name in the array for the UI
|
||||||
def speaking_language(languages=None):
|
def speaking_language(languages=None):
|
||||||
if not languages:
|
if not languages:
|
||||||
languages = db.session.query(db.Languages).join(db.books_languages_link).join(db.Books).filter(common_filters())\
|
languages = db.session.query(db.Languages).join(db.books_languages_link).join(db.Books)\
|
||||||
|
.filter(common_filters())\
|
||||||
.group_by(text('books_languages_link.lang_code')).all()
|
.group_by(text('books_languages_link.lang_code')).all()
|
||||||
for lang in languages:
|
for lang in languages:
|
||||||
try:
|
try:
|
||||||
@ -743,6 +760,7 @@ def speaking_language(languages=None):
|
|||||||
lang.name = _(isoLanguages.get(part3=lang.lang_code).name)
|
lang.name = _(isoLanguages.get(part3=lang.lang_code).name)
|
||||||
return languages
|
return languages
|
||||||
|
|
||||||
|
|
||||||
# checks if domain is in database (including wildcards)
|
# checks if domain is in database (including wildcards)
|
||||||
# example SELECT * FROM @TABLE WHERE 'abcdefg' LIKE Name;
|
# example SELECT * FROM @TABLE WHERE 'abcdefg' LIKE Name;
|
||||||
# from https://code.luasoftware.com/tutorials/flask/execute-raw-sql-in-flask-sqlalchemy/
|
# from https://code.luasoftware.com/tutorials/flask/execute-raw-sql-in-flask-sqlalchemy/
|
||||||
@ -787,21 +805,25 @@ def fill_indexpage_with_archived_books(page, database, db_filter, order, allow_s
|
|||||||
randm = false()
|
randm = false()
|
||||||
off = int(int(config.config_books_per_page) * (page - 1))
|
off = int(int(config.config_books_per_page) * (page - 1))
|
||||||
pagination = Pagination(page, config.config_books_per_page,
|
pagination = Pagination(page, config.config_books_per_page,
|
||||||
len(db.session.query(database).filter(db_filter).filter(common_filters(allow_show_archived)).all()))
|
len(db.session.query(database).filter(db_filter)
|
||||||
entries = db.session.query(database).join(*join, isouter=True).filter(db_filter).filter(common_filters(allow_show_archived)).\
|
.filter(common_filters(allow_show_archived)).all()))
|
||||||
order_by(*order).offset(off).limit(config.config_books_per_page).all()
|
entries = db.session.query(database).join(*join, isouter=True).filter(db_filter)\
|
||||||
|
.filter(common_filters(allow_show_archived))\
|
||||||
|
.order_by(*order).offset(off).limit(config.config_books_per_page).all()
|
||||||
for book in entries:
|
for book in entries:
|
||||||
book = order_authors(book)
|
book = order_authors(book)
|
||||||
return entries, randm, pagination
|
return entries, randm, pagination
|
||||||
|
|
||||||
|
|
||||||
def get_typeahead(database, query, replace=('',''), tag_filter=true()):
|
def get_typeahead(database, query, replace=('', ''), tag_filter=true()):
|
||||||
query = query or ''
|
query = query or ''
|
||||||
db.session.connection().connection.connection.create_function("lower", 1, lcase)
|
db.session.connection().connection.connection.create_function("lower", 1, lcase)
|
||||||
entries = db.session.query(database).filter(tag_filter).filter(func.lower(database.name).ilike("%" + query + "%")).all()
|
entries = db.session.query(database).filter(tag_filter).\
|
||||||
|
filter(func.lower(database.name).ilike("%" + query + "%")).all()
|
||||||
json_dumps = json.dumps([dict(name=r.name.replace(*replace)) for r in entries])
|
json_dumps = json.dumps([dict(name=r.name.replace(*replace)) for r in entries])
|
||||||
return json_dumps
|
return json_dumps
|
||||||
|
|
||||||
|
|
||||||
# read search results from calibre-database and return it (function is used for feed and simple search
|
# read search results from calibre-database and return it (function is used for feed and simple search
|
||||||
def get_search_results(term):
|
def get_search_results(term):
|
||||||
db.session.connection().connection.connection.create_function("lower", 1, lcase)
|
db.session.connection().connection.connection.create_function("lower", 1, lcase)
|
||||||
@ -820,6 +842,7 @@ def get_search_results(term):
|
|||||||
func.lower(db.Books.title).ilike("%" + term + "%")
|
func.lower(db.Books.title).ilike("%" + term + "%")
|
||||||
)).all()
|
)).all()
|
||||||
|
|
||||||
|
|
||||||
def get_cc_columns():
|
def get_cc_columns():
|
||||||
tmpcc = db.session.query(db.Custom_Columns).filter(db.Custom_Columns.datatype.notin_(db.cc_exceptions)).all()
|
tmpcc = db.session.query(db.Custom_Columns).filter(db.Custom_Columns.datatype.notin_(db.cc_exceptions)).all()
|
||||||
if config.config_columns_to_ignore:
|
if config.config_columns_to_ignore:
|
||||||
@ -832,6 +855,7 @@ def get_cc_columns():
|
|||||||
cc = tmpcc
|
cc = tmpcc
|
||||||
return cc
|
return cc
|
||||||
|
|
||||||
|
|
||||||
def get_download_link(book_id, book_format):
|
def get_download_link(book_id, book_format):
|
||||||
book_format = book_format.split(".")[0]
|
book_format = book_format.split(".")[0]
|
||||||
book = db.session.query(db.Books).filter(db.Books.id == book_id).filter(common_filters()).first()
|
book = db.session.query(db.Books).filter(db.Books.id == book_id).filter(common_filters()).first()
|
||||||
@ -856,7 +880,8 @@ def get_download_link(book_id, book_format):
|
|||||||
else:
|
else:
|
||||||
abort(404)
|
abort(404)
|
||||||
|
|
||||||
def check_exists_book(authr,title):
|
|
||||||
|
def check_exists_book(authr, title):
|
||||||
db.session.connection().connection.connection.create_function("lower", 1, lcase)
|
db.session.connection().connection.connection.create_function("lower", 1, lcase)
|
||||||
q = list()
|
q = list()
|
||||||
authorterms = re.split(r'\s*&\s*', authr)
|
authorterms = re.split(r'\s*&\s*', authr)
|
||||||
@ -870,6 +895,7 @@ def check_exists_book(authr,title):
|
|||||||
|
|
||||||
############### Database Helper functions
|
############### Database Helper functions
|
||||||
|
|
||||||
|
|
||||||
def lcase(s):
|
def lcase(s):
|
||||||
try:
|
try:
|
||||||
return unidecode.unidecode(s.lower())
|
return unidecode.unidecode(s.lower())
|
||||||
|
@ -80,9 +80,13 @@ def formatdate_filter(val):
|
|||||||
formatdate = datetime.datetime.strptime(conformed_timestamp[:15], "%Y%m%d %H%M%S")
|
formatdate = datetime.datetime.strptime(conformed_timestamp[:15], "%Y%m%d %H%M%S")
|
||||||
return format_date(formatdate, format='medium', locale=get_locale())
|
return format_date(formatdate, format='medium', locale=get_locale())
|
||||||
except AttributeError as e:
|
except AttributeError as e:
|
||||||
log.error('Babel error: %s, Current user locale: %s, Current User: %s', e, current_user.locale, current_user.nickname)
|
log.error('Babel error: %s, Current user locale: %s, Current User: %s', e,
|
||||||
|
current_user.locale,
|
||||||
|
current_user.nickname
|
||||||
|
)
|
||||||
return formatdate
|
return formatdate
|
||||||
|
|
||||||
|
|
||||||
@jinjia.app_template_filter('formatdateinput')
|
@jinjia.app_template_filter('formatdateinput')
|
||||||
def format_date_input(val):
|
def format_date_input(val):
|
||||||
conformed_timestamp = re.sub(r"[:]|([-](?!((\d{2}[:]\d{2})|(\d{4}))$))", '', val)
|
conformed_timestamp = re.sub(r"[:]|([-](?!((\d{2}[:]\d{2})|(\d{4}))$))", '', val)
|
||||||
|
17
cps/kobo.py
17
cps/kobo.py
@ -385,7 +385,7 @@ def get_metadata(book):
|
|||||||
name = get_series(book)
|
name = get_series(book)
|
||||||
metadata["Series"] = {
|
metadata["Series"] = {
|
||||||
"Name": get_series(book),
|
"Name": get_series(book),
|
||||||
"Number": book.series_index,
|
"Number": book.series_index, # ToDo Check int() ?
|
||||||
"NumberFloat": float(book.series_index),
|
"NumberFloat": float(book.series_index),
|
||||||
# Get a deterministic id based on the series name.
|
# Get a deterministic id based on the series name.
|
||||||
"Id": uuid.uuid3(uuid.NAMESPACE_DNS, name),
|
"Id": uuid.uuid3(uuid.NAMESPACE_DNS, name),
|
||||||
@ -407,8 +407,10 @@ def HandleTagCreate():
|
|||||||
log.debug("Received malformed v1/library/tags request.")
|
log.debug("Received malformed v1/library/tags request.")
|
||||||
abort(400, description="Malformed tags POST request. Data is missing 'Name' or 'Items' field")
|
abort(400, description="Malformed tags POST request. Data is missing 'Name' or 'Items' field")
|
||||||
|
|
||||||
|
# ToDO: Names are not unique ! -> filter only private shelfs
|
||||||
shelf = ub.session.query(ub.Shelf).filter(and_(ub.Shelf.name) == name, ub.Shelf.user_id ==
|
shelf = ub.session.query(ub.Shelf).filter(and_(ub.Shelf.name) == name, ub.Shelf.user_id ==
|
||||||
current_user.id).one_or_none()
|
current_user.id).one_or_none() # ToDO: shouldn't it ) at the end
|
||||||
|
|
||||||
if shelf and not shelf_lib.check_shelf_edit_permissions(shelf):
|
if shelf and not shelf_lib.check_shelf_edit_permissions(shelf):
|
||||||
abort(401, description="User is unauthaurized to edit shelf.")
|
abort(401, description="User is unauthaurized to edit shelf.")
|
||||||
|
|
||||||
@ -517,6 +519,7 @@ def HandleTagRemoveItem(tag_id):
|
|||||||
log.debug("Received malformed v1/library/tags/<tag_id>/items/delete request.")
|
log.debug("Received malformed v1/library/tags/<tag_id>/items/delete request.")
|
||||||
abort(400, description="Malformed tags POST request. Data is missing 'Items' field")
|
abort(400, description="Malformed tags POST request. Data is missing 'Items' field")
|
||||||
|
|
||||||
|
# insconsitent to above requests
|
||||||
shelf = ub.session.query(ub.Shelf).filter(ub.Shelf.uuid == tag_id,
|
shelf = ub.session.query(ub.Shelf).filter(ub.Shelf.uuid == tag_id,
|
||||||
ub.Shelf.user_id == current_user.id).one_or_none()
|
ub.Shelf.user_id == current_user.id).one_or_none()
|
||||||
if not shelf:
|
if not shelf:
|
||||||
@ -552,7 +555,8 @@ def HandleTagRemoveItem(tag_id):
|
|||||||
def sync_shelves(sync_token, sync_results):
|
def sync_shelves(sync_token, sync_results):
|
||||||
new_tags_last_modified = sync_token.tags_last_modified
|
new_tags_last_modified = sync_token.tags_last_modified
|
||||||
|
|
||||||
for shelf in ub.session.query(ub.ShelfArchive).filter(func.datetime(ub.ShelfArchive.last_modified) > sync_token.tags_last_modified, ub.ShelfArchive.user_id == current_user.id):
|
for shelf in ub.session.query(ub.ShelfArchive).filter(func.datetime(ub.ShelfArchive.last_modified) > sync_token.tags_last_modified,
|
||||||
|
ub.ShelfArchive.user_id == current_user.id):
|
||||||
new_tags_last_modified = max(shelf.last_modified, new_tags_last_modified)
|
new_tags_last_modified = max(shelf.last_modified, new_tags_last_modified)
|
||||||
|
|
||||||
sync_results.append({
|
sync_results.append({
|
||||||
@ -564,7 +568,8 @@ def sync_shelves(sync_token, sync_results):
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
for shelf in ub.session.query(ub.Shelf).filter(func.datetime(ub.Shelf.last_modified) > sync_token.tags_last_modified, ub.Shelf.user_id == current_user.id):
|
for shelf in ub.session.query(ub.Shelf).filter(func.datetime(ub.Shelf.last_modified) > sync_token.tags_last_modified,
|
||||||
|
ub.Shelf.user_id == current_user.id):
|
||||||
if not shelf_lib.check_shelf_view_permissions(shelf):
|
if not shelf_lib.check_shelf_view_permissions(shelf):
|
||||||
continue
|
continue
|
||||||
|
|
||||||
@ -600,6 +605,7 @@ def create_kobo_tag(shelf):
|
|||||||
book = db.session.query(db.Books).filter(db.Books.id == book_shelf.book_id).one_or_none()
|
book = db.session.query(db.Books).filter(db.Books.id == book_shelf.book_id).one_or_none()
|
||||||
if not book:
|
if not book:
|
||||||
log.info(u"Book (id: %s) in BookShelf (id: %s) not found in book database", book_shelf.book_id, shelf.id)
|
log.info(u"Book (id: %s) in BookShelf (id: %s) not found in book database", book_shelf.book_id, shelf.id)
|
||||||
|
# ToDo shouldn't it continue?
|
||||||
return None
|
return None
|
||||||
tag["Items"].append(
|
tag["Items"].append(
|
||||||
{
|
{
|
||||||
@ -769,7 +775,8 @@ def HandleCoverImageRequest(book_uuid, width, height,Quality, isGreyscale):
|
|||||||
height=height), 307)
|
height=height), 307)
|
||||||
else:
|
else:
|
||||||
log.debug("Cover for unknown book: %s requested" % book_uuid)
|
log.debug("Cover for unknown book: %s requested" % book_uuid)
|
||||||
return redirect_or_proxy_request()
|
# additional proxy request make no sense, -> direct return
|
||||||
|
return make_response(jsonify({}))
|
||||||
log.debug("Cover request received for book %s" % book_uuid)
|
log.debug("Cover request received for book %s" % book_uuid)
|
||||||
return book_cover
|
return book_cover
|
||||||
|
|
||||||
|
60
cps/opds.py
60
cps/opds.py
@ -56,8 +56,8 @@ def requires_basic_auth_if_no_ano(f):
|
|||||||
return decorated
|
return decorated
|
||||||
|
|
||||||
|
|
||||||
class FeedObject():
|
class FeedObject:
|
||||||
def __init__(self,rating_id , rating_name):
|
def __init__(self, rating_id, rating_name):
|
||||||
self.rating_id = rating_id
|
self.rating_id = rating_id
|
||||||
self.rating_name = rating_name
|
self.rating_name = rating_name
|
||||||
|
|
||||||
@ -119,7 +119,8 @@ def feed_discover():
|
|||||||
def feed_best_rated():
|
def feed_best_rated():
|
||||||
off = request.args.get("offset") or 0
|
off = request.args.get("offset") or 0
|
||||||
entries, __, pagination = fill_indexpage((int(off) / (int(config.config_books_per_page)) + 1),
|
entries, __, pagination = fill_indexpage((int(off) / (int(config.config_books_per_page)) + 1),
|
||||||
db.Books, db.Books.ratings.any(db.Ratings.rating > 9), [db.Books.timestamp.desc()])
|
db.Books, db.Books.ratings.any(db.Ratings.rating > 9),
|
||||||
|
[db.Books.timestamp.desc()])
|
||||||
return render_xml_template('feed.xml', entries=entries, pagination=pagination)
|
return render_xml_template('feed.xml', entries=entries, pagination=pagination)
|
||||||
|
|
||||||
|
|
||||||
@ -153,7 +154,8 @@ def feed_hot():
|
|||||||
def feed_authorindex():
|
def feed_authorindex():
|
||||||
off = request.args.get("offset") or 0
|
off = request.args.get("offset") or 0
|
||||||
entries = db.session.query(db.Authors).join(db.books_authors_link).join(db.Books).filter(common_filters())\
|
entries = db.session.query(db.Authors).join(db.books_authors_link).join(db.Books).filter(common_filters())\
|
||||||
.group_by(text('books_authors_link.author')).order_by(db.Authors.sort).limit(config.config_books_per_page).offset(off)
|
.group_by(text('books_authors_link.author')).order_by(db.Authors.sort).limit(config.config_books_per_page)\
|
||||||
|
.offset(off)
|
||||||
pagination = Pagination((int(off) / (int(config.config_books_per_page)) + 1), config.config_books_per_page,
|
pagination = Pagination((int(off) / (int(config.config_books_per_page)) + 1), config.config_books_per_page,
|
||||||
len(db.session.query(db.Authors).all()))
|
len(db.session.query(db.Authors).all()))
|
||||||
return render_xml_template('feed.xml', listelements=entries, folder='opds.feed_author', pagination=pagination)
|
return render_xml_template('feed.xml', listelements=entries, folder='opds.feed_author', pagination=pagination)
|
||||||
@ -164,7 +166,9 @@ def feed_authorindex():
|
|||||||
def feed_author(book_id):
|
def feed_author(book_id):
|
||||||
off = request.args.get("offset") or 0
|
off = request.args.get("offset") or 0
|
||||||
entries, __, pagination = fill_indexpage((int(off) / (int(config.config_books_per_page)) + 1),
|
entries, __, pagination = fill_indexpage((int(off) / (int(config.config_books_per_page)) + 1),
|
||||||
db.Books, db.Books.authors.any(db.Authors.id == book_id), [db.Books.timestamp.desc()])
|
db.Books,
|
||||||
|
db.Books.authors.any(db.Authors.id == book_id),
|
||||||
|
[db.Books.timestamp.desc()])
|
||||||
return render_xml_template('feed.xml', entries=entries, pagination=pagination)
|
return render_xml_template('feed.xml', entries=entries, pagination=pagination)
|
||||||
|
|
||||||
|
|
||||||
@ -173,7 +177,8 @@ def feed_author(book_id):
|
|||||||
def feed_publisherindex():
|
def feed_publisherindex():
|
||||||
off = request.args.get("offset") or 0
|
off = request.args.get("offset") or 0
|
||||||
entries = db.session.query(db.Publishers).join(db.books_publishers_link).join(db.Books).filter(common_filters())\
|
entries = db.session.query(db.Publishers).join(db.books_publishers_link).join(db.Books).filter(common_filters())\
|
||||||
.group_by(text('books_publishers_link.publisher')).order_by(db.Publishers.sort).limit(config.config_books_per_page).offset(off)
|
.group_by(text('books_publishers_link.publisher')).order_by(db.Publishers.sort)\
|
||||||
|
.limit(config.config_books_per_page).offset(off)
|
||||||
pagination = Pagination((int(off) / (int(config.config_books_per_page)) + 1), config.config_books_per_page,
|
pagination = Pagination((int(off) / (int(config.config_books_per_page)) + 1), config.config_books_per_page,
|
||||||
len(db.session.query(db.Publishers).all()))
|
len(db.session.query(db.Publishers).all()))
|
||||||
return render_xml_template('feed.xml', listelements=entries, folder='opds.feed_publisher', pagination=pagination)
|
return render_xml_template('feed.xml', listelements=entries, folder='opds.feed_publisher', pagination=pagination)
|
||||||
@ -184,7 +189,8 @@ def feed_publisherindex():
|
|||||||
def feed_publisher(book_id):
|
def feed_publisher(book_id):
|
||||||
off = request.args.get("offset") or 0
|
off = request.args.get("offset") or 0
|
||||||
entries, __, pagination = fill_indexpage((int(off) / (int(config.config_books_per_page)) + 1),
|
entries, __, pagination = fill_indexpage((int(off) / (int(config.config_books_per_page)) + 1),
|
||||||
db.Books, db.Books.publishers.any(db.Publishers.id == book_id),
|
db.Books,
|
||||||
|
db.Books.publishers.any(db.Publishers.id == book_id),
|
||||||
[db.Books.timestamp.desc()])
|
[db.Books.timestamp.desc()])
|
||||||
return render_xml_template('feed.xml', entries=entries, pagination=pagination)
|
return render_xml_template('feed.xml', entries=entries, pagination=pagination)
|
||||||
|
|
||||||
@ -205,7 +211,9 @@ def feed_categoryindex():
|
|||||||
def feed_category(book_id):
|
def feed_category(book_id):
|
||||||
off = request.args.get("offset") or 0
|
off = request.args.get("offset") or 0
|
||||||
entries, __, pagination = fill_indexpage((int(off) / (int(config.config_books_per_page)) + 1),
|
entries, __, pagination = fill_indexpage((int(off) / (int(config.config_books_per_page)) + 1),
|
||||||
db.Books, db.Books.tags.any(db.Tags.id == book_id), [db.Books.timestamp.desc()])
|
db.Books,
|
||||||
|
db.Books.tags.any(db.Tags.id == book_id),
|
||||||
|
[db.Books.timestamp.desc()])
|
||||||
return render_xml_template('feed.xml', entries=entries, pagination=pagination)
|
return render_xml_template('feed.xml', entries=entries, pagination=pagination)
|
||||||
|
|
||||||
|
|
||||||
@ -225,9 +233,12 @@ def feed_seriesindex():
|
|||||||
def feed_series(book_id):
|
def feed_series(book_id):
|
||||||
off = request.args.get("offset") or 0
|
off = request.args.get("offset") or 0
|
||||||
entries, __, pagination = fill_indexpage((int(off) / (int(config.config_books_per_page)) + 1),
|
entries, __, pagination = fill_indexpage((int(off) / (int(config.config_books_per_page)) + 1),
|
||||||
db.Books, db.Books.series.any(db.Series.id == book_id), [db.Books.series_index])
|
db.Books,
|
||||||
|
db.Books.series.any(db.Series.id == book_id),
|
||||||
|
[db.Books.series_index])
|
||||||
return render_xml_template('feed.xml', entries=entries, pagination=pagination)
|
return render_xml_template('feed.xml', entries=entries, pagination=pagination)
|
||||||
|
|
||||||
|
|
||||||
@opds.route("/opds/ratings")
|
@opds.route("/opds/ratings")
|
||||||
@requires_basic_auth_if_no_ano
|
@requires_basic_auth_if_no_ano
|
||||||
def feed_ratingindex():
|
def feed_ratingindex():
|
||||||
@ -244,16 +255,18 @@ def feed_ratingindex():
|
|||||||
element.append(FeedObject(entry[0].id, "{} Stars".format(entry.name)))
|
element.append(FeedObject(entry[0].id, "{} Stars".format(entry.name)))
|
||||||
return render_xml_template('feed.xml', listelements=element, folder='opds.feed_ratings', pagination=pagination)
|
return render_xml_template('feed.xml', listelements=element, folder='opds.feed_ratings', pagination=pagination)
|
||||||
|
|
||||||
|
|
||||||
@opds.route("/opds/ratings/<book_id>")
|
@opds.route("/opds/ratings/<book_id>")
|
||||||
@requires_basic_auth_if_no_ano
|
@requires_basic_auth_if_no_ano
|
||||||
def feed_ratings(book_id):
|
def feed_ratings(book_id):
|
||||||
off = request.args.get("offset") or 0
|
off = request.args.get("offset") or 0
|
||||||
entries, __, pagination = fill_indexpage((int(off) / (int(config.config_books_per_page)) + 1),
|
entries, __, pagination = fill_indexpage((int(off) / (int(config.config_books_per_page)) + 1),
|
||||||
db.Books, db.Books.ratings.any(db.Ratings.id == book_id),[db.Books.timestamp.desc()])
|
db.Books,
|
||||||
|
db.Books.ratings.any(db.Ratings.id == book_id),
|
||||||
|
[db.Books.timestamp.desc()])
|
||||||
return render_xml_template('feed.xml', entries=entries, pagination=pagination)
|
return render_xml_template('feed.xml', entries=entries, pagination=pagination)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@opds.route("/opds/formats")
|
@opds.route("/opds/formats")
|
||||||
@requires_basic_auth_if_no_ano
|
@requires_basic_auth_if_no_ano
|
||||||
def feed_formatindex():
|
def feed_formatindex():
|
||||||
@ -274,7 +287,9 @@ def feed_formatindex():
|
|||||||
def feed_format(book_id):
|
def feed_format(book_id):
|
||||||
off = request.args.get("offset") or 0
|
off = request.args.get("offset") or 0
|
||||||
entries, __, pagination = fill_indexpage((int(off) / (int(config.config_books_per_page)) + 1),
|
entries, __, pagination = fill_indexpage((int(off) / (int(config.config_books_per_page)) + 1),
|
||||||
db.Books, db.Books.data.any(db.Data.format == book_id.upper()), [db.Books.timestamp.desc()])
|
db.Books,
|
||||||
|
db.Books.data.any(db.Data.format == book_id.upper()),
|
||||||
|
[db.Books.timestamp.desc()])
|
||||||
return render_xml_template('feed.xml', entries=entries, pagination=pagination)
|
return render_xml_template('feed.xml', entries=entries, pagination=pagination)
|
||||||
|
|
||||||
|
|
||||||
@ -306,7 +321,9 @@ def feed_languagesindex():
|
|||||||
def feed_languages(book_id):
|
def feed_languages(book_id):
|
||||||
off = request.args.get("offset") or 0
|
off = request.args.get("offset") or 0
|
||||||
entries, __, pagination = fill_indexpage((int(off) / (int(config.config_books_per_page)) + 1),
|
entries, __, pagination = fill_indexpage((int(off) / (int(config.config_books_per_page)) + 1),
|
||||||
db.Books, db.Books.languages.any(db.Languages.id == book_id), [db.Books.timestamp.desc()])
|
db.Books,
|
||||||
|
db.Books.languages.any(db.Languages.id == book_id),
|
||||||
|
[db.Books.timestamp.desc()])
|
||||||
return render_xml_template('feed.xml', entries=entries, pagination=pagination)
|
return render_xml_template('feed.xml', entries=entries, pagination=pagination)
|
||||||
|
|
||||||
|
|
||||||
@ -326,7 +343,8 @@ def feed_shelfindex():
|
|||||||
def feed_shelf(book_id):
|
def feed_shelf(book_id):
|
||||||
off = request.args.get("offset") or 0
|
off = request.args.get("offset") or 0
|
||||||
if current_user.is_anonymous:
|
if current_user.is_anonymous:
|
||||||
shelf = ub.session.query(ub.Shelf).filter(ub.Shelf.is_public == 1, ub.Shelf.id == book_id, not ub.Shelf.deleted).first()
|
shelf = ub.session.query(ub.Shelf).filter(ub.Shelf.is_public == 1,
|
||||||
|
ub.Shelf.id == book_id, not ub.Shelf.deleted).first()
|
||||||
else:
|
else:
|
||||||
shelf = ub.session.query(ub.Shelf).filter(or_(and_(ub.Shelf.user_id == int(current_user.id),
|
shelf = ub.session.query(ub.Shelf).filter(or_(and_(ub.Shelf.user_id == int(current_user.id),
|
||||||
ub.Shelf.id == book_id),
|
ub.Shelf.id == book_id),
|
||||||
@ -349,11 +367,11 @@ def feed_shelf(book_id):
|
|||||||
@requires_basic_auth_if_no_ano
|
@requires_basic_auth_if_no_ano
|
||||||
@download_required
|
@download_required
|
||||||
def opds_download_link(book_id, book_format):
|
def opds_download_link(book_id, book_format):
|
||||||
return get_download_link(book_id,book_format.lower())
|
return get_download_link(book_id, book_format.lower())
|
||||||
|
|
||||||
|
|
||||||
@opds.route("/ajax/book/<string:uuid>/<library>")
|
@opds.route("/ajax/book/<string:uuid>/<library>")
|
||||||
@opds.route("/ajax/book/<string:uuid>",defaults={'library': ""})
|
@opds.route("/ajax/book/<string:uuid>", defaults={'library': ""})
|
||||||
@requires_basic_auth_if_no_ano
|
@requires_basic_auth_if_no_ano
|
||||||
def get_metadata_calibre_companion(uuid, library):
|
def get_metadata_calibre_companion(uuid, library):
|
||||||
entry = db.session.query(db.Books).filter(db.Books.uuid.like("%" + uuid + "%")).first()
|
entry = db.session.query(db.Books).filter(db.Books.uuid.like("%" + uuid + "%")).first()
|
||||||
@ -369,16 +387,17 @@ def get_metadata_calibre_companion(uuid, library):
|
|||||||
def feed_search(term):
|
def feed_search(term):
|
||||||
if term:
|
if term:
|
||||||
term = term.strip().lower()
|
term = term.strip().lower()
|
||||||
entries = get_search_results( term)
|
entries = get_search_results(term)
|
||||||
entriescount = len(entries) if len(entries) > 0 else 1
|
entriescount = len(entries) if len(entries) > 0 else 1
|
||||||
pagination = Pagination(1, entriescount, entriescount)
|
pagination = Pagination(1, entriescount, entriescount)
|
||||||
return render_xml_template('feed.xml', searchterm=term, entries=entries, pagination=pagination)
|
return render_xml_template('feed.xml', searchterm=term, entries=entries, pagination=pagination)
|
||||||
else:
|
else:
|
||||||
return render_xml_template('feed.xml', searchterm="")
|
return render_xml_template('feed.xml', searchterm="")
|
||||||
|
|
||||||
|
|
||||||
def check_auth(username, password):
|
def check_auth(username, password):
|
||||||
if sys.version_info.major == 3:
|
if sys.version_info.major == 3:
|
||||||
username=username.encode('windows-1252')
|
username = username.encode('windows-1252')
|
||||||
user = ub.session.query(ub.User).filter(func.lower(ub.User.nickname) ==
|
user = ub.session.query(ub.User).filter(func.lower(ub.User.nickname) ==
|
||||||
username.decode('utf-8').lower()).first()
|
username.decode('utf-8').lower()).first()
|
||||||
return bool(user and check_password_hash(str(user.password), password))
|
return bool(user and check_password_hash(str(user.password), password))
|
||||||
@ -392,13 +411,14 @@ def authenticate():
|
|||||||
|
|
||||||
|
|
||||||
def render_xml_template(*args, **kwargs):
|
def render_xml_template(*args, **kwargs):
|
||||||
#ToDo: return time in current timezone similar to %z
|
# ToDo: return time in current timezone similar to %z
|
||||||
currtime = datetime.datetime.now().strftime("%Y-%m-%dT%H:%M:%S+00:00")
|
currtime = datetime.datetime.now().strftime("%Y-%m-%dT%H:%M:%S+00:00")
|
||||||
xml = render_template(current_time=currtime, instance=config.config_calibre_web_title, *args, **kwargs)
|
xml = render_template(current_time=currtime, instance=config.config_calibre_web_title, *args, **kwargs)
|
||||||
response = make_response(xml)
|
response = make_response(xml)
|
||||||
response.headers["Content-Type"] = "application/atom+xml; charset=utf-8"
|
response.headers["Content-Type"] = "application/atom+xml; charset=utf-8"
|
||||||
return response
|
return response
|
||||||
|
|
||||||
|
|
||||||
@opds.route("/opds/thumb_240_240/<book_id>")
|
@opds.route("/opds/thumb_240_240/<book_id>")
|
||||||
@opds.route("/opds/cover_240_240/<book_id>")
|
@opds.route("/opds/cover_240_240/<book_id>")
|
||||||
@opds.route("/opds/cover_90_90/<book_id>")
|
@opds.route("/opds/cover_90_90/<book_id>")
|
||||||
@ -407,6 +427,7 @@ def render_xml_template(*args, **kwargs):
|
|||||||
def feed_get_cover(book_id):
|
def feed_get_cover(book_id):
|
||||||
return get_book_cover(book_id)
|
return get_book_cover(book_id)
|
||||||
|
|
||||||
|
|
||||||
@opds.route("/opds/readbooks")
|
@opds.route("/opds/readbooks")
|
||||||
@requires_basic_auth_if_no_ano
|
@requires_basic_auth_if_no_ano
|
||||||
def feed_read_books():
|
def feed_read_books():
|
||||||
@ -414,6 +435,7 @@ def feed_read_books():
|
|||||||
result, pagination = render_read_books(int(off) / (int(config.config_books_per_page)) + 1, True, True)
|
result, pagination = render_read_books(int(off) / (int(config.config_books_per_page)) + 1, True, True)
|
||||||
return render_xml_template('feed.xml', entries=result, pagination=pagination)
|
return render_xml_template('feed.xml', entries=result, pagination=pagination)
|
||||||
|
|
||||||
|
|
||||||
@opds.route("/opds/unreadbooks")
|
@opds.route("/opds/unreadbooks")
|
||||||
@requires_basic_auth_if_no_ano
|
@requires_basic_auth_if_no_ano
|
||||||
def feed_unread_books():
|
def feed_unread_books():
|
||||||
|
@ -43,7 +43,6 @@ from . import logger
|
|||||||
log = logger.create()
|
log = logger.create()
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
def _readable_listen_address(address, port):
|
def _readable_listen_address(address, port):
|
||||||
if ':' in address:
|
if ':' in address:
|
||||||
address = "[" + address + "]"
|
address = "[" + address + "]"
|
||||||
@ -84,7 +83,8 @@ class WebServer(object):
|
|||||||
if os.path.isfile(certfile_path) and os.path.isfile(keyfile_path):
|
if os.path.isfile(certfile_path) and os.path.isfile(keyfile_path):
|
||||||
self.ssl_args = dict(certfile=certfile_path, keyfile=keyfile_path)
|
self.ssl_args = dict(certfile=certfile_path, keyfile=keyfile_path)
|
||||||
else:
|
else:
|
||||||
log.warning('The specified paths for the ssl certificate file and/or key file seem to be broken. Ignoring ssl.')
|
log.warning('The specified paths for the ssl certificate file and/or key file seem to be broken. '
|
||||||
|
'Ignoring ssl.')
|
||||||
log.warning('Cert path: %s', certfile_path)
|
log.warning('Cert path: %s', certfile_path)
|
||||||
log.warning('Key path: %s', keyfile_path)
|
log.warning('Key path: %s', keyfile_path)
|
||||||
|
|
||||||
|
@ -49,10 +49,11 @@ def get_datetime_from_json(json_object, field_name):
|
|||||||
return datetime.min
|
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
|
||||||
As an example use-case, the SyncToken is used to detect books that have been added to the library since the last time the device synced to the server.
|
requests to the service. As an example use-case, the SyncToken is used to detect books that have been added
|
||||||
|
to the library since the last time the device synced to the server.
|
||||||
|
|
||||||
Attributes:
|
Attributes:
|
||||||
books_last_created: Datetime representing the newest book that the device knows about.
|
books_last_created: Datetime representing the newest book that the device knows about.
|
||||||
@ -66,10 +67,11 @@ class SyncToken():
|
|||||||
|
|
||||||
token_schema = {
|
token_schema = {
|
||||||
"type": "object",
|
"type": "object",
|
||||||
"properties": {"version": {"type": "string"}, "data": {"type": "object"},},
|
"properties": {"version": {"type": "string"}, "data": {"type": "object"}, },
|
||||||
}
|
}
|
||||||
# This Schema doesn't contain enough information to detect and propagate book deletions from Calibre to the device.
|
# This Schema doesn't contain enough information to detect and propagate book deletions from Calibre to the device.
|
||||||
# A potential solution might be to keep a list of all known book uuids in the token, and look for any missing from the db.
|
# A potential solution might be to keep a list of all known book uuids in the token, and look for any missing
|
||||||
|
# from the db.
|
||||||
data_schema_v1 = {
|
data_schema_v1 = {
|
||||||
"type": "object",
|
"type": "object",
|
||||||
"properties": {
|
"properties": {
|
||||||
|
49
cps/shelf.py
49
cps/shelf.py
@ -25,7 +25,7 @@ from __future__ import division, print_function, unicode_literals
|
|||||||
from flask import Blueprint, request, flash, redirect, url_for
|
from flask import Blueprint, request, flash, redirect, url_for
|
||||||
from flask_babel import gettext as _
|
from flask_babel import gettext as _
|
||||||
from flask_login import login_required, current_user
|
from flask_login import login_required, current_user
|
||||||
from sqlalchemy.sql.expression import func, or_, and_
|
from sqlalchemy.sql.expression import func
|
||||||
|
|
||||||
from . import logger, ub, searched_ids, db
|
from . import logger, ub, searched_ids, db
|
||||||
from .web import render_title_template
|
from .web import render_title_template
|
||||||
@ -35,6 +35,7 @@ from .helper import common_filters
|
|||||||
shelf = Blueprint('shelf', __name__)
|
shelf = Blueprint('shelf', __name__)
|
||||||
log = logger.create()
|
log = logger.create()
|
||||||
|
|
||||||
|
|
||||||
def check_shelf_edit_permissions(cur_shelf):
|
def check_shelf_edit_permissions(cur_shelf):
|
||||||
if not cur_shelf.is_public and not cur_shelf.user_id == int(current_user.id):
|
if not cur_shelf.is_public and not cur_shelf.user_id == int(current_user.id):
|
||||||
log.error("User %s not allowed to edit shelf %s", current_user, cur_shelf)
|
log.error("User %s not allowed to edit shelf %s", current_user, cur_shelf)
|
||||||
@ -195,7 +196,6 @@ def remove_from_shelf(shelf_id, book_id):
|
|||||||
return "Sorry you are not allowed to remove a book from this shelf: %s" % shelf.name, 403
|
return "Sorry you are not allowed to remove a book from this shelf: %s" % shelf.name, 403
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@shelf.route("/shelf/create", methods=["GET", "POST"])
|
@shelf.route("/shelf/create", methods=["GET", "POST"])
|
||||||
@login_required
|
@login_required
|
||||||
def create_shelf():
|
def create_shelf():
|
||||||
@ -214,21 +214,24 @@ def create_shelf():
|
|||||||
.first() is None
|
.first() is None
|
||||||
|
|
||||||
if not is_shelf_name_unique:
|
if not is_shelf_name_unique:
|
||||||
flash(_(u"A public shelf with the name '%(title)s' already exists.", title=to_save["title"]), category="error")
|
flash(_(u"A public shelf with the name '%(title)s' already exists.", title=to_save["title"]),
|
||||||
|
category="error")
|
||||||
else:
|
else:
|
||||||
is_shelf_name_unique = ub.session.query(ub.Shelf) \
|
is_shelf_name_unique = ub.session.query(ub.Shelf) \
|
||||||
.filter((ub.Shelf.name == to_save["title"]) & (ub.Shelf.is_public == 0) & (ub.Shelf.user_id == int(current_user.id))) \
|
.filter((ub.Shelf.name == to_save["title"]) & (ub.Shelf.is_public == 0) &
|
||||||
|
(ub.Shelf.user_id == int(current_user.id)))\
|
||||||
.first() is None
|
.first() is None
|
||||||
|
|
||||||
if not is_shelf_name_unique:
|
if not is_shelf_name_unique:
|
||||||
flash(_(u"A private shelf with the name '%(title)s' already exists.", title=to_save["title"]), category="error")
|
flash(_(u"A private shelf with the name '%(title)s' already exists.", title=to_save["title"]),
|
||||||
|
category="error")
|
||||||
|
|
||||||
if is_shelf_name_unique:
|
if is_shelf_name_unique:
|
||||||
try:
|
try:
|
||||||
ub.session.add(shelf)
|
ub.session.add(shelf)
|
||||||
ub.session.commit()
|
ub.session.commit()
|
||||||
flash(_(u"Shelf %(title)s created", title=to_save["title"]), category="success")
|
flash(_(u"Shelf %(title)s created", title=to_save["title"]), category="success")
|
||||||
return redirect(url_for('shelf.show_shelf', shelf_id = shelf.id ))
|
return redirect(url_for('shelf.show_shelf', shelf_id=shelf.id))
|
||||||
except Exception:
|
except Exception:
|
||||||
flash(_(u"There was an error"), category="error")
|
flash(_(u"There was an error"), category="error")
|
||||||
return render_title_template('shelf_edit.html', shelf=shelf, title=_(u"Create a Shelf"), page="shelfcreate")
|
return render_title_template('shelf_edit.html', shelf=shelf, title=_(u"Create a Shelf"), page="shelfcreate")
|
||||||
@ -251,15 +254,18 @@ def edit_shelf(shelf_id):
|
|||||||
.first() is None
|
.first() is None
|
||||||
|
|
||||||
if not is_shelf_name_unique:
|
if not is_shelf_name_unique:
|
||||||
flash(_(u"A public shelf with the name '%(title)s' already exists.", title=to_save["title"]), category="error")
|
flash(_(u"A public shelf with the name '%(title)s' already exists.", title=to_save["title"]),
|
||||||
|
category="error")
|
||||||
else:
|
else:
|
||||||
is_shelf_name_unique = ub.session.query(ub.Shelf) \
|
is_shelf_name_unique = ub.session.query(ub.Shelf) \
|
||||||
.filter((ub.Shelf.name == to_save["title"]) & (ub.Shelf.is_public == 0) & (ub.Shelf.user_id == int(current_user.id))) \
|
.filter((ub.Shelf.name == to_save["title"]) & (ub.Shelf.is_public == 0) &
|
||||||
.filter(ub.Shelf.id != shelf_id) \
|
(ub.Shelf.user_id == int(current_user.id)))\
|
||||||
|
.filter(ub.Shelf.id != shelf_id)\
|
||||||
.first() is None
|
.first() is None
|
||||||
|
|
||||||
if not is_shelf_name_unique:
|
if not is_shelf_name_unique:
|
||||||
flash(_(u"A private shelf with the name '%(title)s' already exists.", title=to_save["title"]), category="error")
|
flash(_(u"A private shelf with the name '%(title)s' already exists.", title=to_save["title"]),
|
||||||
|
category="error")
|
||||||
|
|
||||||
if is_shelf_name_unique:
|
if is_shelf_name_unique:
|
||||||
shelf.name = to_save["title"]
|
shelf.name = to_save["title"]
|
||||||
@ -283,7 +289,7 @@ def delete_shelf_helper(cur_shelf):
|
|||||||
shelf_id = cur_shelf.id
|
shelf_id = cur_shelf.id
|
||||||
ub.session.delete(cur_shelf)
|
ub.session.delete(cur_shelf)
|
||||||
ub.session.query(ub.BookShelf).filter(ub.BookShelf.shelf == shelf_id).delete()
|
ub.session.query(ub.BookShelf).filter(ub.BookShelf.shelf == shelf_id).delete()
|
||||||
ub.session.add(ub.ShelfArchive(uuid = cur_shelf.uuid, user_id = cur_shelf.uuid))
|
ub.session.add(ub.ShelfArchive(uuid=cur_shelf.uuid, user_id=cur_shelf.uuid))
|
||||||
ub.session.commit()
|
ub.session.commit()
|
||||||
log.info("successfully deleted %s", cur_shelf)
|
log.info("successfully deleted %s", cur_shelf)
|
||||||
|
|
||||||
@ -295,7 +301,7 @@ def delete_shelf(shelf_id):
|
|||||||
delete_shelf_helper(cur_shelf)
|
delete_shelf_helper(cur_shelf)
|
||||||
return redirect(url_for('web.index'))
|
return redirect(url_for('web.index'))
|
||||||
|
|
||||||
# @shelf.route("/shelfdown/<int:shelf_id>")
|
|
||||||
@shelf.route("/shelf/<int:shelf_id>", defaults={'shelf_type': 1})
|
@shelf.route("/shelf/<int:shelf_id>", defaults={'shelf_type': 1})
|
||||||
@shelf.route("/shelf/<int:shelf_id>/<int:shelf_type>")
|
@shelf.route("/shelf/<int:shelf_id>/<int:shelf_type>")
|
||||||
def show_shelf(shelf_type, shelf_id):
|
def show_shelf(shelf_type, shelf_id):
|
||||||
@ -325,7 +331,6 @@ def show_shelf(shelf_type, shelf_id):
|
|||||||
return redirect(url_for("web.index"))
|
return redirect(url_for("web.index"))
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@shelf.route("/shelf/order/<int:shelf_id>", methods=["GET", "POST"])
|
@shelf.route("/shelf/order/<int:shelf_id>", methods=["GET", "POST"])
|
||||||
@login_required
|
@login_required
|
||||||
def order_shelf(shelf_id):
|
def order_shelf(shelf_id):
|
||||||
@ -347,17 +352,17 @@ def order_shelf(shelf_id):
|
|||||||
for book in books_in_shelf2:
|
for book in books_in_shelf2:
|
||||||
cur_book = db.session.query(db.Books).filter(db.Books.id == book.book_id).filter(common_filters()).first()
|
cur_book = db.session.query(db.Books).filter(db.Books.id == book.book_id).filter(common_filters()).first()
|
||||||
if cur_book:
|
if cur_book:
|
||||||
result.append({'title':cur_book.title,
|
result.append({'title': cur_book.title,
|
||||||
'id':cur_book.id,
|
'id': cur_book.id,
|
||||||
'author':cur_book.authors,
|
'author': cur_book.authors,
|
||||||
'series':cur_book.series,
|
'series': cur_book.series,
|
||||||
'series_index':cur_book.series_index})
|
'series_index': cur_book.series_index})
|
||||||
else:
|
else:
|
||||||
cur_book = db.session.query(db.Books).filter(db.Books.id == book.book_id).first()
|
cur_book = db.session.query(db.Books).filter(db.Books.id == book.book_id).first()
|
||||||
result.append({'title':_('Hidden Book'),
|
result.append({'title': _('Hidden Book'),
|
||||||
'id':cur_book.id,
|
'id': cur_book.id,
|
||||||
'author':[],
|
'author': [],
|
||||||
'series':[]})
|
'series': []})
|
||||||
return render_title_template('shelf_order.html', entries=result,
|
return render_title_template('shelf_order.html', entries=result,
|
||||||
title=_(u"Change order of Shelf: '%(name)s'", name=shelf.name),
|
title=_(u"Change order of Shelf: '%(name)s'", name=shelf.name),
|
||||||
shelf=shelf, page="shelforder")
|
shelf=shelf, page="shelforder")
|
||||||
|
@ -45,10 +45,10 @@ def process_open(command, quotes=(), env=None, sout=subprocess.PIPE, serr=subpro
|
|||||||
|
|
||||||
|
|
||||||
def process_wait(command, serr=subprocess.PIPE):
|
def process_wait(command, serr=subprocess.PIPE):
|
||||||
'''Run command, wait for process to terminate, and return an iterator over lines of its output.'''
|
# Run command, wait for process to terminate, and return an iterator over lines of its output.
|
||||||
p = process_open(command, serr=serr)
|
p = process_open(command, serr=serr)
|
||||||
p.wait()
|
p.wait()
|
||||||
for l in p.stdout.readlines():
|
for line in p.stdout.readlines():
|
||||||
if isinstance(l, bytes):
|
if isinstance(line, bytes):
|
||||||
l = l.decode('utf-8')
|
line = line.decode('utf-8')
|
||||||
yield l
|
yield line
|
||||||
|
@ -4,18 +4,18 @@
|
|||||||
|
|
||||||
<div class="filterheader hidden-xs hidden-sm">
|
<div class="filterheader hidden-xs hidden-sm">
|
||||||
{% if entries.__len__() %}
|
{% if entries.__len__() %}
|
||||||
{% if entries[0][0].sort %}
|
{% if data == 'author' %}
|
||||||
<button id="sort_name" class="btn btn-success"><b>B,A <-> A B</b></button>
|
<button id="sort_name" class="btn btn-primary"><b>B,A <-> A B</b></button>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% endif %}
|
{% endif %}
|
||||||
<button id="desc" class="btn btn-success"><span class="glyphicon glyphicon-sort-by-alphabet"></span></button>
|
<button id="desc" class="btn btn-primary"><span class="glyphicon glyphicon-sort-by-alphabet"></span></button>
|
||||||
<button id="asc" class="btn btn-success"><span class="glyphicon glyphicon-sort-by-alphabet-alt"></span></button>
|
<button id="asc" class="btn btn-primary"><span class="glyphicon glyphicon-sort-by-alphabet-alt"></span></button>
|
||||||
{% if charlist|length %}
|
{% if charlist|length %}
|
||||||
<button id="all" class="btn btn-success">{{_('All')}}</button>
|
<button id="all" class="btn btn-primary">{{_('All')}}</button>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
<div class="btn-group character" role="group">
|
<div class="btn-group character" role="group">
|
||||||
{% for char in charlist%}
|
{% for char in charlist%}
|
||||||
<button class="btn btn-success char">{{char.char}}</button>
|
<button class="btn btn-primary char">{{char.char}}</button>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
86
cps/ub.py
86
cps/ub.py
@ -42,11 +42,10 @@ 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, Float
|
from sqlalchemy import String, Integer, SmallInteger, Boolean, DateTime, Float
|
||||||
from sqlalchemy.ext.declarative import declarative_base
|
from sqlalchemy.ext.declarative import declarative_base
|
||||||
from sqlalchemy.orm import backref, foreign, relationship, remote, sessionmaker, Session
|
from sqlalchemy.orm import backref, relationship, sessionmaker, Session
|
||||||
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
|
||||||
|
|
||||||
|
|
||||||
session = None
|
session = None
|
||||||
@ -57,39 +56,39 @@ def get_sidebar_config(kwargs=None):
|
|||||||
kwargs = kwargs or []
|
kwargs = kwargs or []
|
||||||
if 'content' in kwargs:
|
if 'content' in kwargs:
|
||||||
content = kwargs['content']
|
content = kwargs['content']
|
||||||
content = isinstance(content, (User,LocalProxy)) and not content.role_anonymous()
|
content = isinstance(content, (User, LocalProxy)) and not content.role_anonymous()
|
||||||
else:
|
else:
|
||||||
content = 'conf' in kwargs
|
content = 'conf' in kwargs
|
||||||
sidebar = list()
|
sidebar = list()
|
||||||
sidebar.append({"glyph": "glyphicon-book", "text": _('Recently Added'), "link": 'web.index', "id": "new",
|
sidebar.append({"glyph": "glyphicon-book", "text": _('Recently Added'), "link": 'web.index', "id": "new",
|
||||||
"visibility": constants.SIDEBAR_RECENT, 'public': True, "page": "root",
|
"visibility": constants.SIDEBAR_RECENT, 'public': True, "page": "root",
|
||||||
"show_text": _('Show recent books'), "config_show":True})
|
"show_text": _('Show recent books'), "config_show": True})
|
||||||
sidebar.append({"glyph": "glyphicon-fire", "text": _('Hot Books'), "link": 'web.books_list', "id": "hot",
|
sidebar.append({"glyph": "glyphicon-fire", "text": _('Hot Books'), "link": 'web.books_list', "id": "hot",
|
||||||
"visibility": constants.SIDEBAR_HOT, 'public': True, "page": "hot", "show_text": _('Show Hot Books'),
|
"visibility": constants.SIDEBAR_HOT, 'public': True, "page": "hot",
|
||||||
"config_show":True})
|
"show_text": _('Show Hot Books'), "config_show": True})
|
||||||
sidebar.append(
|
sidebar.append(
|
||||||
{"glyph": "glyphicon-star", "text": _('Top Rated Books'), "link": 'web.books_list', "id": "rated",
|
{"glyph": "glyphicon-star", "text": _('Top Rated Books'), "link": 'web.books_list', "id": "rated",
|
||||||
"visibility": constants.SIDEBAR_BEST_RATED, 'public': True, "page": "rated",
|
"visibility": constants.SIDEBAR_BEST_RATED, 'public': True, "page": "rated",
|
||||||
"show_text": _('Show Top Rated Books'), "config_show":True})
|
"show_text": _('Show Top Rated Books'), "config_show": True})
|
||||||
sidebar.append({"glyph": "glyphicon-eye-open", "text": _('Read Books'), "link": 'web.books_list', "id": "read",
|
sidebar.append({"glyph": "glyphicon-eye-open", "text": _('Read Books'), "link": 'web.books_list', "id": "read",
|
||||||
"visibility": constants.SIDEBAR_READ_AND_UNREAD, 'public': (not g.user.is_anonymous), "page": "read",
|
"visibility": constants.SIDEBAR_READ_AND_UNREAD, 'public': (not g.user.is_anonymous), "page": "read",
|
||||||
"show_text": _('Show read and unread'), "config_show": content})
|
"show_text": _('Show read and unread'), "config_show": content})
|
||||||
sidebar.append(
|
sidebar.append(
|
||||||
{"glyph": "glyphicon-eye-close", "text": _('Unread Books'), "link": 'web.books_list', "id": "unread",
|
{"glyph": "glyphicon-eye-close", "text": _('Unread Books'), "link": 'web.books_list', "id": "unread",
|
||||||
"visibility": constants.SIDEBAR_READ_AND_UNREAD, 'public': (not g.user.is_anonymous), "page": "unread",
|
"visibility": constants.SIDEBAR_READ_AND_UNREAD, 'public': (not g.user.is_anonymous), "page": "unread",
|
||||||
"show_text": _('Show unread'), "config_show":False})
|
"show_text": _('Show unread'), "config_show": False})
|
||||||
sidebar.append({"glyph": "glyphicon-random", "text": _('Discover'), "link": 'web.books_list', "id": "rand",
|
sidebar.append({"glyph": "glyphicon-random", "text": _('Discover'), "link": 'web.books_list', "id": "rand",
|
||||||
"visibility": constants.SIDEBAR_RANDOM, 'public': True, "page": "discover",
|
"visibility": constants.SIDEBAR_RANDOM, 'public': True, "page": "discover",
|
||||||
"show_text": _('Show random books'), "config_show":True})
|
"show_text": _('Show random books'), "config_show": True})
|
||||||
sidebar.append({"glyph": "glyphicon-inbox", "text": _('Categories'), "link": 'web.category_list', "id": "cat",
|
sidebar.append({"glyph": "glyphicon-inbox", "text": _('Categories'), "link": 'web.category_list', "id": "cat",
|
||||||
"visibility": constants.SIDEBAR_CATEGORY, 'public': True, "page": "category",
|
"visibility": constants.SIDEBAR_CATEGORY, 'public': True, "page": "category",
|
||||||
"show_text": _('Show category selection'), "config_show":True})
|
"show_text": _('Show category selection'), "config_show": True})
|
||||||
sidebar.append({"glyph": "glyphicon-bookmark", "text": _('Series'), "link": 'web.series_list', "id": "serie",
|
sidebar.append({"glyph": "glyphicon-bookmark", "text": _('Series'), "link": 'web.series_list', "id": "serie",
|
||||||
"visibility": constants.SIDEBAR_SERIES, 'public': True, "page": "series",
|
"visibility": constants.SIDEBAR_SERIES, 'public': True, "page": "series",
|
||||||
"show_text": _('Show series selection'), "config_show":True})
|
"show_text": _('Show series selection'), "config_show": True})
|
||||||
sidebar.append({"glyph": "glyphicon-user", "text": _('Authors'), "link": 'web.author_list', "id": "author",
|
sidebar.append({"glyph": "glyphicon-user", "text": _('Authors'), "link": 'web.author_list', "id": "author",
|
||||||
"visibility": constants.SIDEBAR_AUTHOR, 'public': True, "page": "author",
|
"visibility": constants.SIDEBAR_AUTHOR, 'public': True, "page": "author",
|
||||||
"show_text": _('Show author selection'), "config_show":True})
|
"show_text": _('Show author selection'), "config_show": True})
|
||||||
sidebar.append(
|
sidebar.append(
|
||||||
{"glyph": "glyphicon-text-size", "text": _('Publishers'), "link": 'web.publisher_list', "id": "publisher",
|
{"glyph": "glyphicon-text-size", "text": _('Publishers'), "link": 'web.publisher_list', "id": "publisher",
|
||||||
"visibility": constants.SIDEBAR_PUBLISHER, 'public': True, "page": "publisher",
|
"visibility": constants.SIDEBAR_PUBLISHER, 'public': True, "page": "publisher",
|
||||||
@ -97,13 +96,13 @@ def get_sidebar_config(kwargs=None):
|
|||||||
sidebar.append({"glyph": "glyphicon-flag", "text": _('Languages'), "link": 'web.language_overview', "id": "lang",
|
sidebar.append({"glyph": "glyphicon-flag", "text": _('Languages'), "link": 'web.language_overview', "id": "lang",
|
||||||
"visibility": constants.SIDEBAR_LANGUAGE, 'public': (g.user.filter_language() == 'all'),
|
"visibility": constants.SIDEBAR_LANGUAGE, 'public': (g.user.filter_language() == 'all'),
|
||||||
"page": "language",
|
"page": "language",
|
||||||
"show_text": _('Show language selection'), "config_show":True})
|
"show_text": _('Show language selection'), "config_show": True})
|
||||||
sidebar.append({"glyph": "glyphicon-star-empty", "text": _('Ratings'), "link": 'web.ratings_list', "id": "rate",
|
sidebar.append({"glyph": "glyphicon-star-empty", "text": _('Ratings'), "link": 'web.ratings_list', "id": "rate",
|
||||||
"visibility": constants.SIDEBAR_RATING, 'public': True,
|
"visibility": constants.SIDEBAR_RATING, 'public': True,
|
||||||
"page": "rating", "show_text": _('Show ratings selection'), "config_show":True})
|
"page": "rating", "show_text": _('Show ratings selection'), "config_show": True})
|
||||||
sidebar.append({"glyph": "glyphicon-file", "text": _('File formats'), "link": 'web.formats_list', "id": "format",
|
sidebar.append({"glyph": "glyphicon-file", "text": _('File formats'), "link": 'web.formats_list', "id": "format",
|
||||||
"visibility": constants.SIDEBAR_FORMAT, 'public': True,
|
"visibility": constants.SIDEBAR_FORMAT, 'public': True,
|
||||||
"page": "format", "show_text": _('Show file formats selection'), "config_show":True})
|
"page": "format", "show_text": _('Show file formats selection'), "config_show": True})
|
||||||
sidebar.append(
|
sidebar.append(
|
||||||
{"glyph": "glyphicon-trash", "text": _('Archived Books'), "link": 'web.books_list', "id": "archived",
|
{"glyph": "glyphicon-trash", "text": _('Archived Books'), "link": 'web.books_list', "id": "archived",
|
||||||
"visibility": constants.SIDEBAR_ARCHIVED, 'public': (not g.user.is_anonymous), "page": "archived",
|
"visibility": constants.SIDEBAR_ARCHIVED, 'public': (not g.user.is_anonymous), "page": "archived",
|
||||||
@ -236,7 +235,8 @@ class Anonymous(AnonymousUserMixin, UserBase):
|
|||||||
self.loadSettings()
|
self.loadSettings()
|
||||||
|
|
||||||
def loadSettings(self):
|
def loadSettings(self):
|
||||||
data = session.query(User).filter(User.role.op('&')(constants.ROLE_ANONYMOUS) == constants.ROLE_ANONYMOUS).first() # type: User
|
data = session.query(User).filter(User.role.op('&')(constants.ROLE_ANONYMOUS) == constants.ROLE_ANONYMOUS)\
|
||||||
|
.first() # type: User
|
||||||
self.nickname = data.nickname
|
self.nickname = data.nickname
|
||||||
self.role = data.role
|
self.role = data.role
|
||||||
self.id=data.id
|
self.id=data.id
|
||||||
@ -259,7 +259,7 @@ class Anonymous(AnonymousUserMixin, UserBase):
|
|||||||
|
|
||||||
@property
|
@property
|
||||||
def is_anonymous(self):
|
def is_anonymous(self):
|
||||||
return True # self.anon_browse
|
return True
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def is_authenticated(self):
|
def is_authenticated(self):
|
||||||
@ -271,7 +271,7 @@ class Shelf(Base):
|
|||||||
__tablename__ = 'shelf'
|
__tablename__ = 'shelf'
|
||||||
|
|
||||||
id = Column(Integer, primary_key=True)
|
id = Column(Integer, primary_key=True)
|
||||||
uuid = Column(String, default=lambda : str(uuid.uuid4()))
|
uuid = Column(String, default=lambda: str(uuid.uuid4()))
|
||||||
name = Column(String)
|
name = Column(String)
|
||||||
is_public = Column(Integer, default=0)
|
is_public = Column(Integer, default=0)
|
||||||
user_id = Column(Integer, ForeignKey('user.id'))
|
user_id = Column(Integer, ForeignKey('user.id'))
|
||||||
@ -318,8 +318,12 @@ class ReadBook(Base):
|
|||||||
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)
|
||||||
read_status = Column(Integer, unique=False, default=STATUS_UNREAD, nullable=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), "
|
kobo_reading_state = relationship("KoboReadingState", uselist=False,
|
||||||
"ReadBook.book_id == foreign(KoboReadingState.book_id))", cascade="all", backref=backref("book_read_link", 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_modified = Column(DateTime, default=datetime.datetime.utcnow, onupdate=datetime.datetime.utcnow)
|
||||||
last_time_started_reading = Column(DateTime, nullable=True)
|
last_time_started_reading = Column(DateTime, nullable=True)
|
||||||
times_started_reading = Column(Integer, default=0, nullable=False)
|
times_started_reading = Column(Integer, default=0, nullable=False)
|
||||||
@ -334,6 +338,7 @@ 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.
|
# Baseclass representing books that are archived on the user's Kobo device.
|
||||||
class ArchivedBook(Base):
|
class ArchivedBook(Base):
|
||||||
__tablename__ = 'archived_book'
|
__tablename__ = 'archived_book'
|
||||||
@ -421,7 +426,6 @@ class Registration(Base):
|
|||||||
return u"<Registration('{0}')>".format(self.domain)
|
return u"<Registration('{0}')>".format(self.domain)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
class RemoteAuthToken(Base):
|
class RemoteAuthToken(Base):
|
||||||
__tablename__ = 'remote_auth_token'
|
__tablename__ = 'remote_auth_token'
|
||||||
|
|
||||||
@ -532,18 +536,12 @@ def migrate_Database(session):
|
|||||||
conn = engine.connect()
|
conn = engine.connect()
|
||||||
conn.execute("UPDATE user SET 'sidebar_view' = (random_books* :side_random + language_books * :side_lang "
|
conn.execute("UPDATE user SET 'sidebar_view' = (random_books* :side_random + language_books * :side_lang "
|
||||||
"+ series_books * :side_series + category_books * :side_category + hot_books * "
|
"+ series_books * :side_series + category_books * :side_category + hot_books * "
|
||||||
":side_hot + :side_autor + :detail_random)"
|
":side_hot + :side_autor + :detail_random)",
|
||||||
,{'side_random': constants.SIDEBAR_RANDOM, 'side_lang': constants.SIDEBAR_LANGUAGE,
|
{'side_random': constants.SIDEBAR_RANDOM, 'side_lang': constants.SIDEBAR_LANGUAGE,
|
||||||
'side_series': constants.SIDEBAR_SERIES,
|
'side_series': constants.SIDEBAR_SERIES, 'side_category': constants.SIDEBAR_CATEGORY,
|
||||||
'side_category': constants.SIDEBAR_CATEGORY, 'side_hot': constants.SIDEBAR_HOT,
|
'side_hot': constants.SIDEBAR_HOT, 'side_autor': constants.SIDEBAR_AUTHOR,
|
||||||
'side_autor': constants.SIDEBAR_AUTHOR,
|
|
||||||
'detail_random': constants.DETAIL_RANDOM})
|
'detail_random': constants.DETAIL_RANDOM})
|
||||||
session.commit()
|
session.commit()
|
||||||
'''try:
|
|
||||||
session.query(exists().where(User.mature_content)).scalar()
|
|
||||||
except exc.OperationalError:
|
|
||||||
conn = engine.connect()
|
|
||||||
conn.execute("ALTER TABLE user ADD column `mature_content` INTEGER DEFAULT 1")'''
|
|
||||||
try:
|
try:
|
||||||
session.query(exists().where(User.denied_tags)).scalar()
|
session.query(exists().where(User.denied_tags)).scalar()
|
||||||
except exc.OperationalError: # Database is not compatible, some columns are missing
|
except exc.OperationalError: # Database is not compatible, some columns are missing
|
||||||
@ -552,7 +550,8 @@ def migrate_Database(session):
|
|||||||
conn.execute("ALTER TABLE user ADD column `allowed_tags` String DEFAULT ''")
|
conn.execute("ALTER TABLE user ADD column `allowed_tags` String DEFAULT ''")
|
||||||
conn.execute("ALTER TABLE user ADD column `denied_column_value` DEFAULT ''")
|
conn.execute("ALTER TABLE user ADD column `denied_column_value` DEFAULT ''")
|
||||||
conn.execute("ALTER TABLE user ADD column `allowed_column_value` DEFAULT ''")
|
conn.execute("ALTER TABLE user ADD column `allowed_column_value` DEFAULT ''")
|
||||||
if session.query(User).filter(User.role.op('&')(constants.ROLE_ANONYMOUS) == constants.ROLE_ANONYMOUS).first() is None:
|
if session.query(User).filter(User.role.op('&')(constants.ROLE_ANONYMOUS) == constants.ROLE_ANONYMOUS).first() \
|
||||||
|
is None:
|
||||||
create_anonymous_user(session)
|
create_anonymous_user(session)
|
||||||
try:
|
try:
|
||||||
# check if one table with autoincrement is existing (should be user table)
|
# check if one table with autoincrement is existing (should be user table)
|
||||||
@ -562,7 +561,7 @@ def migrate_Database(session):
|
|||||||
# Create new table user_id and copy contents of table user into it
|
# Create new table user_id and copy contents of table user into it
|
||||||
conn = engine.connect()
|
conn = engine.connect()
|
||||||
conn.execute("CREATE TABLE user_id (id INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT,"
|
conn.execute("CREATE TABLE user_id (id INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT,"
|
||||||
"nickname VARCHAR(64),"
|
" nickname VARCHAR(64),"
|
||||||
"email VARCHAR(120),"
|
"email VARCHAR(120),"
|
||||||
"role SMALLINT,"
|
"role SMALLINT,"
|
||||||
"password VARCHAR,"
|
"password VARCHAR,"
|
||||||
@ -591,25 +590,26 @@ def clean_database(session):
|
|||||||
# Remove expired remote login tokens
|
# Remove expired remote login tokens
|
||||||
now = datetime.datetime.now()
|
now = datetime.datetime.now()
|
||||||
session.query(RemoteAuthToken).filter(now > RemoteAuthToken.expiration).\
|
session.query(RemoteAuthToken).filter(now > RemoteAuthToken.expiration).\
|
||||||
filter(RemoteAuthToken.token_type !=1 ).delete()
|
filter(RemoteAuthToken.token_type != 1).delete()
|
||||||
session.commit()
|
session.commit()
|
||||||
|
|
||||||
|
|
||||||
# Save downloaded books per user in calibre-web's own database
|
# Save downloaded books per user in calibre-web's own database
|
||||||
def update_download(book_id, user_id):
|
def update_download(book_id, user_id):
|
||||||
check = session.query(Downloads).filter(Downloads.user_id == user_id).filter(Downloads.book_id ==
|
check = session.query(Downloads).filter(Downloads.user_id == user_id).filter(Downloads.book_id == book_id).first()
|
||||||
book_id).first()
|
|
||||||
|
|
||||||
if not check:
|
if not check:
|
||||||
new_download = Downloads(user_id=user_id, book_id=book_id)
|
new_download = Downloads(user_id=user_id, book_id=book_id)
|
||||||
session.add(new_download)
|
session.add(new_download)
|
||||||
session.commit()
|
session.commit()
|
||||||
|
|
||||||
|
|
||||||
# Delete non exisiting downloaded books in calibre-web's own database
|
# Delete non exisiting downloaded books in calibre-web's own database
|
||||||
def delete_download(book_id):
|
def delete_download(book_id):
|
||||||
session.query(Downloads).filter(book_id == Downloads.book_id).delete()
|
session.query(Downloads).filter(book_id == Downloads.book_id).delete()
|
||||||
session.commit()
|
session.commit()
|
||||||
|
|
||||||
|
|
||||||
# Generate user Guest (translated text), as anoymous user, no rights
|
# Generate user Guest (translated text), as anoymous user, no rights
|
||||||
def create_anonymous_user(session):
|
def create_anonymous_user(session):
|
||||||
user = User()
|
user = User()
|
||||||
@ -667,8 +667,12 @@ def dispose():
|
|||||||
old_session = session
|
old_session = session
|
||||||
session = None
|
session = None
|
||||||
if old_session:
|
if old_session:
|
||||||
try: old_session.close()
|
try:
|
||||||
except: pass
|
old_session.close()
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
if old_session.bind:
|
if old_session.bind:
|
||||||
try: old_session.bind.dispose()
|
try:
|
||||||
except: pass
|
old_session.bind.dispose()
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
|
@ -69,7 +69,7 @@ class Updater(threading.Thread):
|
|||||||
def get_available_updates(self, request_method, locale):
|
def get_available_updates(self, request_method, locale):
|
||||||
if config.config_updatechannel == constants.UPDATE_STABLE:
|
if config.config_updatechannel == constants.UPDATE_STABLE:
|
||||||
return self._stable_available_updates(request_method)
|
return self._stable_available_updates(request_method)
|
||||||
return self._nightly_available_updates(request_method,locale)
|
return self._nightly_available_updates(request_method, locale)
|
||||||
|
|
||||||
def do_work(self):
|
def do_work(self):
|
||||||
try:
|
try:
|
||||||
@ -132,7 +132,7 @@ class Updater(threading.Thread):
|
|||||||
def pause(self):
|
def pause(self):
|
||||||
self.can_run.clear()
|
self.can_run.clear()
|
||||||
|
|
||||||
#should just resume the thread
|
# should just resume the thread
|
||||||
def resume(self):
|
def resume(self):
|
||||||
self.can_run.set()
|
self.can_run.set()
|
||||||
|
|
||||||
@ -268,7 +268,7 @@ class Updater(threading.Thread):
|
|||||||
|
|
||||||
def is_venv(self):
|
def is_venv(self):
|
||||||
if (hasattr(sys, 'real_prefix')) or (hasattr(sys, 'base_prefix') and sys.base_prefix != sys.prefix):
|
if (hasattr(sys, 'real_prefix')) or (hasattr(sys, 'base_prefix') and sys.base_prefix != sys.prefix):
|
||||||
return os.sep + os.path.relpath(sys.prefix,constants.BASE_DIR)
|
return os.sep + os.path.relpath(sys.prefix, constants.BASE_DIR)
|
||||||
else:
|
else:
|
||||||
return False
|
return False
|
||||||
|
|
||||||
@ -436,7 +436,7 @@ class Updater(threading.Thread):
|
|||||||
patch_version_update > current_version[2]) or \
|
patch_version_update > current_version[2]) or \
|
||||||
minor_version_update > current_version[1]:
|
minor_version_update > current_version[1]:
|
||||||
parents.append([commit[i]['tag_name'], commit[i]['body'].replace('\r\n', '<p>')])
|
parents.append([commit[i]['tag_name'], commit[i]['body'].replace('\r\n', '<p>')])
|
||||||
newer=True
|
newer = True
|
||||||
i -= 1
|
i -= 1
|
||||||
continue
|
continue
|
||||||
if major_version_update < current_version[0]:
|
if major_version_update < current_version[0]:
|
||||||
|
Loading…
Reference in New Issue
Block a user