-
- {{entry.title|shortentitle}}
+
+ {{entry.Books.title|shortentitle}}
- {% for author in entry.authors %}
+ {% for author in entry.Books.authors %}
{% if loop.index > g.config_authors_max and g.config_authors_max != 0 %}
{% if not loop.first %}
&
@@ -72,24 +72,24 @@
{{author.name.replace('|',',')|shortentitle(30)}}
{% endif %}
{% endfor %}
- {% for format in entry.data %}
+ {% for format in entry.Books.data %}
{% if format.format|lower in g.constants.EXTENSIONS_AUDIO %}
{% endif %}
{% endfor %}
- {% if entry.series.__len__() > 0 %}
+ {% if entry.Books.series.__len__() > 0 %}
-
- {{entry.series[0].name}}
+
+ {{entry.Books.series[0].name}}
- ({{entry.series_index|formatseriesindex}})
+ ({{entry.Books.series_index|formatseriesindex}})
{% endif %}
- {% if entry.ratings.__len__() > 0 %}
+ {% if entry.Books.ratings.__len__() > 0 %}
- {% for number in range((entry.ratings[0].rating/2)|int(2)) %}
+ {% for number in range((entry.Books.ratings[0].rating/2)|int(2)) %}
{% if loop.last and loop.index < 5 %}
{% for numer in range(5 - loop.index) %}
diff --git a/cps/ub.py b/cps/ub.py
index 27e9ef8f..e52ce201 100644
--- a/cps/ub.py
+++ b/cps/ub.py
@@ -90,7 +90,7 @@ def delete_user_session(user_id, session_key):
session.query(User_Sessions).filter(User_Sessions.user_id==user_id,
User_Sessions.session_key==session_key).delete()
session.commit()
- except (exc.OperationalError, exc.InvalidRequestError):
+ except (exc.OperationalError, exc.InvalidRequestError) as e:
session.rollback()
log.exception(e)
@@ -112,6 +112,12 @@ def store_ids(result):
ids.append(element.id)
searched_ids[current_user.id] = ids
+def store_combo_ids(result):
+ ids = list()
+ for element in result:
+ ids.append(element[0].id)
+ searched_ids[current_user.id] = ids
+
class UserBase:
diff --git a/cps/updater.py b/cps/updater.py
index 9090263f..2166b334 100644
--- a/cps/updater.py
+++ b/cps/updater.py
@@ -53,12 +53,10 @@ class Updater(threading.Thread):
def __init__(self):
threading.Thread.__init__(self)
self.paused = False
- # self.pause_cond = threading.Condition(threading.Lock())
self.can_run = threading.Event()
self.pause()
self.status = -1
self.updateIndex = None
- # self.run()
def get_current_version_info(self):
if config.config_updatechannel == constants.UPDATE_STABLE:
@@ -85,15 +83,15 @@ class Updater(threading.Thread):
log.debug(u'Extracting zipfile')
tmp_dir = gettempdir()
z.extractall(tmp_dir)
- foldername = os.path.join(tmp_dir, z.namelist()[0])[:-1]
- if not os.path.isdir(foldername):
+ folder_name = os.path.join(tmp_dir, z.namelist()[0])[:-1]
+ if not os.path.isdir(folder_name):
self.status = 11
log.info(u'Extracted contents of zipfile not found in temp folder')
self.pause()
return False
self.status = 4
log.debug(u'Replacing files')
- if self.update_source(foldername, constants.BASE_DIR):
+ if self.update_source(folder_name, constants.BASE_DIR):
self.status = 6
log.debug(u'Preparing restart of server')
time.sleep(2)
@@ -184,29 +182,30 @@ class Updater(threading.Thread):
return rf
@classmethod
- def check_permissions(cls, root_src_dir, root_dst_dir):
+ def check_permissions(cls, root_src_dir, root_dst_dir, log_function):
access = True
remove_path = len(root_src_dir) + 1
for src_dir, __, files in os.walk(root_src_dir):
root_dir = os.path.join(root_dst_dir, src_dir[remove_path:])
- # Skip non existing folders on check
- if not os.path.isdir(root_dir): # root_dir.lstrip(os.sep).startswith('.') or
+ # Skip non-existing folders on check
+ if not os.path.isdir(root_dir):
continue
- if not os.access(root_dir, os.R_OK|os.W_OK):
- log.debug("Missing permissions for {}".format(root_dir))
+ if not os.access(root_dir, os.R_OK | os.W_OK):
+ log_function("Missing permissions for {}".format(root_dir))
access = False
for file_ in files:
curr_file = os.path.join(root_dir, file_)
- # Skip non existing files on check
- if not os.path.isfile(curr_file): # or curr_file.startswith('.'):
+ # Skip non-existing files on check
+ if not os.path.isfile(curr_file): # or curr_file.startswith('.'):
continue
- if not os.access(curr_file, os.R_OK|os.W_OK):
- log.debug("Missing permissions for {}".format(curr_file))
+ if not os.access(curr_file, os.R_OK | os.W_OK):
+ log_function("Missing permissions for {}".format(curr_file))
access = False
return access
@classmethod
- def moveallfiles(cls, root_src_dir, root_dst_dir):
+ def move_all_files(cls, root_src_dir, root_dst_dir):
+ permission = None
new_permissions = os.stat(root_dst_dir)
log.debug('Performing Update on OS-System: %s', sys.platform)
change_permissions = not (sys.platform == "win32" or sys.platform == "darwin")
@@ -258,18 +257,11 @@ class Updater(threading.Thread):
def update_source(self, source, destination):
# destination files
old_list = list()
- exclude = (
- os.sep + 'app.db', os.sep + 'calibre-web.log1', os.sep + 'calibre-web.log2', os.sep + 'gdrive.db',
- os.sep + 'vendor', os.sep + 'calibre-web.log', os.sep + '.git', os.sep + 'client_secrets.json',
- os.sep + 'gdrive_credentials', os.sep + 'settings.yaml', os.sep + 'venv', os.sep + 'virtualenv',
- os.sep + 'access.log', os.sep + 'access.log1', os.sep + 'access.log2',
- os.sep + '.calibre-web.log.swp', os.sep + '_sqlite3.so', os.sep + 'cps' + os.sep + '.HOMEDIR',
- os.sep + 'gmail.json'
- )
+ exclude = self._add_excluded_files(log.info)
additional_path = self.is_venv()
if additional_path:
- exclude = exclude + (additional_path,)
-
+ exclude.append(additional_path)
+ exclude = tuple(exclude)
# check if we are in a package, rename cps.py to __init__.py
if constants.HOME_CONFIG:
shutil.move(os.path.join(source, 'cps.py'), os.path.join(source, '__init__.py'))
@@ -293,8 +285,8 @@ class Updater(threading.Thread):
remove_items = self.reduce_dirs(rf, new_list)
- if self.check_permissions(source, destination):
- self.moveallfiles(source, destination)
+ if self.check_permissions(source, destination, log.debug):
+ self.move_all_files(source, destination)
for item in remove_items:
item_path = os.path.join(destination, item[1:])
@@ -332,6 +324,12 @@ class Updater(threading.Thread):
log.debug("Stable version: {}".format(constants.STABLE_VERSION))
return constants.STABLE_VERSION # Current version
+ @classmethod
+ def dry_run(cls):
+ cls._add_excluded_files(print)
+ cls.check_permissions(constants.BASE_DIR, constants.BASE_DIR, print)
+ print("\n*** Finished ***")
+
@staticmethod
def _populate_parent_commits(update_data, status, locale, tz, parents):
try:
@@ -340,6 +338,7 @@ class Updater(threading.Thread):
remaining_parents_cnt = 10
except (IndexError, KeyError):
remaining_parents_cnt = None
+ parent_commit = None
if remaining_parents_cnt is not None:
while True:
@@ -391,6 +390,30 @@ class Updater(threading.Thread):
status['message'] = _(u'General error')
return status, update_data
+ @staticmethod
+ def _add_excluded_files(log_function):
+ excluded_files = [
+ os.sep + 'app.db', os.sep + 'calibre-web.log1', os.sep + 'calibre-web.log2', os.sep + 'gdrive.db',
+ os.sep + 'vendor', os.sep + 'calibre-web.log', os.sep + '.git', os.sep + 'client_secrets.json',
+ os.sep + 'gdrive_credentials', os.sep + 'settings.yaml', os.sep + 'venv', os.sep + 'virtualenv',
+ os.sep + 'access.log', os.sep + 'access.log1', os.sep + 'access.log2',
+ os.sep + '.calibre-web.log.swp', os.sep + '_sqlite3.so', os.sep + 'cps' + os.sep + '.HOMEDIR',
+ os.sep + 'gmail.json', os.sep + 'exclude.txt'
+ ]
+ try:
+ with open(os.path.join(constants.BASE_DIR, "exclude.txt"), "r") as f:
+ lines = f.readlines()
+ for line in lines:
+ processed_line = line.strip("\n\r ").strip("\"'").lstrip("\\/ ").\
+ replace("\\", os.sep).replace("/", os.sep)
+ if os.path.exists(os.path.join(constants.BASE_DIR, processed_line)):
+ excluded_files.append(os.sep + processed_line)
+ else:
+ log_function("File list for updater: {} not found".format(line))
+ except (PermissionError, FileNotFoundError):
+ log_function("Excluded file list for updater not found, or not accessible")
+ return excluded_files
+
def _nightly_available_updates(self, request_method, locale):
tz = datetime.timedelta(seconds=time.timezone if (time.localtime().tm_isdst == 0) else time.altzone)
if request_method == "GET":
@@ -449,7 +472,7 @@ class Updater(threading.Thread):
return ''
def _stable_updater_set_status(self, i, newer, status, parents, commit):
- if i == -1 and newer == False:
+ if i == -1 and newer is False:
status.update({
'update': True,
'success': True,
@@ -458,7 +481,7 @@ class Updater(threading.Thread):
'history': parents
})
self.updateFile = commit[0]['zipball_url']
- elif i == -1 and newer == True:
+ elif i == -1 and newer is True:
status.update({
'update': True,
'success': True,
@@ -495,6 +518,7 @@ class Updater(threading.Thread):
return status, parents
def _stable_available_updates(self, request_method):
+ status = None
if request_method == "GET":
parents = []
# repository_url = 'https://api.github.com/repos/flatpak/flatpak/releases' # test URL
@@ -539,7 +563,7 @@ class Updater(threading.Thread):
except ValueError:
current_version[2] = int(current_version[2].split(' ')[0])-1
- # Check if major versions are identical search for newest non equal commit and update to this one
+ # Check if major versions are identical search for newest non-equal commit and update to this one
if major_version_update == current_version[0]:
if (minor_version_update == current_version[1] and
patch_version_update > current_version[2]) or \
@@ -552,7 +576,7 @@ class Updater(threading.Thread):
i -= 1
continue
if major_version_update > current_version[0]:
- # found update update to last version before major update, unless current version is on last version
+ # found update to last version before major update, unless current version is on last version
# before major update
if i == (len(commit) - 1):
i -= 1
diff --git a/cps/web.py b/cps/web.py
index e139848e..6d33f809 100644
--- a/cps/web.py
+++ b/cps/web.py
@@ -50,7 +50,8 @@ from . import calibre_db, kobo_sync_status
from .gdriveutils import getFileFromEbooksFolder, do_gdrive_download
from .helper import check_valid_domain, render_task_status, check_email, check_username, \
get_cc_columns, get_book_cover, get_download_link, send_mail, generate_random_password, \
- send_registration_mail, check_send_to_kindle, check_read_formats, tags_filters, reset_password, valid_email
+ send_registration_mail, check_send_to_kindle, check_read_formats, tags_filters, reset_password, valid_email, \
+ edit_book_read_status
from .pagination import Pagination
from .redirect import redirect_back
from .usermanagement import login_required_if_no_ano
@@ -154,46 +155,12 @@ def bookmark(book_id, book_format):
@web.route("/ajax/toggleread/
", methods=['POST'])
@login_required
def toggle_read(book_id):
- if not config.config_read_column:
- book = ub.session.query(ub.ReadBook).filter(and_(ub.ReadBook.user_id == int(current_user.id),
- ub.ReadBook.book_id == book_id)).first()
- if book:
- if book.read_status == ub.ReadBook.STATUS_FINISHED:
- book.read_status = ub.ReadBook.STATUS_UNREAD
- else:
- book.read_status = ub.ReadBook.STATUS_FINISHED
- else:
- readBook = ub.ReadBook(user_id=current_user.id, book_id = book_id)
- readBook.read_status = ub.ReadBook.STATUS_FINISHED
- book = readBook
- if not book.kobo_reading_state:
- kobo_reading_state = ub.KoboReadingState(user_id=current_user.id, book_id=book_id)
- kobo_reading_state.current_bookmark = ub.KoboBookmark()
- kobo_reading_state.statistics = ub.KoboStatistics()
- book.kobo_reading_state = kobo_reading_state
- ub.session.merge(book)
- ub.session_commit("Book {} readbit toggled".format(book_id))
+ message = edit_book_read_status(book_id)
+ if message:
+ return message, 400
else:
- try:
- calibre_db.update_title_sort(config)
- book = calibre_db.get_filtered_book(book_id)
- read_status = getattr(book, 'custom_column_' + str(config.config_read_column))
- if len(read_status):
- read_status[0].value = not read_status[0].value
- calibre_db.session.commit()
- else:
- cc_class = db.cc_classes[config.config_read_column]
- new_cc = cc_class(value=1, book=book_id)
- calibre_db.session.add(new_cc)
- calibre_db.session.commit()
- except (KeyError, AttributeError):
- log.error(u"Custom Column No.%d is not existing in calibre database", config.config_read_column)
- return "Custom Column No.{} is not existing in calibre database".format(config.config_read_column), 400
- except (OperationalError, InvalidRequestError) as e:
- calibre_db.session.rollback()
- log.error(u"Read status could not set: {}".format(e))
- return "Read status could not set: {}".format(e), 400
- return ""
+ return message
+
@web.route("/ajax/togglearchived/", methods=['POST'])
@login_required
@@ -409,6 +376,7 @@ def render_books_list(data, sort, book_id, page):
else:
website = data or "newest"
entries, random, pagination = calibre_db.fill_indexpage(page, 0, db.Books, True, order[0],
+ False, 0,
db.books_series_link,
db.Books.id == db.books_series_link.c.book,
db.Series)
@@ -422,6 +390,7 @@ def render_rated_books(page, book_id, order):
db.Books,
db.Books.ratings.any(db.Ratings.rating > 9),
order[0],
+ False, 0,
db.books_series_link,
db.Books.id == db.books_series_link.c.book,
db.Series)
@@ -490,6 +459,7 @@ def render_downloaded_books(page, order, user_id):
db.Books,
ub.Downloads.user_id == user_id,
order[0],
+ False, 0,
db.books_series_link,
db.Books.id == db.books_series_link.c.book,
db.Series,
@@ -516,6 +486,7 @@ def render_author_books(page, author_id, order):
db.Books,
db.Books.authors.any(db.Authors.id == author_id),
[order[0][0], db.Series.name, db.Books.series_index],
+ False, 0,
db.books_series_link,
db.Books.id == db.books_series_link.c.book,
db.Series)
@@ -534,7 +505,6 @@ def render_author_books(page, author_id, order):
if services.goodreads_support and config.config_use_goodreads:
author_info = services.goodreads_support.get_author_info(author_name)
other_books = services.goodreads_support.get_other_books(author_info, entries)
-
return render_title_template('author.html', entries=entries, pagination=pagination, id=author_id,
title=_(u"Author: %(name)s", name=author_name), author=author_info,
other_books=other_books, page="author", order=order[1])
@@ -547,6 +517,7 @@ def render_publisher_books(page, book_id, order):
db.Books,
db.Books.publishers.any(db.Publishers.id == book_id),
[db.Series.name, order[0][0], db.Books.series_index],
+ False, 0,
db.books_series_link,
db.Books.id == db.books_series_link.c.book,
db.Series)
@@ -608,6 +579,7 @@ def render_category_books(page, book_id, order):
db.Books,
db.Books.tags.any(db.Tags.id == book_id),
[order[0][0], db.Series.name, db.Books.series_index],
+ False, 0,
db.books_series_link,
db.Books.id == db.books_series_link.c.book,
db.Series)
@@ -643,6 +615,7 @@ def render_read_books(page, are_read, as_xml=False, order=None):
db.Books,
db_filter,
sort,
+ False, 0,
db.books_series_link,
db.Books.id == db.books_series_link.c.book,
db.Series,
@@ -657,6 +630,7 @@ def render_read_books(page, are_read, as_xml=False, order=None):
db.Books,
db_filter,
sort,
+ False, 0,
db.books_series_link,
db.Books.id == db.books_series_link.c.book,
db.Series,
@@ -694,11 +668,12 @@ def render_archived_books(page, sort):
archived_filter = db.Books.id.in_(archived_book_ids)
- entries, random, pagination = calibre_db.fill_indexpage_with_archived_books(page, 0,
- db.Books,
+ entries, random, pagination = calibre_db.fill_indexpage_with_archived_books(page, db.Books,
+ 0,
archived_filter,
order,
- allow_show_archived=True)
+ True,
+ False, 0)
name = _(u'Archived Books') + ' (' + str(len(archived_book_ids)) + ')'
pagename = "archived"
@@ -739,7 +714,13 @@ def render_prepare_search_form(cc):
def render_search_results(term, offset=None, order=None, limit=None):
join = db.books_series_link, db.Books.id == db.books_series_link.c.book, db.Series
- entries, result_count, pagination = calibre_db.get_search_results(term, offset, order, limit, *join)
+ entries, result_count, pagination = calibre_db.get_search_results(term,
+ offset,
+ order,
+ limit,
+ False,
+ config.config_read_column,
+ *join)
return render_title_template('search.html',
searchterm=term,
pagination=pagination,
@@ -795,13 +776,13 @@ def list_books():
state = json.loads(request.args.get("state", "[]"))
elif sort == "tags":
order = [db.Tags.name.asc()] if order == "asc" else [db.Tags.name.desc()]
- join = db.books_tags_link,db.Books.id == db.books_tags_link.c.book, db.Tags
+ join = db.books_tags_link, db.Books.id == db.books_tags_link.c.book, db.Tags
elif sort == "series":
order = [db.Series.name.asc()] if order == "asc" else [db.Series.name.desc()]
- join = db.books_series_link,db.Books.id == db.books_series_link.c.book, db.Series
+ join = db.books_series_link, db.Books.id == db.books_series_link.c.book, db.Series
elif sort == "publishers":
order = [db.Publishers.name.asc()] if order == "asc" else [db.Publishers.name.desc()]
- join = db.books_publishers_link,db.Books.id == db.books_publishers_link.c.book, db.Publishers
+ join = db.books_publishers_link, db.Books.id == db.books_publishers_link.c.book, db.Publishers
elif sort == "authors":
order = [db.Authors.name.asc(), db.Series.name, db.Books.series_index] if order == "asc" \
else [db.Authors.name.desc(), db.Series.name.desc(), db.Books.series_index.desc()]
@@ -815,25 +796,62 @@ def list_books():
elif not state:
order = [db.Books.timestamp.desc()]
- total_count = filtered_count = calibre_db.session.query(db.Books).filter(calibre_db.common_filters(False)).count()
-
+ total_count = filtered_count = calibre_db.session.query(db.Books).filter(calibre_db.common_filters(allow_show_archived=True)).count()
if state is not None:
if search:
- books = calibre_db.search_query(search).all()
+ books = calibre_db.search_query(search, config.config_read_column).all()
filtered_count = len(books)
else:
- books = calibre_db.session.query(db.Books).filter(calibre_db.common_filters()).all()
- entries = calibre_db.get_checkbox_sorted(books, state, off, limit, order)
+ if not config.config_read_column:
+ books = (calibre_db.session.query(db.Books, ub.ReadBook.read_status, ub.ArchivedBook.is_archived)
+ .select_from(db.Books)
+ .outerjoin(ub.ReadBook,
+ and_(ub.ReadBook.user_id == int(current_user.id),
+ ub.ReadBook.book_id == db.Books.id)))
+ else:
+ try:
+ read_column = db.cc_classes[config.config_read_column]
+ books = (calibre_db.session.query(db.Books, read_column.value, ub.ArchivedBook.is_archived)
+ .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", read_column)
+ # Skip linking read column and return None instead of read status
+ books =calibre_db.session.query(db.Books, None, ub.ArchivedBook.is_archived)
+ books = (books.outerjoin(ub.ArchivedBook, and_(db.Books.id == ub.ArchivedBook.book_id,
+ int(current_user.id) == ub.ArchivedBook.user_id))
+ .filter(calibre_db.common_filters(allow_show_archived=True)).all())
+ entries = calibre_db.get_checkbox_sorted(books, state, off, limit, order, True)
elif search:
- entries, filtered_count, __ = calibre_db.get_search_results(search, off, [order,''], limit, *join)
+ entries, filtered_count, __ = calibre_db.get_search_results(search,
+ off,
+ [order,''],
+ limit,
+ True,
+ config.config_read_column,
+ *join)
else:
- entries, __, __ = calibre_db.fill_indexpage((int(off) / (int(limit)) + 1), limit, db.Books, True, order, *join)
+ entries, __, __ = calibre_db.fill_indexpage_with_archived_books((int(off) / (int(limit)) + 1),
+ db.Books,
+ limit,
+ True,
+ order,
+ True,
+ True,
+ config.config_read_column,
+ *join)
+ result = list()
for entry in entries:
- for index in range(0, len(entry.languages)):
- entry.languages[index].language_name = isoLanguages.get_language_name(get_locale(), entry.languages[
+ val = entry[0]
+ val.read_status = entry[1] == ub.ReadBook.STATUS_FINISHED
+ val.is_archived = entry[2] is True
+ for index in range(0, len(val.languages)):
+ val.languages[index].language_name = isoLanguages.get_language_name(get_locale(), val.languages[
index].lang_code)
- table_entries = {'totalNotFiltered': total_count, 'total': filtered_count, "rows": entries}
+ result.append(val)
+
+ table_entries = {'totalNotFiltered': total_count, 'total': filtered_count, "rows": result}
js_list = json.dumps(table_entries, cls=db.AlchemyEncoder)
response = make_response(js_list)
@@ -843,8 +861,6 @@ def list_books():
@web.route("/ajax/table_settings", methods=['POST'])
@login_required
def update_table_settings():
- # vals = request.get_json()
- # ToDo: Save table settings
current_user.view_settings['table'] = json.loads(request.data)
try:
try:
@@ -1055,13 +1071,6 @@ def get_tasks_status():
return render_title_template('tasks.html', entries=answer, title=_(u"Tasks"), page="tasks")
-# method is available without login and not protected by CSRF to make it easy reachable
-@app.route("/reconnect", methods=['GET'])
-def reconnect():
- calibre_db.reconnect_db(config, ub.app_DB_path)
- return json.dumps({})
-
-
# ################################### Search functions ################################################################
@web.route("/search", methods=["GET"])
@@ -1259,7 +1268,24 @@ def render_adv_search_results(term, offset=None, order=None, limit=None):
cc = get_cc_columns(filter_config_custom_read=True)
calibre_db.session.connection().connection.connection.create_function("lower", 1, db.lcase)
- q = calibre_db.session.query(db.Books).outerjoin(db.books_series_link, db.Books.id == db.books_series_link.c.book)\
+ if not config.config_read_column:
+ 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)\
.outerjoin(db.Series)\
.filter(calibre_db.common_filters(True))
@@ -1323,7 +1349,7 @@ def render_adv_search_results(term, offset=None, order=None, limit=None):
rating_high,
rating_low,
read_status)
- q = q.filter()
+ # q = q.filter()
if author_name:
q = q.filter(db.Books.authors.any(func.lower(db.Authors.name).ilike("%" + author_name + "%")))
if book_title:
@@ -1354,7 +1380,7 @@ def render_adv_search_results(term, offset=None, order=None, limit=None):
q = q.order_by(*sort).all()
flask_session['query'] = json.dumps(term)
- ub.store_ids(q)
+ ub.store_combo_ids(q)
result_count = len(q)
if offset is not None and limit is not None:
offset = int(offset)
@@ -1363,16 +1389,16 @@ def render_adv_search_results(term, offset=None, order=None, limit=None):
else:
offset = 0
limit_all = result_count
+ entries = calibre_db.order_authors(q[offset:limit_all], list_return=True, combined=True)
return render_title_template('search.html',
adv_searchterm=searchterm,
pagination=pagination,
- entries=q[offset:limit_all],
+ entries=entries,
result_count=result_count,
title=_(u"Advanced Search"), page="advsearch",
order=order[1])
-
@web.route("/advsearch", methods=['GET'])
@login_required_if_no_ano
def advanced_search_form():
@@ -1748,63 +1774,40 @@ def read_book(book_id, book_format):
@web.route("/book/")
@login_required_if_no_ano
def show_book(book_id):
- entries = calibre_db.get_filtered_book(book_id, allow_show_archived=True)
+ entries = calibre_db.get_book_read_archived(book_id, config.config_read_column, allow_show_archived=True)
if entries:
- for index in range(0, len(entries.languages)):
- entries.languages[index].language_name = isoLanguages.get_language_name(get_locale(), entries.languages[
+ read_book = entries[1]
+ archived_book = entries[2]
+ entry = entries[0]
+ entry.read_status = read_book == ub.ReadBook.STATUS_FINISHED
+ entry.is_archived = archived_book
+ for index in range(0, len(entry.languages)):
+ entry.languages[index].language_name = isoLanguages.get_language_name(get_locale(), entry.languages[
index].lang_code)
cc = get_cc_columns(filter_config_custom_read=True)
book_in_shelfs = []
shelfs = ub.session.query(ub.BookShelf).filter(ub.BookShelf.book_id == book_id).all()
- for entry in shelfs:
- book_in_shelfs.append(entry.shelf)
+ for sh in shelfs:
+ book_in_shelfs.append(sh.shelf)
- if not current_user.is_anonymous:
- if not config.config_read_column:
- matching_have_read_book = ub.session.query(ub.ReadBook). \
- filter(and_(ub.ReadBook.user_id == int(current_user.id), ub.ReadBook.book_id == book_id)).all()
- have_read = len(
- matching_have_read_book) > 0 and matching_have_read_book[0].read_status == ub.ReadBook.STATUS_FINISHED
- else:
- try:
- matching_have_read_book = getattr(entries, 'custom_column_' + str(config.config_read_column))
- have_read = len(matching_have_read_book) > 0 and matching_have_read_book[0].value
- except (KeyError, AttributeError):
- log.error("Custom Column No.%d is not existing in calibre database", config.config_read_column)
- have_read = None
+ entry.tags = sort(entry.tags, key=lambda tag: tag.name)
- archived_book = ub.session.query(ub.ArchivedBook).\
- filter(and_(ub.ArchivedBook.user_id == int(current_user.id),
- ub.ArchivedBook.book_id == book_id)).first()
- is_archived = archived_book and archived_book.is_archived
+ entry.authors = calibre_db.order_authors([entry])
- else:
- have_read = None
- is_archived = None
+ entry.kindle_list = check_send_to_kindle(entry)
+ entry.reader_list = check_read_formats(entry)
- entries.tags = sort(entries.tags, key=lambda tag: tag.name)
-
- entries = calibre_db.order_authors(entries)
-
- kindle_list = check_send_to_kindle(entries)
- reader_list = check_read_formats(entries)
-
- audioentries = []
- for media_format in entries.data:
+ entry.audioentries = []
+ for media_format in entry.data:
if media_format.format.lower() in constants.EXTENSIONS_AUDIO:
- audioentries.append(media_format.format.lower())
+ entry.audioentries.append(media_format.format.lower())
return render_title_template('detail.html',
- entry=entries,
- audioentries=audioentries,
+ entry=entry,
cc=cc,
is_xhr=request.headers.get('X-Requested-With')=='XMLHttpRequest',
- title=entries.title,
+ title=entry.title,
books_shelfs=book_in_shelfs,
- have_read=have_read,
- is_archived=is_archived,
- kindle_list=kindle_list,
- reader_list=reader_list,
page="book")
else:
log.debug(u"Oops! Selected book title is unavailable. File does not exist or is not accessible")
diff --git a/exclude.txt b/exclude.txt
new file mode 100644
index 00000000..e69de29b
diff --git a/optional-requirements.txt b/optional-requirements.txt
index f894fcc1..2370bc7e 100644
--- a/optional-requirements.txt
+++ b/optional-requirements.txt
@@ -10,7 +10,7 @@ pyasn1>=0.1.9,<0.5.0
PyDrive2>=1.3.1,<1.11.0
PyYAML>=3.12
rsa>=3.4.2,<4.9.0
-six>=1.10.0,<1.17.0
+# six>=1.10.0,<1.17.0
# Gmail
google-auth-oauthlib>=0.4.3,<0.5.0
@@ -31,6 +31,11 @@ SQLAlchemy-Utils>=0.33.5,<0.39.0
# metadata extraction
rarfile>=2.7
scholarly>=1.2.0,<1.6
+markdown2>=2.0.0,<2.5.0
+html2text>=2020.1.16,<2022.1.1
+python-dateutil>=2.1,<2.9.0
+beautifulsoup4>=4.0.1,<4.2.0
+cchardet>=2.0.0,<2.2.0
# Comics
natsort>=2.2.0,<8.2.0
diff --git a/setup.cfg b/setup.cfg
index d17642dd..5053d19f 100644
--- a/setup.cfg
+++ b/setup.cfg
@@ -86,6 +86,11 @@ oauth =
metadata =
rarfile>=2.7
scholarly>=1.2.0,<1.6
+ markdown2>=2.0.0,<2.5.0
+ html2text>=2020.1.16,<2022.1.1
+ python-dateutil>=2.1,<2.9.0
+ beautifulsoup4>=4.0.1,<4.2.0
+ cchardet>=2.0.0,<2.2.0
comics =
natsort>=2.2.0,<8.2.0
comicapi>=2.2.0,<2.3.0
diff --git a/test/Calibre-Web TestSummary_Linux.html b/test/Calibre-Web TestSummary_Linux.html
index 8d8966ad..bd12238c 100644
--- a/test/Calibre-Web TestSummary_Linux.html
+++ b/test/Calibre-Web TestSummary_Linux.html
@@ -37,20 +37,20 @@
-
Start Time: 2022-01-24 05:56:04
+
Start Time: 2022-02-01 20:32:05
-
Stop Time: 2022-01-24 09:54:16
+
Stop Time: 2022-02-02 00:57:41
-
Duration: 3h 17 min
+
Duration: 3h 39 min
@@ -236,13 +236,13 @@
TestCli |
- 8 |
- 8 |
+ 9 |
+ 9 |
0 |
0 |
0 |
- Detail
+ Detail
|
@@ -304,7 +304,7 @@
- TestCli - test_environ_port_setting
+ TestCli - test_dryrun_update
|
PASS |
@@ -312,6 +312,15 @@
+
+ TestCli - test_environ_port_setting
+ |
+ PASS |
+
+
+
+
+
TestCli - test_settingsdb_not_writeable
|
@@ -561,11 +570,11 @@
-
+
TestEbookConvertCalibreGDrive |
6 |
- 5 |
- 1 |
+ 6 |
+ 0 |
0 |
0 |
@@ -593,33 +602,11 @@
- |
+
TestEbookConvertCalibreGDrive - test_convert_only
|
-
-
-
-
-
-
-
- |
+ PASS |
@@ -1296,14 +1283,14 @@ AssertionError: 'Failed' != 'Finished'
- TestEditBooksList |
- 18 |
- 18 |
+ TestEditAuthors |
+ 6 |
+ 6 |
0 |
0 |
0 |
- Detail
+ Detail
|
@@ -1311,7 +1298,7 @@ AssertionError: 'Failed' != 'Finished'
- TestEditBooksList - test_bookslist_edit_author
+ TestEditAuthors - test_change_capital_co_author
|
PASS |
@@ -1320,7 +1307,7 @@ AssertionError: 'Failed' != 'Finished'
- TestEditBooksList - test_bookslist_edit_categories
+ TestEditAuthors - test_change_capital_one_author_one_book
|
PASS |
@@ -1329,7 +1316,7 @@ AssertionError: 'Failed' != 'Finished'
- TestEditBooksList - test_bookslist_edit_comment
+ TestEditAuthors - test_change_capital_one_author_two_books
|
PASS |
@@ -1338,7 +1325,7 @@ AssertionError: 'Failed' != 'Finished'
- TestEditBooksList - test_bookslist_edit_cust_category
+ TestEditAuthors - test_change_capital_rename_co_author
|
PASS |
@@ -1347,7 +1334,7 @@ AssertionError: 'Failed' != 'Finished'
- TestEditBooksList - test_bookslist_edit_cust_comment
+ TestEditAuthors - test_change_capital_rename_two_co_authors
|
PASS |
@@ -1355,6 +1342,164 @@ AssertionError: 'Failed' != 'Finished'
+
+ TestEditAuthors - test_rename_capital_on_upload
+ |
+ PASS |
+
+
+
+
+
+
+ TestEditAuthorsGdrive |
+ 6 |
+ 5 |
+ 1 |
+ 0 |
+ 0 |
+
+ Detail
+ |
+
+
+
+
+
+
+ TestEditAuthorsGdrive - test_change_capital_co_author
+ |
+ PASS |
+
+
+
+
+
+
+ TestEditAuthorsGdrive - test_change_capital_one_author_one_book
+ |
+ PASS |
+
+
+
+
+
+
+ TestEditAuthorsGdrive - test_change_capital_one_author_two_books
+ |
+ PASS |
+
+
+
+
+
+
+ TestEditAuthorsGdrive - test_change_capital_rename_co_author
+ |
+ PASS |
+
+
+
+
+
+
+ TestEditAuthorsGdrive - test_change_capital_rename_two_co_authors
+ |
+ PASS |
+
+
+
+
+
+
+ TestEditAuthorsGdrive - test_rename_capital_on_upload
+ |
+
+
+
+
+
+
+
+ |
+
+
+
+
+
+
+ TestEditBooksList |
+ 18 |
+ 18 |
+ 0 |
+ 0 |
+ 0 |
+
+ Detail
+ |
+
+
+
+
+
+
+ TestEditBooksList - test_bookslist_edit_author
+ |
+ PASS |
+
+
+
+
+
+
+ TestEditBooksList - test_bookslist_edit_categories
+ |
+ PASS |
+
+
+
+
+
+
+ TestEditBooksList - test_bookslist_edit_comment
+ |
+ PASS |
+
+
+
+
+
+
+ TestEditBooksList - test_bookslist_edit_cust_category
+ |
+ PASS |
+
+
+
+
+
+
+ TestEditBooksList - test_bookslist_edit_cust_comment
+ |
+ PASS |
+
+
+
+
+
TestEditBooksList - test_bookslist_edit_cust_enum
|
@@ -1363,7 +1508,7 @@ AssertionError: 'Failed' != 'Finished'
-
+
TestEditBooksList - test_bookslist_edit_cust_float
|
@@ -1372,7 +1517,7 @@ AssertionError: 'Failed' != 'Finished'
-
+
TestEditBooksList - test_bookslist_edit_cust_int
|
@@ -1381,7 +1526,7 @@ AssertionError: 'Failed' != 'Finished'
-
+
TestEditBooksList - test_bookslist_edit_cust_ratings
|
@@ -1390,7 +1535,7 @@ AssertionError: 'Failed' != 'Finished'
-
+
TestEditBooksList - test_bookslist_edit_cust_text
|
@@ -1399,7 +1544,7 @@ AssertionError: 'Failed' != 'Finished'
-
+
TestEditBooksList - test_bookslist_edit_languages
|
@@ -1408,7 +1553,7 @@ AssertionError: 'Failed' != 'Finished'
-
+
TestEditBooksList - test_bookslist_edit_publisher
|
@@ -1417,7 +1562,7 @@ AssertionError: 'Failed' != 'Finished'
-
+
TestEditBooksList - test_bookslist_edit_series
|
@@ -1426,7 +1571,7 @@ AssertionError: 'Failed' != 'Finished'
-
+
TestEditBooksList - test_bookslist_edit_seriesindex
|
@@ -1435,7 +1580,7 @@ AssertionError: 'Failed' != 'Finished'
-
+
TestEditBooksList - test_bookslist_edit_title
|
@@ -1444,7 +1589,7 @@ AssertionError: 'Failed' != 'Finished'
-
+
TestEditBooksList - test_list_visibility
|
@@ -1453,7 +1598,7 @@ AssertionError: 'Failed' != 'Finished'
-
+
TestEditBooksList - test_restricted_rights
|
@@ -1462,7 +1607,7 @@ AssertionError: 'Failed' != 'Finished'
-
+
TestEditBooksList - test_search_books_list
|
@@ -1472,25 +1617,45 @@ AssertionError: 'Failed' != 'Finished'
-
+
TestLoadMetadata |
1 |
- 1 |
0 |
+ 1 |
0 |
0 |
- Detail
+ Detail
|
-
+
TestLoadMetadata - test_load_metadata
|
- PASS |
+
+
+
+
+
+
+
+ |
@@ -1504,13 +1669,13 @@ AssertionError: 'Failed' != 'Finished'
0 |
0 |
- Detail
+ Detail
|
-
+
TestEditBooksOnGdrive - test_download_book
|
@@ -1519,7 +1684,7 @@ AssertionError: 'Failed' != 'Finished'
-
+
TestEditBooksOnGdrive - test_edit_author
|
@@ -1528,7 +1693,7 @@ AssertionError: 'Failed' != 'Finished'
-
+
TestEditBooksOnGdrive - test_edit_category
|
@@ -1537,7 +1702,7 @@ AssertionError: 'Failed' != 'Finished'
-
+
TestEditBooksOnGdrive - test_edit_comments
|
@@ -1546,7 +1711,7 @@ AssertionError: 'Failed' != 'Finished'
-
+
TestEditBooksOnGdrive - test_edit_custom_bool
|
@@ -1555,7 +1720,7 @@ AssertionError: 'Failed' != 'Finished'
-
+
TestEditBooksOnGdrive - test_edit_custom_categories
|
@@ -1564,7 +1729,7 @@ AssertionError: 'Failed' != 'Finished'
-
+
TestEditBooksOnGdrive - test_edit_custom_float
|
@@ -1573,7 +1738,7 @@ AssertionError: 'Failed' != 'Finished'
-
+
TestEditBooksOnGdrive - test_edit_custom_int
|
@@ -1582,7 +1747,7 @@ AssertionError: 'Failed' != 'Finished'
-
+
TestEditBooksOnGdrive - test_edit_custom_rating
|
@@ -1591,7 +1756,7 @@ AssertionError: 'Failed' != 'Finished'
-
+
TestEditBooksOnGdrive - test_edit_custom_single_select
|
@@ -1600,7 +1765,7 @@ AssertionError: 'Failed' != 'Finished'
-
+
TestEditBooksOnGdrive - test_edit_custom_text
|
@@ -1609,7 +1774,7 @@ AssertionError: 'Failed' != 'Finished'
-
+
TestEditBooksOnGdrive - test_edit_language
|
@@ -1618,7 +1783,7 @@ AssertionError: 'Failed' != 'Finished'
-
+
TestEditBooksOnGdrive - test_edit_publisher
|
@@ -1627,7 +1792,7 @@ AssertionError: 'Failed' != 'Finished'
-
+
TestEditBooksOnGdrive - test_edit_rating
|
@@ -1636,7 +1801,7 @@ AssertionError: 'Failed' != 'Finished'
-
+
TestEditBooksOnGdrive - test_edit_series
|
@@ -1645,7 +1810,7 @@ AssertionError: 'Failed' != 'Finished'
-
+
TestEditBooksOnGdrive - test_edit_title
|
@@ -1654,7 +1819,7 @@ AssertionError: 'Failed' != 'Finished'
-
+
TestEditBooksOnGdrive - test_upload_book_epub
|
@@ -1663,7 +1828,7 @@ AssertionError: 'Failed' != 'Finished'
-
+
TestEditBooksOnGdrive - test_upload_book_lit
|
@@ -1672,7 +1837,7 @@ AssertionError: 'Failed' != 'Finished'
-
+
TestEditBooksOnGdrive - test_upload_cover_hdd
|
@@ -1681,7 +1846,7 @@ AssertionError: 'Failed' != 'Finished'
-
+
TestEditBooksOnGdrive - test_watch_metadata
|
@@ -1699,13 +1864,13 @@ AssertionError: 'Failed' != 'Finished'
0 |
0 |
- Detail
+ Detail
|
-
+
TestLoadMetadataScholar - test_load_metadata
|
@@ -1723,13 +1888,13 @@ AssertionError: 'Failed' != 'Finished'
0 |
0 |
- Detail
+ Detail
|
-
+
TestSTARTTLS - test_STARTTLS
|
@@ -1738,7 +1903,7 @@ AssertionError: 'Failed' != 'Finished'
-
+
TestSTARTTLS - test_STARTTLS_SSL_setup_error
|
@@ -1747,7 +1912,7 @@ AssertionError: 'Failed' != 'Finished'
-
+
TestSTARTTLS - test_STARTTLS_resend_password
|
@@ -1765,13 +1930,13 @@ AssertionError: 'Failed' != 'Finished'
0 |
0 |
- Detail
+ Detail
|
-
+
TestSSL - test_SSL_None_setup_error
|
@@ -1780,7 +1945,7 @@ AssertionError: 'Failed' != 'Finished'
-
+
TestSSL - test_SSL_STARTTLS_setup_error
|
@@ -1789,7 +1954,7 @@ AssertionError: 'Failed' != 'Finished'
-
+
TestSSL - test_SSL_logging_email
|
@@ -1798,7 +1963,7 @@ AssertionError: 'Failed' != 'Finished'
-
+
TestSSL - test_SSL_non_admin_user
|
@@ -1807,7 +1972,7 @@ AssertionError: 'Failed' != 'Finished'
-
+
TestSSL - test_SSL_only
|
@@ -1816,7 +1981,7 @@ AssertionError: 'Failed' != 'Finished'
-
+
TestSSL - test_email_limit
|
@@ -1825,7 +1990,7 @@ AssertionError: 'Failed' != 'Finished'
-
+
TestSSL - test_filepicker_two_file
|
@@ -1843,13 +2008,13 @@ AssertionError: 'Failed' != 'Finished'
0 |
0 |
- Detail
+ Detail
|
-
+
TestBookDatabase - test_invalid_book_path
|
@@ -1867,13 +2032,13 @@ AssertionError: 'Failed' != 'Finished'
0 |
0 |
- Detail
+ Detail
|
-
+
TestErrorReadColumn - test_invalid_custom_column
|
@@ -1882,7 +2047,7 @@ AssertionError: 'Failed' != 'Finished'
-
+
TestErrorReadColumn - test_invalid_custom_read_column
|
@@ -1900,13 +2065,13 @@ AssertionError: 'Failed' != 'Finished'
0 |
1 |
- Detail
+ Detail
|
-
+
TestFilePicker - test_filepicker_limited_file
|
@@ -1915,19 +2080,19 @@ AssertionError: 'Failed' != 'Finished'
-
+
TestFilePicker - test_filepicker_new_file
|
-
+
- |