diff --git a/cps/config_sql.py b/cps/config_sql.py index 4ea4a9e0..6d5b1177 100644 --- a/cps/config_sql.py +++ b/cps/config_sql.py @@ -483,8 +483,10 @@ def autodetect_calibre_binaries(): else: calibre_path = ["/opt/calibre/"] for element in calibre_path: - supported_binary_paths = [os.path.join(element, binary) for binary in constants.SUPPORTED_CALIBRE_BINARIES.values()] - if all(os.path.isfile(binary_path) and os.access(binary_path, os.X_OK) for binary_path in supported_binary_paths): + supported_binary_paths = [os.path.join(element, binary) + for binary in constants.SUPPORTED_CALIBRE_BINARIES.values()] + if all(os.path.isfile(binary_path) and os.access(binary_path, os.X_OK) + for binary_path in supported_binary_paths): values = [process_wait([binary_path, "--version"], pattern=r'\(calibre (.*)\)') for binary_path in supported_binary_paths] if all(values): diff --git a/cps/opds.py b/cps/opds.py index b13b0570..2226895c 100644 --- a/cps/opds.py +++ b/cps/opds.py @@ -24,14 +24,14 @@ import datetime import json from urllib.parse import unquote_plus -from flask import Blueprint, request, render_template, make_response, abort, Response +from flask import Blueprint, request, render_template, make_response, abort, Response, g from flask_login import current_user from flask_babel import get_locale from flask_babel import gettext as _ from sqlalchemy.sql.expression import func, text, or_, and_, true from sqlalchemy.exc import InvalidRequestError, OperationalError -from . import logger, config, db, calibre_db, ub, isoLanguages +from . import logger, config, db, calibre_db, ub, isoLanguages, constants from .usermanagement import requires_basic_auth_if_no_ano from .helper import get_download_link, get_book_cover from .pagination import Pagination @@ -94,6 +94,8 @@ def feed_letter_books(book_id): @opds.route("/opds/new") @requires_basic_auth_if_no_ano def feed_new(): + if not current_user.check_visibility(constants.SIDEBAR_RECENT): + abort(404) off = request.args.get("offset") or 0 entries, __, pagination = calibre_db.fill_indexpage((int(off) / (int(config.config_books_per_page)) + 1), 0, db.Books, True, [db.Books.timestamp.desc()], @@ -104,6 +106,8 @@ def feed_new(): @opds.route("/opds/discover") @requires_basic_auth_if_no_ano def feed_discover(): + if not current_user.check_visibility(constants.SIDEBAR_RANDOM): + abort(404) query = calibre_db.generate_linked_query(config.config_read_column, db.Books) entries = query.filter(calibre_db.common_filters()).order_by(func.random()).limit(config.config_books_per_page) pagination = Pagination(1, config.config_books_per_page, int(config.config_books_per_page)) @@ -113,6 +117,8 @@ def feed_discover(): @opds.route("/opds/rated") @requires_basic_auth_if_no_ano def feed_best_rated(): + if not current_user.check_visibility(constants.SIDEBAR_BEST_RATED): + abort(404) off = request.args.get("offset") or 0 entries, __, pagination = calibre_db.fill_indexpage((int(off) / (int(config.config_books_per_page)) + 1), 0, db.Books, db.Books.ratings.any(db.Ratings.rating > 9), @@ -124,6 +130,8 @@ def feed_best_rated(): @opds.route("/opds/hot") @requires_basic_auth_if_no_ano def feed_hot(): + if not current_user.check_visibility(constants.SIDEBAR_HOT): + abort(404) off = request.args.get("offset") or 0 all_books = ub.session.query(ub.Downloads, func.count(ub.Downloads.book_id)).order_by( func.count(ub.Downloads.book_id).desc()).group_by(ub.Downloads.book_id) @@ -146,12 +154,16 @@ def feed_hot(): @opds.route("/opds/author") @requires_basic_auth_if_no_ano def feed_authorindex(): + if not current_user.check_visibility(constants.SIDEBAR_AUTHOR): + abort(404) return render_element_index(db.Authors.sort, db.books_authors_link, 'opds.feed_letter_author') @opds.route("/opds/author/letter/") @requires_basic_auth_if_no_ano def feed_letter_author(book_id): + if not current_user.check_visibility(constants.SIDEBAR_AUTHOR): + abort(404) off = request.args.get("offset") or 0 letter = true() if book_id == "00" else func.upper(db.Authors.sort).startswith(book_id) entries = calibre_db.session.query(db.Authors).join(db.books_authors_link).join(db.Books)\ @@ -173,6 +185,8 @@ def feed_author(book_id): @opds.route("/opds/publisher") @requires_basic_auth_if_no_ano def feed_publisherindex(): + if not current_user.check_visibility(constants.SIDEBAR_PUBLISHER): + abort(404) off = request.args.get("offset") or 0 entries = calibre_db.session.query(db.Publishers)\ .join(db.books_publishers_link)\ @@ -194,12 +208,16 @@ def feed_publisher(book_id): @opds.route("/opds/category") @requires_basic_auth_if_no_ano def feed_categoryindex(): + if not current_user.check_visibility(constants.SIDEBAR_CATEGORY): + abort(404) return render_element_index(db.Tags.name, db.books_tags_link, 'opds.feed_letter_category') @opds.route("/opds/category/letter/") @requires_basic_auth_if_no_ano def feed_letter_category(book_id): + if not current_user.check_visibility(constants.SIDEBAR_CATEGORY): + abort(404) off = request.args.get("offset") or 0 letter = true() if book_id == "00" else func.upper(db.Tags.name).startswith(book_id) entries = calibre_db.session.query(db.Tags)\ @@ -223,12 +241,16 @@ def feed_category(book_id): @opds.route("/opds/series") @requires_basic_auth_if_no_ano def feed_seriesindex(): + if not current_user.check_visibility(constants.SIDEBAR_SERIES): + abort(404) return render_element_index(db.Series.sort, db.books_series_link, 'opds.feed_letter_series') @opds.route("/opds/series/letter/") @requires_basic_auth_if_no_ano def feed_letter_series(book_id): + if not current_user.check_visibility(constants.SIDEBAR_SERIES): + abort(404) off = request.args.get("offset") or 0 letter = true() if book_id == "00" else func.upper(db.Series.sort).startswith(book_id) entries = calibre_db.session.query(db.Series)\ @@ -258,6 +280,8 @@ def feed_series(book_id): @opds.route("/opds/ratings") @requires_basic_auth_if_no_ano def feed_ratingindex(): + if not current_user.check_visibility(constants.SIDEBAR_RATING): + abort(404) off = request.args.get("offset") or 0 entries = calibre_db.session.query(db.Ratings, func.count('books_ratings_link.book').label('count'), (db.Ratings.rating / 2).label('name')) \ @@ -284,6 +308,8 @@ def feed_ratings(book_id): @opds.route("/opds/formats") @requires_basic_auth_if_no_ano def feed_formatindex(): + if not current_user.check_visibility(constants.SIDEBAR_FORMAT): + abort(404) off = request.args.get("offset") or 0 entries = calibre_db.session.query(db.Data).join(db.Books)\ .filter(calibre_db.common_filters()) \ @@ -291,7 +317,6 @@ def feed_formatindex(): .order_by(db.Data.format).all() pagination = Pagination((int(off) / (int(config.config_books_per_page)) + 1), config.config_books_per_page, len(entries)) - element = list() for entry in entries: element.append(FeedObject(entry.format, entry.format)) @@ -314,6 +339,8 @@ def feed_format(book_id): @opds.route("/opds/language/") @requires_basic_auth_if_no_ano def feed_languagesindex(): + if not current_user.check_visibility(constants.SIDEBAR_LANGUAGE): + abort(404) off = request.args.get("offset") or 0 if current_user.filter_language() == "all": languages = calibre_db.speaking_language() @@ -341,6 +368,8 @@ def feed_languages(book_id): @opds.route("/opds/shelfindex") @requires_basic_auth_if_no_ano def feed_shelfindex(): + if not (current_user.is_authenticated or g.allow_anonymous): + abort(404) off = request.args.get("offset") or 0 shelf = ub.session.query(ub.Shelf).filter( or_(ub.Shelf.is_public == 1, ub.Shelf.user_id == current_user.id)).order_by(ub.Shelf.name).all() @@ -353,6 +382,8 @@ def feed_shelfindex(): @opds.route("/opds/shelf/") @requires_basic_auth_if_no_ano def feed_shelf(book_id): + if not (current_user.is_authenticated or g.allow_anonymous): + abort(404) off = request.args.get("offset") or 0 if current_user.is_anonymous: shelf = ub.session.query(ub.Shelf).filter(ub.Shelf.is_public == 1, @@ -436,6 +467,8 @@ def feed_get_cover(book_id): @opds.route("/opds/readbooks") @requires_basic_auth_if_no_ano def feed_read_books(): + if not (current_user.check_visibility(constants.SIDEBAR_READ_AND_UNREAD) and not current_user.is_anonymous): + return abort(403) off = request.args.get("offset") or 0 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) @@ -444,6 +477,8 @@ def feed_read_books(): @opds.route("/opds/unreadbooks") @requires_basic_auth_if_no_ano def feed_unread_books(): + if not (current_user.check_visibility(constants.SIDEBAR_READ_AND_UNREAD) and not current_user.is_anonymous): + return abort(403) off = request.args.get("offset") or 0 result, pagination = render_read_books(int(off) / (int(config.config_books_per_page)) + 1, False, True) return render_xml_template('feed.xml', entries=result, pagination=pagination) @@ -477,7 +512,7 @@ def feed_search(term): def render_xml_template(*args, **kwargs): # ToDo: return time in current timezone similar to %z 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, constants=constants.sidebar_settings, *args, **kwargs) response = make_response(xml) response.headers["Content-Type"] = "application/atom+xml; charset=utf-8" return response diff --git a/cps/templates/index.xml b/cps/templates/index.xml index cae3f629..4c088e37 100644 --- a/cps/templates/index.xml +++ b/cps/templates/index.xml @@ -22,6 +22,7 @@ {{ current_time }} {{_('Books sorted alphabetically')}} + {% if current_user.check_visibility(g.constants.SIDEBAR_HOT) %} {{_('Hot Books')}} @@ -29,6 +30,8 @@ {{ current_time }} {{_('Popular publications from this catalog based on Downloads.')}} + {%endif %} + {% if current_user.check_visibility(g.constants.SIDEBAR_BEST_RATED) %} {{_('Top Rated Books')}} @@ -36,6 +39,8 @@ {{ current_time }} {{_('Popular publications from this catalog based on Rating.')}} + {%endif %} + {% if current_user.check_visibility(g.constants.SIDEBAR_RECENT) %} {{_('Recently added Books')}} @@ -43,6 +48,8 @@ {{ current_time }} {{_('The latest Books')}} + {%endif %} + {% if current_user.check_visibility(g.constants.SIDEBAR_RANDOM) %} {{_('Random Books')}} @@ -50,7 +57,8 @@ {{ current_time }} {{_('Show Random Books')}} - {% if not current_user.is_anonymous %} + {%endif %} + {% if current_user.check_visibility(g.constants.SIDEBAR_READ_AND_UNREAD) and not current_user.is_anonymous %} {{_('Read Books')}} @@ -66,6 +74,7 @@ {{_('Unread Books')}} {% endif %} + {% if current_user.check_visibility(g.constants.SIDEBAR_AUTHOR) %} {{_('Authors')}} @@ -73,13 +82,17 @@ {{ current_time }} {{_('Books ordered by Author')}} - + {% endif %} + {% if current_user.check_visibility(g.constants.SIDEBAR_PUBLISHER) %} + {{_('Publishers')}} {{url_for('opds.feed_publisherindex')}} {{ current_time }} {{_('Books ordered by publisher')}} + {% endif %} + {% if current_user.check_visibility(g.constants.SIDEBAR_CATEGORY) %} {{_('Categories')}} @@ -87,6 +100,8 @@ {{ current_time }} {{_('Books ordered by category')}} + {% endif %} + {% if current_user.check_visibility(g.constants.SIDEBAR_SERIES) %} {{_('Series')}} @@ -94,6 +109,8 @@ {{ current_time }} {{_('Books ordered by series')}} + {% endif %} + {% if current_user.check_visibility(g.constants.SIDEBAR_LANGUAGE) %} {{_('Languages')}} @@ -101,6 +118,8 @@ {{ current_time }} {{_('Books ordered by Languages')}} + {% endif %} + {% if current_user.check_visibility(g.constants.SIDEBAR_RATING) %} {{_('Ratings')}} @@ -108,7 +127,8 @@ {{ current_time }} {{_('Books ordered by Rating')}} - + {% endif %} + {% if current_user.check_visibility(g.constants.SIDEBAR_FORMAT) %} {{_('File formats')}} @@ -116,6 +136,8 @@ {{ current_time }} {{_('Books ordered by file formats')}} + {% endif %} + {% if current_user.is_authenticated or g.allow_anonymous %} {{_('Shelves')}} @@ -123,4 +145,5 @@ {{ current_time }} {{_('Books organized in shelves')}} + {% endif %} diff --git a/optional-requirements.txt b/optional-requirements.txt index 7b27a63e..4b34aef2 100644 --- a/optional-requirements.txt +++ b/optional-requirements.txt @@ -5,8 +5,8 @@ greenlet>=0.4.17,<3.1.0 httplib2>=0.9.2,<0.23.0 oauth2client>=4.0.0,<4.1.4 uritemplate>=3.0.0,<4.2.0 -pyasn1-modules>=0.0.8,<0.4.0 -pyasn1>=0.1.9,<0.6.0 +pyasn1-modules>=0.0.8,<0.5.0 +pyasn1>=0.1.9,<0.7.0 PyDrive2>=1.3.1,<1.20.0 PyYAML>=3.12,<6.1 rsa>=3.4.2,<4.10.0 @@ -28,11 +28,11 @@ Flask-Dance>=2.0.0,<7.1.0 SQLAlchemy-Utils>=0.33.5,<0.42.0 # metadata extraction -rarfile>=3.2,<4.2 +rarfile>=3.2,<5.0 scholarly>=1.2.0,<1.8 markdown2>=2.0.0,<2.5.0 html2text>=2020.1.16,<2024.2.26 -python-dateutil>=2.1,<2.9.0 +python-dateutil>=2.1,<2.10.0 beautifulsoup4>=4.0.1,<4.13.0 faust-cchardet>=2.1.18,<2.1.20 py7zr>=0.15.0,<0.21.0 @@ -42,4 +42,4 @@ natsort>=2.2.0,<8.5.0 comicapi>=2.2.0,<3.3.0 # Kobo integration -jsonschema>=3.2.0,<4.22.0 +jsonschema>=3.2.0,<4.23.0 diff --git a/setup.cfg b/setup.cfg index 89edcbde..ac832da9 100644 --- a/setup.cfg +++ b/setup.cfg @@ -73,8 +73,8 @@ gdrive = httplib2>=0.9.2,<0.23.0 oauth2client>=4.0.0,<4.1.4 uritemplate>=3.0.0,<4.2.0 - pyasn1-modules>=0.0.8,<0.4.0 - pyasn1>=0.1.9,<0.6.0 + pyasn1-modules>=0.0.8,<0.5.0 + pyasn1>=0.1.9,<0.7.0 PyDrive2>=1.3.1,<1.20.0 PyYAML>=3.12,<6.1 rsa>=3.4.2,<4.10.0 @@ -91,11 +91,11 @@ oauth = Flask-Dance>=2.0.0,<7.1.0 SQLAlchemy-Utils>=0.33.5,<0.42.0 metadata = - rarfile>=3.2,<4.2 + rarfile>=3.2,<5.0 scholarly>=1.2.0,<1.8 markdown2>=2.0.0,<2.5.0 html2text>=2020.1.16,<2024.2.26 - python-dateutil>=2.1,<2.9.0 + python-dateutil>=2.1,<2.10.0 beautifulsoup4>=4.0.1,<4.13.0 faust-cchardet>=2.1.18,<2.1.20 py7zr>=0.15.0,<0.21.0 @@ -103,5 +103,5 @@ comics = natsort>=2.2.0,<8.5.0 comicapi>=2.2.0,<3.3.0 kobo = - jsonschema>=3.2.0,<4.22.0 + jsonschema>=3.2.0,<4.23.0 diff --git a/test/Calibre-Web TestSummary_Linux.html b/test/Calibre-Web TestSummary_Linux.html index b1a91154..1fc3469c 100644 --- a/test/Calibre-Web TestSummary_Linux.html +++ b/test/Calibre-Web TestSummary_Linux.html @@ -37,20 +37,20 @@
-

Start Time: 2024-05-10 20:24:40

+

Start Time: 2024-05-11 18:39:24

-

Stop Time: 2024-05-11 03:33:47

+

Stop Time: 2024-05-12 01:48:22

-

Duration: 5h 58 min

+

Duration: 5h 59 min

@@ -234,11 +234,11 @@ - + TestBackupMetadata 21 - 20 - 1 + 21 + 0 0 0 @@ -320,35 +320,11 @@ - +
TestBackupMetadata - test_backup_change_book_series_index
- -
- FAIL -
- - - - + PASS @@ -2594,11 +2570,11 @@ IndexError: list index out of range - + TestGoodreads 3 - 2 - 1 + 3 + 0 0 0 @@ -2608,31 +2584,11 @@ IndexError: list index out of range - +
TestGoodreads - test_author_page
- -
- FAIL -
- - - - + PASS @@ -3577,13 +3533,13 @@ AssertionError: False is not true TestOPDSFeed - 24 - 24 + 26 + 26 0 0 0 - Detail + Detail @@ -3591,7 +3547,7 @@ AssertionError: False is not true -
TestOPDSFeed - test_opds
+
TestOPDSFeed - test_access_right_guest
PASS @@ -3600,7 +3556,7 @@ AssertionError: False is not true -
TestOPDSFeed - test_opds_author
+
TestOPDSFeed - test_access_right_user
PASS @@ -3609,7 +3565,7 @@ AssertionError: False is not true -
TestOPDSFeed - test_opds_books
+
TestOPDSFeed - test_opds
PASS @@ -3618,7 +3574,7 @@ AssertionError: False is not true -
TestOPDSFeed - test_opds_calibre_companion
+
TestOPDSFeed - test_opds_author
PASS @@ -3627,7 +3583,7 @@ AssertionError: False is not true -
TestOPDSFeed - test_opds_colon_password
+
TestOPDSFeed - test_opds_books
PASS @@ -3636,7 +3592,7 @@ AssertionError: False is not true -
TestOPDSFeed - test_opds_cover
+
TestOPDSFeed - test_opds_calibre_companion
PASS @@ -3645,7 +3601,7 @@ AssertionError: False is not true -
TestOPDSFeed - test_opds_download_book
+
TestOPDSFeed - test_opds_colon_password
PASS @@ -3654,7 +3610,7 @@ AssertionError: False is not true -
TestOPDSFeed - test_opds_formats
+
TestOPDSFeed - test_opds_cover
PASS @@ -3663,7 +3619,7 @@ AssertionError: False is not true -
TestOPDSFeed - test_opds_guest_user
+
TestOPDSFeed - test_opds_download_book
PASS @@ -3672,7 +3628,7 @@ AssertionError: False is not true -
TestOPDSFeed - test_opds_hot
+
TestOPDSFeed - test_opds_formats
PASS @@ -3681,7 +3637,7 @@ AssertionError: False is not true -
TestOPDSFeed - test_opds_language
+
TestOPDSFeed - test_opds_guest_user
PASS @@ -3690,7 +3646,7 @@ AssertionError: False is not true -
TestOPDSFeed - test_opds_non_admin
+
TestOPDSFeed - test_opds_hot
PASS @@ -3699,7 +3655,7 @@ AssertionError: False is not true -
TestOPDSFeed - test_opds_publisher
+
TestOPDSFeed - test_opds_language
PASS @@ -3708,7 +3664,7 @@ AssertionError: False is not true -
TestOPDSFeed - test_opds_random
+
TestOPDSFeed - test_opds_non_admin
PASS @@ -3717,7 +3673,7 @@ AssertionError: False is not true -
TestOPDSFeed - test_opds_ratings
+
TestOPDSFeed - test_opds_publisher
PASS @@ -3726,7 +3682,7 @@ AssertionError: False is not true -
TestOPDSFeed - test_opds_read_unread
+
TestOPDSFeed - test_opds_random
PASS @@ -3735,7 +3691,7 @@ AssertionError: False is not true -
TestOPDSFeed - test_opds_search
+
TestOPDSFeed - test_opds_ratings
PASS @@ -3744,7 +3700,7 @@ AssertionError: False is not true -
TestOPDSFeed - test_opds_series
+
TestOPDSFeed - test_opds_read_unread
PASS @@ -3753,7 +3709,7 @@ AssertionError: False is not true -
TestOPDSFeed - test_opds_shelf_access
+
TestOPDSFeed - test_opds_search
PASS @@ -3762,7 +3718,7 @@ AssertionError: False is not true -
TestOPDSFeed - test_opds_stats
+
TestOPDSFeed - test_opds_series
PASS @@ -3771,7 +3727,7 @@ AssertionError: False is not true -
TestOPDSFeed - test_opds_tags
+
TestOPDSFeed - test_opds_shelf_access
PASS @@ -3780,7 +3736,7 @@ AssertionError: False is not true -
TestOPDSFeed - test_opds_top_rated
+
TestOPDSFeed - test_opds_stats
PASS @@ -3789,7 +3745,7 @@ AssertionError: False is not true -
TestOPDSFeed - test_opds_unicode_user
+
TestOPDSFeed - test_opds_tags
PASS @@ -3797,6 +3753,24 @@ AssertionError: False is not true + +
TestOPDSFeed - test_opds_top_rated
+ + PASS + + + + + + +
TestOPDSFeed - test_opds_unicode_user
+ + PASS + + + + +
TestOPDSFeed - test_recently_added
@@ -4447,11 +4421,11 @@ AssertionError: False is not true - + TestThumbnails 8 - 7 - 0 + 6 + 1 0 1 @@ -4524,11 +4498,31 @@ AssertionError: False is not true - +
TestThumbnails - test_sideloaded_book
- PASS + +
+ FAIL +
+ + + + @@ -5611,9 +5605,9 @@ AssertionError: False is not true Total - 492 - 477 - 4 + 494 + 480 + 3 1 10   @@ -5799,7 +5793,7 @@ AssertionError: False is not true google-api-python-client - 2.128.0 + 2.129.0 TestBackupMetadataGdrive @@ -5829,7 +5823,7 @@ AssertionError: False is not true google-api-python-client - 2.128.0 + 2.129.0 TestCliGdrivedb @@ -5859,7 +5853,7 @@ AssertionError: False is not true google-api-python-client - 2.128.0 + 2.129.0 TestEbookConvertCalibreGDrive @@ -6141,7 +6135,7 @@ AssertionError: False is not true