1
0
mirror of https://github.com/janeczku/calibre-web synced 2024-12-18 22:20:30 +00:00

Merge branch 'master' into Develop

This commit is contained in:
Ozzie Isaacs 2022-06-14 18:45:50 +02:00
commit b206b7a5d8
15 changed files with 1024 additions and 791 deletions

View File

@ -1264,13 +1264,13 @@ def update_mailsettings():
return edit_mailsettings() return edit_mailsettings()
else: else:
_config_string(to_save, "mail_server")
_config_int(to_save, "mail_port") _config_int(to_save, "mail_port")
_config_int(to_save, "mail_use_ssl") _config_int(to_save, "mail_use_ssl")
_config_string(to_save, "mail_login")
_config_string(to_save, "mail_password") _config_string(to_save, "mail_password")
_config_string(to_save, "mail_from")
_config_int(to_save, "mail_size", lambda y: int(y)*1024*1024) _config_int(to_save, "mail_size", lambda y: int(y)*1024*1024)
config.mail_server = to_save.get('mail_server', "").strip()
config.mail_from = to_save.get('mail_from', "").strip()
config.mail_login = to_save.get('mail_login', "").strip()
try: try:
config.save() config.save()
except (OperationalError, InvalidRequestError) as e: except (OperationalError, InvalidRequestError) as e:
@ -1278,6 +1278,9 @@ def update_mailsettings():
log.error_or_exception("Settings Database error: {}".format(e)) log.error_or_exception("Settings Database error: {}".format(e))
flash(_(u"Database error: %(error)s.", error=e.orig), category="error") flash(_(u"Database error: %(error)s.", error=e.orig), category="error")
return edit_mailsettings() return edit_mailsettings()
except Exception as e:
flash(_(u"Database error: %(error)s.", error=e.orig), category="error")
return edit_mailsettings()
if to_save.get("test"): if to_save.get("test"):
if current_user.email: if current_user.email:
@ -1668,9 +1671,10 @@ def _db_configuration_update_helper():
if db_change or not db_valid or not config.db_configured \ if db_change or not db_valid or not config.db_configured \
or config.config_calibre_dir != to_save["config_calibre_dir"]: or config.config_calibre_dir != to_save["config_calibre_dir"]:
if not calibre_db.setup_db(to_save['config_calibre_dir'], ub.app_DB_path): if not os.path.exists(metadata_db) or not to_save['config_calibre_dir']:
return _db_configuration_result(_('DB Location is not Valid, Please Enter Correct Path'), return _db_configuration_result(_('DB Location is not Valid, Please Enter Correct Path'), gdrive_error)
gdrive_error) else:
calibre_db.setup_db(to_save['config_calibre_dir'], ub.app_DB_path)
config.store_calibre_uuid(calibre_db, db.Library_Id) config.store_calibre_uuid(calibre_db, db.Library_Id)
# if db changed -> delete shelfs, delete download books, delete read books, kobo sync... # if db changed -> delete shelfs, delete download books, delete read books, kobo sync...
if db_change: if db_change:

View File

@ -567,12 +567,12 @@ class CalibreDB:
if not config_calibre_dir: if not config_calibre_dir:
cls.config.invalidate() cls.config.invalidate()
return False return None
dbpath = os.path.join(config_calibre_dir, "metadata.db") dbpath = os.path.join(config_calibre_dir, "metadata.db")
if not os.path.exists(dbpath): if not os.path.exists(dbpath):
cls.config.invalidate() cls.config.invalidate()
return False return None
try: try:
cls.engine = create_engine('sqlite://', cls.engine = create_engine('sqlite://',
@ -588,7 +588,7 @@ class CalibreDB:
# conn.text_factory = lambda b: b.decode(errors = 'ignore') possible fix for #1302 # conn.text_factory = lambda b: b.decode(errors = 'ignore') possible fix for #1302
except Exception as ex: except Exception as ex:
cls.config.invalidate(ex) cls.config.invalidate(ex)
return False return None
cls.config.db_configured = True cls.config.db_configured = True
@ -598,7 +598,7 @@ class CalibreDB:
cls.setup_db_cc_classes(cc) cls.setup_db_cc_classes(cc)
except OperationalError as e: except OperationalError as e:
log.error_or_exception(e) log.error_or_exception(e)
return False return None
cls.session_factory = scoped_session(sessionmaker(autocommit=False, cls.session_factory = scoped_session(sessionmaker(autocommit=False,
autoflush=True, autoflush=True,
@ -607,7 +607,6 @@ class CalibreDB:
inst.init_session() inst.init_session()
cls._init = True cls._init = True
return True
def get_book(self, book_id): def get_book(self, book_id):
return self.session.query(Books).filter(Books.id == book_id).first() return self.session.query(Books).filter(Books.id == book_id).first()
@ -628,7 +627,7 @@ class CalibreDB:
.join(read_column, read_column.book == book_id, .join(read_column, read_column.book == book_id,
isouter=True)) isouter=True))
except (KeyError, AttributeError, IndexError): except (KeyError, AttributeError, IndexError):
log.error("Custom Column No.{} is not existing in calibre database".format(read_column)) log.error("Custom Column No.{} does not exist in calibre database".format(read_column))
# Skip linking read column and return None instead of read status # Skip linking read column and return None instead of read status
bd = self.session.query(Books, None, ub.ArchivedBook.is_archived) bd = self.session.query(Books, None, ub.ArchivedBook.is_archived)
return (bd.filter(Books.id == book_id) return (bd.filter(Books.id == book_id)
@ -675,9 +674,9 @@ class CalibreDB:
except (KeyError, AttributeError, IndexError): except (KeyError, AttributeError, IndexError):
pos_content_cc_filter = false() pos_content_cc_filter = false()
neg_content_cc_filter = true() neg_content_cc_filter = true()
log.error("Custom Column No.{} is not existing in calibre database".format( log.error("Custom Column No.{} does not exist in calibre database".format(
self.config.config_restricted_column)) self.config.config_restricted_column))
flash(_("Custom Column No.%(column)d is not existing in calibre database", flash(_("Custom Column No.%(column)d does not exist in calibre database",
column=self.config.config_restricted_column), column=self.config.config_restricted_column),
category="error") category="error")
@ -700,7 +699,7 @@ class CalibreDB:
.select_from(Books) .select_from(Books)
.outerjoin(read_column, read_column.book == Books.id)) .outerjoin(read_column, read_column.book == Books.id))
except (KeyError, AttributeError, IndexError): except (KeyError, AttributeError, IndexError):
log.error("Custom Column No.{} is not existing in calibre database".format(config_read_column)) log.error("Custom Column No.{} does not exist in calibre database".format(config_read_column))
# Skip linking read column and return None instead of read status # Skip linking read column and return None instead of read status
query = self.session.query(database, None, ub.ArchivedBook.is_archived) query = self.session.query(database, None, ub.ArchivedBook.is_archived)
return query.outerjoin(ub.ArchivedBook, and_(Books.id == ub.ArchivedBook.book_id, return query.outerjoin(ub.ArchivedBook, and_(Books.id == ub.ArchivedBook.book_id,

View File

@ -22,6 +22,7 @@ import glob
import zipfile import zipfile
import json import json
from io import BytesIO from io import BytesIO
from flask_babel.speaklater import LazyString
import os import os
@ -32,6 +33,12 @@ from .about import collect_stats
log = logger.create() log = logger.create()
class lazyEncoder(json.JSONEncoder):
def default(self, obj):
if isinstance(obj, LazyString):
return str(obj)
# Let the base class default method raise the TypeError
return json.JSONEncoder.default(self, obj)
def assemble_logfiles(file_name): def assemble_logfiles(file_name):
log_list = sorted(glob.glob(file_name + '*'), reverse=True) log_list = sorted(glob.glob(file_name + '*'), reverse=True)
@ -58,8 +65,8 @@ def send_debug():
file_list.remove(element) file_list.remove(element)
memory_zip = BytesIO() memory_zip = BytesIO()
with zipfile.ZipFile(memory_zip, 'w', compression=zipfile.ZIP_DEFLATED) as zf: with zipfile.ZipFile(memory_zip, 'w', compression=zipfile.ZIP_DEFLATED) as zf:
zf.writestr('settings.txt', json.dumps(config.toDict())) zf.writestr('settings.txt', json.dumps(config.toDict(), sort_keys=True, indent=2))
zf.writestr('libs.txt', json.dumps(collect_stats())) zf.writestr('libs.txt', json.dumps(collect_stats(), sort_keys=True, indent=2, cls=lazyEncoder))
for fp in file_list: for fp in file_list:
zf.write(fp, os.path.basename(fp)) zf.write(fp, os.path.basename(fp))
memory_zip.seek(0) memory_zip.seek(0)

View File

@ -39,6 +39,7 @@ from flask_babel import lazy_gettext as N_
from flask_babel import get_locale from flask_babel import get_locale
from flask_login import current_user, login_required from flask_login import current_user, login_required
from sqlalchemy.exc import OperationalError, IntegrityError from sqlalchemy.exc import OperationalError, IntegrityError
from sqlalchemy.orm.exc import StaleDataError
from . import constants, logger, isoLanguages, gdriveutils, uploader, helper, kobo_sync_status from . import constants, logger, isoLanguages, gdriveutils, uploader, helper, kobo_sync_status
from . import config, ub, db, calibre_db from . import config, ub, db, calibre_db
@ -221,7 +222,7 @@ def edit_book(book_id):
calibre_db.session.rollback() calibre_db.session.rollback()
flash(str(e), category="error") flash(str(e), category="error")
return redirect(url_for('web.show_book', book_id=book.id)) return redirect(url_for('web.show_book', book_id=book.id))
except (OperationalError, IntegrityError) as e: except (OperationalError, IntegrityError, StaleDataError) as e:
log.error_or_exception("Database error: {}".format(e)) log.error_or_exception("Database error: {}".format(e))
calibre_db.session.rollback() calibre_db.session.rollback()
flash(_(u"Database error: %(error)s.", error=e.orig), category="error") flash(_(u"Database error: %(error)s.", error=e.orig), category="error")
@ -295,7 +296,7 @@ def upload():
else: else:
resp = {"location": url_for('web.show_book', book_id=book_id)} resp = {"location": url_for('web.show_book', book_id=book_id)}
return Response(json.dumps(resp), mimetype='application/json') return Response(json.dumps(resp), mimetype='application/json')
except (OperationalError, IntegrityError) as e: except (OperationalError, IntegrityError, StaleDataError) as e:
calibre_db.session.rollback() calibre_db.session.rollback()
log.error_or_exception("Database error: {}".format(e)) log.error_or_exception("Database error: {}".format(e))
flash(_(u"Database error: %(error)s.", error=e.orig), category="error") flash(_(u"Database error: %(error)s.", error=e.orig), category="error")
@ -443,7 +444,7 @@ def edit_list_book(param):
if param == 'title' and vals.get('checkT') == "false": if param == 'title' and vals.get('checkT') == "false":
book.sort = sort_param book.sort = sort_param
calibre_db.session.commit() calibre_db.session.commit()
except (OperationalError, IntegrityError) as e: except (OperationalError, IntegrityError, StaleDataError) as e:
calibre_db.session.rollback() calibre_db.session.rollback()
log.error_or_exception("Database error: {}".format(e)) log.error_or_exception("Database error: {}".format(e))
ret = Response(json.dumps({'success': False, ret = Response(json.dumps({'success': False,
@ -556,7 +557,7 @@ def table_xchange_author_title():
book.last_modified = datetime.utcnow() book.last_modified = datetime.utcnow()
try: try:
calibre_db.session.commit() calibre_db.session.commit()
except (OperationalError, IntegrityError) as e: except (OperationalError, IntegrityError, StaleDataError) as e:
calibre_db.session.rollback() calibre_db.session.rollback()
log.error_or_exception("Database error: %s", e) log.error_or_exception("Database error: %s", e)
return json.dumps({'success': False}) return json.dumps({'success': False})
@ -1190,7 +1191,7 @@ def upload_single_file(file_request, book, book_id):
calibre_db.session.add(db_format) calibre_db.session.add(db_format)
calibre_db.session.commit() calibre_db.session.commit()
calibre_db.update_title_sort(config) calibre_db.update_title_sort(config)
except (OperationalError, IntegrityError) as e: except (OperationalError, IntegrityError, StaleDataError) as e:
calibre_db.session.rollback() calibre_db.session.rollback()
log.error_or_exception("Database error: {}".format(e)) log.error_or_exception("Database error: {}".format(e))
flash(_(u"Database error: %(error)s.", error=e.orig), category="error") flash(_(u"Database error: %(error)s.", error=e.orig), category="error")

View File

@ -33,6 +33,7 @@ try:
except ImportError: except ImportError:
from sqlalchemy.ext.declarative import declarative_base from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy.exc import OperationalError, InvalidRequestError, IntegrityError from sqlalchemy.exc import OperationalError, InvalidRequestError, IntegrityError
from sqlalchemy.orm.exc import StaleDataError
from sqlalchemy.sql.expression import text from sqlalchemy.sql.expression import text
try: try:
@ -318,7 +319,7 @@ def getFolderId(path, drive):
session.commit() session.commit()
else: else:
currentFolderId = storedPathName.gdrive_id currentFolderId = storedPathName.gdrive_id
except (OperationalError, IntegrityError) as ex: except (OperationalError, IntegrityError, StaleDataError) as ex:
log.error_or_exception('Database error: {}'.format(ex)) log.error_or_exception('Database error: {}'.format(ex))
session.rollback() session.rollback()
except ApiRequestError as ex: except ApiRequestError as ex:

View File

@ -322,12 +322,12 @@ def edit_book_read_status(book_id, read_status=None):
try: try:
calibre_db.update_title_sort(config) calibre_db.update_title_sort(config)
book = calibre_db.get_filtered_book(book_id) book = calibre_db.get_filtered_book(book_id)
read_status = getattr(book, 'custom_column_' + str(config.config_read_column)) book_read_status = getattr(book, 'custom_column_' + str(config.config_read_column))
if len(read_status): if len(book_read_status):
if read_status is None: if read_status is None:
read_status[0].value = not read_status[0].value book_read_status[0].value = not book_read_status[0].value
else: else:
read_status[0].value = read_status is True book_read_status[0].value = read_status is True
calibre_db.session.commit() calibre_db.session.commit()
else: else:
cc_class = db.cc_classes[config.config_read_column] cc_class = db.cc_classes[config.config_read_column]
@ -336,8 +336,8 @@ def edit_book_read_status(book_id, read_status=None):
calibre_db.session.commit() calibre_db.session.commit()
except (KeyError, AttributeError, IndexError): except (KeyError, AttributeError, IndexError):
log.error( log.error(
"Custom Column No.{} is not existing in calibre database".format(config.config_read_column)) "Custom Column No.{} does not exist in calibre database".format(config.config_read_column))
return "Custom Column No.{} is not existing in calibre database".format(config.config_read_column) return "Custom Column No.{} does not exist in calibre database".format(config.config_read_column)
except (OperationalError, InvalidRequestError) as ex: except (OperationalError, InvalidRequestError) as ex:
calibre_db.session.rollback() calibre_db.session.rollback()
log.error(u"Read status could not set: {}".format(ex)) log.error(u"Read status could not set: {}".format(ex))

View File

@ -107,7 +107,7 @@ def generate_auth_token(user_id):
for book in books: for book in books:
formats = [data.format for data in book.data] formats = [data.format for data in book.data]
if not 'KEPUB' in formats and config.config_kepubifypath and 'EPUB' in formats: if 'KEPUB' not in formats and config.config_kepubifypath and 'EPUB' in formats:
helper.convert_book_format(book.id, config.config_calibre_dir, 'EPUB', 'KEPUB', current_user.name) helper.convert_book_format(book.id, config.config_calibre_dir, 'EPUB', 'KEPUB', current_user.name)
return render_title_template( return render_title_template(

View File

@ -101,19 +101,6 @@ def get_sidebar_config(kwargs=None):
"show_text": _('Show Books List'), "config_show": content}) "show_text": _('Show Books List'), "config_show": content})
return sidebar, simple return sidebar, simple
'''def get_readbooks_ids():
if not config.config_read_column:
readBooks = ub.session.query(ub.ReadBook).filter(ub.ReadBook.user_id == int(current_user.id))\
.filter(ub.ReadBook.read_status == ub.ReadBook.STATUS_FINISHED).all()
return frozenset([x.book_id for x in readBooks])
else:
try:
readBooks = calibre_db.session.query(db.cc_classes[config.config_read_column])\
.filter(db.cc_classes[config.config_read_column].value == True).all()
return frozenset([x.book for x in readBooks])
except (KeyError, AttributeError, IndexError):
log.error("Custom Column No.{} is not existing in calibre database".format(config.config_read_column))
return []'''
# Returns the template for rendering and includes the instance name # Returns the template for rendering and includes the instance name
def render_title_template(*args, **kwargs): def render_title_template(*args, **kwargs):

View File

@ -22,7 +22,7 @@ from flask import session as flask_session
from flask_login import current_user from flask_login import current_user
from flask_babel import format_date from flask_babel import format_date
from flask_babel import gettext as _ from flask_babel import gettext as _
from sqlalchemy.sql.expression import func, not_, and_, or_, text from sqlalchemy.sql.expression import func, not_, and_, or_, text, true
from sqlalchemy.sql.functions import coalesce from sqlalchemy.sql.functions import coalesce
from . import logger, db, calibre_db, config, ub from . import logger, db, calibre_db, config, ub
@ -119,32 +119,23 @@ def adv_search_ratings(q, rating_high, rating_low):
return q return q
def adv_search_read_status(q, read_status): def adv_search_read_status(read_status):
if read_status: if not config.config_read_column:
if config.config_read_column:
try:
if read_status == "True": if read_status == "True":
q = q.join(db.cc_classes[config.config_read_column], isouter=True) \ db_filter = and_(ub.ReadBook.user_id == int(current_user.id),
.filter(db.cc_classes[config.config_read_column].value == True)
else:
q = q.join(db.cc_classes[config.config_read_column], isouter=True) \
.filter(coalesce(db.cc_classes[config.config_read_column].value, False) != True)
except (KeyError, AttributeError):
log.error(u"Custom Column No.%d is not existing in calibre database", config.config_read_column)
flash(_("Custom Column No.%(column)d is not existing in calibre database",
column=config.config_read_column),
category="error")
return q
else:
if read_status == "True":
q = q.join(ub.ReadBook, db.Books.id == ub.ReadBook.book_id, isouter=True) \
.filter(ub.ReadBook.user_id == int(current_user.id),
ub.ReadBook.read_status == ub.ReadBook.STATUS_FINISHED) ub.ReadBook.read_status == ub.ReadBook.STATUS_FINISHED)
else: else:
q = q.join(ub.ReadBook, db.Books.id == ub.ReadBook.book_id, isouter=True) \ db_filter = coalesce(ub.ReadBook.read_status, 0) != ub.ReadBook.STATUS_FINISHED
.filter(ub.ReadBook.user_id == int(current_user.id), else:
coalesce(ub.ReadBook.read_status, 0) != ub.ReadBook.STATUS_FINISHED) try:
return q if read_status == "True":
db_filter = db.cc_classes[config.config_read_column].value == True
else:
db_filter = coalesce(db.cc_classes[config.config_read_column].value, False) != True
except (KeyError, AttributeError, IndexError):
log.error("Custom Column No.{} does not exist in calibre database".format(config.config_read_column))
return true()
return db_filter
def adv_search_extension(q, include_extension_inputs, exclude_extension_inputs): def adv_search_extension(q, include_extension_inputs, exclude_extension_inputs):
@ -238,23 +229,7 @@ def render_adv_search_results(term, offset=None, order=None, limit=None):
cc = calibre_db.get_cc_columns(config, filter_config_custom_read=True) cc = calibre_db.get_cc_columns(config, filter_config_custom_read=True)
calibre_db.session.connection().connection.connection.create_function("lower", 1, db.lcase) calibre_db.session.connection().connection.connection.create_function("lower", 1, db.lcase)
if not config.config_read_column: query = calibre_db.generate_linked_query(config.config_read_column, db.Books)
query = (calibre_db.session.query(db.Books, ub.ArchivedBook.is_archived, ub.ReadBook).select_from(db.Books)
.outerjoin(ub.ReadBook, and_(db.Books.id == ub.ReadBook.book_id,
int(current_user.id) == ub.ReadBook.user_id)))
else:
try:
read_column = cc[config.config_read_column]
query = (calibre_db.session.query(db.Books, ub.ArchivedBook.is_archived, read_column.value)
.select_from(db.Books)
.outerjoin(read_column, read_column.book == db.Books.id))
except (KeyError, AttributeError):
log.error("Custom Column No.%d is not existing in calibre database", config.config_read_column)
# Skip linking read column
query = calibre_db.session.query(db.Books, ub.ArchivedBook.is_archived, None)
query = query.outerjoin(ub.ArchivedBook, and_(db.Books.id == ub.ArchivedBook.book_id,
int(current_user.id) == ub.ArchivedBook.user_id))
q = query.outerjoin(db.books_series_link, db.Books.id == db.books_series_link.c.book)\ q = query.outerjoin(db.books_series_link, db.Books.id == db.books_series_link.c.book)\
.outerjoin(db.Series)\ .outerjoin(db.Series)\
.filter(calibre_db.common_filters(True)) .filter(calibre_db.common_filters(True))
@ -324,7 +299,7 @@ def render_adv_search_results(term, offset=None, order=None, limit=None):
q = q.filter(func.datetime(db.Books.pubdate) > func.datetime(pub_start)) q = q.filter(func.datetime(db.Books.pubdate) > func.datetime(pub_start))
if pub_end: if pub_end:
q = q.filter(func.datetime(db.Books.pubdate) < func.datetime(pub_end)) q = q.filter(func.datetime(db.Books.pubdate) < func.datetime(pub_end))
q = adv_search_read_status(q, read_status) q = q.filter(adv_search_read_status(read_status))
if publisher: if publisher:
q = q.filter(db.Books.publishers.any(func.lower(db.Publishers.name).ilike("%" + publisher + "%"))) q = q.filter(db.Books.publishers.any(func.lower(db.Publishers.name).ilike("%" + publisher + "%")))
q = adv_search_tag(q, tags['include_tag'], tags['exclude_tag']) q = adv_search_tag(q, tags['include_tag'], tags['exclude_tag'])

View File

@ -188,6 +188,8 @@ class TaskConvert(CalibreTask):
log.info("ebook converter failed with error while converting book") log.info("ebook converter failed with error while converting book")
if not error_message: if not error_message:
error_message = N_('Ebook converter failed with unknown error') error_message = N_('Ebook converter failed with unknown error')
else:
log.error(error_message)
self._handleError(error_message) self._handleError(error_message)
return return
@ -273,7 +275,10 @@ class TaskConvert(CalibreTask):
return N_("Convert") return N_("Convert")
def __str__(self): def __str__(self):
if self.ereader_mail:
return "Convert {} {}".format(self.book_id, self.ereader_mail) return "Convert {} {}".format(self.book_id, self.ereader_mail)
else:
return "Convert {}".format(self.book_id)
@property @property
def is_cancellable(self): def is_cancellable(self):

View File

@ -715,9 +715,9 @@ def render_read_books(page, are_read, as_xml=False, order=None):
else: else:
db_filter = coalesce(db.cc_classes[config.config_read_column].value, False) != True db_filter = coalesce(db.cc_classes[config.config_read_column].value, False) != True
except (KeyError, AttributeError, IndexError): except (KeyError, AttributeError, IndexError):
log.error("Custom Column No.{} is not existing in calibre database".format(config.config_read_column)) log.error("Custom Column No.{} does not exist in calibre database".format(config.config_read_column))
if not as_xml: if not as_xml:
flash(_("Custom Column No.%(column)d is not existing in calibre database", flash(_("Custom Column No.%(column)d does not exist in calibre database",
column=config.config_read_column), column=config.config_read_column),
category="error") category="error")
return redirect(url_for("web.index")) return redirect(url_for("web.index"))

View File

@ -41,4 +41,4 @@ natsort>=2.2.0,<8.2.0
comicapi>=2.2.0,<2.3.0 comicapi>=2.2.0,<2.3.0
# Kobo integration # Kobo integration
jsonschema>=3.2.0,<4.6.0 jsonschema>=3.2.0,<4.7.0

View File

@ -42,7 +42,7 @@ install_requires =
werkzeug<2.1.0 werkzeug<2.1.0
Babel>=1.3,<3.0 Babel>=1.3,<3.0
Flask-Babel>=0.11.1,<2.1.0 Flask-Babel>=0.11.1,<2.1.0
Flask-Login>=0.3.2,<0.6.1 Flask-Login>=0.3.2,<0.6.2
Flask-Principal>=0.3.2,<0.5.1 Flask-Principal>=0.3.2,<0.5.1
backports_abc>=0.4 backports_abc>=0.4
Flask>=1.0.2,<2.1.0 Flask>=1.0.2,<2.1.0
@ -97,5 +97,5 @@ comics =
natsort>=2.2.0,<8.2.0 natsort>=2.2.0,<8.2.0
comicapi>=2.2.0,<2.3.0 comicapi>=2.2.0,<2.3.0
kobo = kobo =
jsonschema>=3.2.0,<4.5.0 jsonschema>=3.2.0,<4.6.0

File diff suppressed because it is too large Load Diff