Skip to content

Commit

Permalink
Fix for #2983 (visible OPDS categories reflect settings in web UI)
Browse files Browse the repository at this point in the history
Merge branch 'feature/OPDS-access'
  • Loading branch information
OzzieIsaacs committed May 12, 2024
2 parents 6f60ec7 + ee451fb commit 6760d69
Show file tree
Hide file tree
Showing 6 changed files with 168 additions and 114 deletions.
6 changes: 4 additions & 2 deletions cps/config_sql.py
Original file line number Diff line number Diff line change
Expand Up @@ -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):
Expand Down
43 changes: 39 additions & 4 deletions cps/opds.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,14 +24,14 @@
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
Expand Down Expand Up @@ -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()],
Expand All @@ -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))
Expand All @@ -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),
Expand All @@ -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)
Expand All @@ -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/<book_id>")
@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)\
Expand All @@ -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)\
Expand All @@ -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/<book_id>")
@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)\
Expand All @@ -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/<book_id>")
@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)\
Expand Down Expand Up @@ -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')) \
Expand All @@ -284,14 +308,15 @@ 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()) \
.group_by(db.Data.format)\
.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))
Expand All @@ -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()
Expand Down Expand Up @@ -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()
Expand All @@ -353,6 +382,8 @@ def feed_shelfindex():
@opds.route("/opds/shelf/<int:book_id>")
@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,
Expand Down Expand Up @@ -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)
Expand All @@ -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)
Expand Down Expand Up @@ -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
Expand Down
29 changes: 26 additions & 3 deletions cps/templates/index.xml
Original file line number Diff line number Diff line change
Expand Up @@ -22,35 +22,43 @@
<updated>{{ current_time }}</updated>
<content type="text">{{_('Books sorted alphabetically')}}</content>
</entry>
{% if current_user.check_visibility(g.constants.SIDEBAR_HOT) %}
<entry>
<title>{{_('Hot Books')}}</title>
<link href="{{url_for('opds.feed_hot')}}" type="application/atom+xml;profile=opds-catalog"/>
<id>{{url_for('opds.feed_hot')}}</id>
<updated>{{ current_time }}</updated>
<content type="text">{{_('Popular publications from this catalog based on Downloads.')}}</content>
</entry>
{%endif %}
{% if current_user.check_visibility(g.constants.SIDEBAR_BEST_RATED) %}
<entry>
<title>{{_('Top Rated Books')}}</title>
<link href="{{url_for('opds.feed_best_rated')}}" type="application/atom+xml;profile=opds-catalog"/>
<id>{{url_for('opds.feed_best_rated')}}</id>
<updated>{{ current_time }}</updated>
<content type="text">{{_('Popular publications from this catalog based on Rating.')}}</content>
</entry>
{%endif %}
{% if current_user.check_visibility(g.constants.SIDEBAR_RECENT) %}
<entry>
<title>{{_('Recently added Books')}}</title>
<link href="{{url_for('opds.feed_new')}}" type="application/atom+xml;profile=opds-catalog"/>
<id>{{url_for('opds.feed_new')}}</id>
<updated>{{ current_time }}</updated>
<content type="text">{{_('The latest Books')}}</content>
</entry>
{%endif %}
{% if current_user.check_visibility(g.constants.SIDEBAR_RANDOM) %}
<entry>
<title>{{_('Random Books')}}</title>
<link href="{{url_for('opds.feed_discover')}}" type="application/atom+xml;profile=opds-catalog"/>
<id>{{url_for('opds.feed_discover')}}</id>
<updated>{{ current_time }}</updated>
<content type="text">{{_('Show Random Books')}}</content>
</entry>
{% if not current_user.is_anonymous %}
{%endif %}
{% if current_user.check_visibility(g.constants.SIDEBAR_READ_AND_UNREAD) and not current_user.is_anonymous %}
<entry>
<title>{{_('Read Books')}}</title>
<link href="{{url_for('opds.feed_read_books')}}" type="application/atom+xml;profile=opds-catalog"/>
Expand All @@ -66,61 +74,76 @@
<content type="text">{{_('Unread Books')}}</content>
</entry>
{% endif %}
{% if current_user.check_visibility(g.constants.SIDEBAR_AUTHOR) %}
<entry>
<title>{{_('Authors')}}</title>
<link href="{{url_for('opds.feed_authorindex')}}" type="application/atom+xml;profile=opds-catalog"/>
<id>{{url_for('opds.feed_authorindex')}}</id>
<updated>{{ current_time }}</updated>
<content type="text">{{_('Books ordered by Author')}}</content>
</entry>
<entry>
{% endif %}
{% if current_user.check_visibility(g.constants.SIDEBAR_PUBLISHER) %}
<entry>
<title>{{_('Publishers')}}</title>
<link href="{{url_for('opds.feed_publisherindex')}}" type="application/atom+xml;profile=opds-catalog"/>
<id>{{url_for('opds.feed_publisherindex')}}</id>
<updated>{{ current_time }}</updated>
<content type="text">{{_('Books ordered by publisher')}}</content>
</entry>
{% endif %}
{% if current_user.check_visibility(g.constants.SIDEBAR_CATEGORY) %}
<entry>
<title>{{_('Categories')}}</title>
<link href="{{url_for('opds.feed_categoryindex')}}" type="application/atom+xml;profile=opds-catalog"/>
<id>{{url_for('opds.feed_categoryindex')}}</id>
<updated>{{ current_time }}</updated>
<content type="text">{{_('Books ordered by category')}}</content>
</entry>
{% endif %}
{% if current_user.check_visibility(g.constants.SIDEBAR_SERIES) %}
<entry>
<title>{{_('Series')}}</title>
<link href="{{url_for('opds.feed_seriesindex')}}" type="application/atom+xml;profile=opds-catalog"/>
<id>{{url_for('opds.feed_seriesindex')}}</id>
<updated>{{ current_time }}</updated>
<content type="text">{{_('Books ordered by series')}}</content>
</entry>
{% endif %}
{% if current_user.check_visibility(g.constants.SIDEBAR_LANGUAGE) %}
<entry>
<title>{{_('Languages')}}</title>
<link href="{{url_for('opds.feed_languagesindex')}}" type="application/atom+xml;profile=opds-catalog"/>
<id>{{url_for('opds.feed_languagesindex')}}</id>
<updated>{{ current_time }}</updated>
<content type="text">{{_('Books ordered by Languages')}}</content>
</entry>
{% endif %}
{% if current_user.check_visibility(g.constants.SIDEBAR_RATING) %}
<entry>
<title>{{_('Ratings')}}</title>
<link href="{{url_for('opds.feed_ratingindex')}}" type="application/atom+xml;profile=opds-catalog"/>
<id>{{url_for('opds.feed_ratingindex')}}</id>
<updated>{{ current_time }}</updated>
<content type="text">{{_('Books ordered by Rating')}}</content>
</entry>

{% endif %}
{% if current_user.check_visibility(g.constants.SIDEBAR_FORMAT) %}
<entry>
<title>{{_('File formats')}}</title>
<link href="{{url_for('opds.feed_formatindex')}}" type="application/atom+xml;profile=opds-catalog"/>
<id>{{url_for('opds.feed_formatindex')}}</id>
<updated>{{ current_time }}</updated>
<content type="text">{{_('Books ordered by file formats')}}</content>
</entry>
{% endif %}
{% if current_user.is_authenticated or g.allow_anonymous %}
<entry>
<title>{{_('Shelves')}}</title>
<link href="{{url_for('opds.feed_shelfindex')}}" type="application/atom+xml;profile=opds-catalog"/>
<id>{{url_for('opds.feed_shelfindex')}}</id>
<updated>{{ current_time }}</updated>
<content type="text">{{_('Books organized in shelves')}}</content>
</entry>
{% endif %}
</feed>
10 changes: 5 additions & 5 deletions optional-requirements.txt
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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
Expand All @@ -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
10 changes: 5 additions & 5 deletions setup.cfg
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -91,17 +91,17 @@ 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
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

0 comments on commit 6760d69

Please sign in to comment.