Merge branch 'master' into Develop

# Conflicts:
#	cps/helper.py
This commit is contained in:
Ozzie Isaacs 2024-02-24 14:51:53 +01:00
commit 2ba14acf4f
69 changed files with 7639 additions and 6498 deletions

View File

@ -6,12 +6,23 @@ labels: ''
assignees: '' assignees: ''
--- ---
<!-- Please have a look at our [Contributing Guidelines](https://github.com/janeczku/calibre-web/blob/master/CONTRIBUTING.md) -->
**Describe the bug/problem** ## Short Notice from the maintainer
After 6 years of more or less intensive programming on Calibre-Web, I need a break.
The last few months, maintaining Calibre-Web has felt more like work than a hobby. I felt pressured and teased by people to solve "their" problems and merge PRs for "their" Calibre-Web.
I have turned off all notifications from Github/Discord and will now concentrate undisturbed on the development of “my” Calibre-Web over the next few weeks/months.
I will look into the issues and maybe also the PRs from time to time, but don't expect a quick response from me.
Please also have a look at our [Contributing Guidelines](https://github.com/janeczku/calibre-web/blob/master/CONTRIBUTING.md)
**Describe the bug/problem**
A clear and concise description of what the bug is. If you are asking for support, please check our [Wiki](https://github.com/janeczku/calibre-web/wiki) if your question is already answered there. A clear and concise description of what the bug is. If you are asking for support, please check our [Wiki](https://github.com/janeczku/calibre-web/wiki) if your question is already answered there.
**To Reproduce** **To Reproduce**
Steps to reproduce the behavior: Steps to reproduce the behavior:
1. Go to '...' 1. Go to '...'
2. Click on '....' 2. Click on '....'
@ -19,15 +30,19 @@ Steps to reproduce the behavior:
4. See error 4. See error
**Logfile** **Logfile**
Add content of calibre-web.log file or the relevant error, try to reproduce your problem with "debug" log-level to get more output. Add content of calibre-web.log file or the relevant error, try to reproduce your problem with "debug" log-level to get more output.
**Expected behavior** **Expected behavior**
A clear and concise description of what you expected to happen. A clear and concise description of what you expected to happen.
**Screenshots** **Screenshots**
If applicable, add screenshots to help explain your problem. If applicable, add screenshots to help explain your problem.
**Environment (please complete the following information):** **Environment (please complete the following information):**
- OS: [e.g. Windows 10/Raspberry Pi OS] - OS: [e.g. Windows 10/Raspberry Pi OS]
- Python version: [e.g. python2.7] - Python version: [e.g. python2.7]
- Calibre-Web version: [e.g. 0.6.8 or 087c4c59 (git rev-parse --short HEAD)]: - Calibre-Web version: [e.g. 0.6.8 or 087c4c59 (git rev-parse --short HEAD)]:
@ -37,3 +52,4 @@ If applicable, add screenshots to help explain your problem.
**Additional context** **Additional context**
Add any other context about the problem here. [e.g. access via reverse proxy, database background sync, special database location] Add any other context about the problem here. [e.g. access via reverse proxy, database background sync, special database location]

View File

@ -7,7 +7,14 @@ assignees: ''
--- ---
<!-- Please have a look at our [Contributing Guidelines](https://github.com/janeczku/calibre-web/blob/master/CONTRIBUTING.md) --> # Short Notice from the maintainer
After 6 years of more or less intensive programming on Calibre-Web, I need a break.
The last few months, maintaining Calibre-Web has felt more like work than a hobby. I felt pressured and teased by people to solve "their" problems and merge PRs for "their" Calibre-Web.
I have turned off all notifications from Github/Discord and will now concentrate undisturbed on the development of “my” Calibre-Web over the next few weeks/months.
I will look into the issues and maybe also the PRs from time to time, but don't expect a quick response from me.
Please have a look at our [Contributing Guidelines](https://github.com/janeczku/calibre-web/blob/master/CONTRIBUTING.md)
**Is your feature request related to a problem? Please describe.** **Is your feature request related to a problem? Please describe.**
A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] A clear and concise description of what the problem is. Ex. I'm always frustrated when [...]

View File

@ -1,3 +1,10 @@
# Short Notice from the maintainer
After 6 years of more or less intensive programming on Calibre-Web, I need a break.
The last few months, maintaining Calibre-Web has felt more like work than a hobby. I felt pressured and teased by people to solve "their" problems and merge PRs for "their" Calibre-Web.
I have turned off all notifications from Github/Discord and will now concentrate undisturbed on the development of “my” Calibre-Web over the next few weeks/months.
I will look into the issues and maybe also the PRs from time to time, but don't expect a quick response from me.
# Calibre-Web # Calibre-Web
Calibre-Web is a web app that offers a clean and intuitive interface for browsing, reading, and downloading eBooks using a valid [Calibre](https://calibre-ebook.com) database. Calibre-Web is a web app that offers a clean and intuitive interface for browsing, reading, and downloading eBooks using a valid [Calibre](https://calibre-ebook.com) database.

View File

@ -25,16 +25,23 @@ from .constants import SUPPORTED_CALIBRE_BINARIES
log = logger.create() log = logger.create()
def do_calibre_export(book_id, book_format): def do_calibre_export(book_id, book_format):
try: try:
quotes = [3, 5, 7, 9] quotes = [3, 5, 7, 9]
tmp_dir = get_temp_dir() tmp_dir = get_temp_dir()
calibredb_binarypath = get_calibre_binarypath("calibredb") calibredb_binarypath = get_calibre_binarypath("calibredb")
temp_file_name = str(uuid4()) temp_file_name = str(uuid4())
opf_command = [calibredb_binarypath, 'export', '--dont-write-opf', '--with-library', config.config_calibre_dir, my_env = os.environ.copy()
if config.config_calibre_split:
my_env['CALIBRE_OVERRIDE_DATABASE_PATH'] = os.path.join(config.config_calibre_dir, "metadata.db")
library_path = config.config_calibre_split_dir
else:
library_path = config.config_calibre_dir
opf_command = [calibredb_binarypath, 'export', '--dont-write-opf', '--with-library', library_path,
'--to-dir', tmp_dir, '--formats', book_format, "--template", "{}".format(temp_file_name), '--to-dir', tmp_dir, '--formats', book_format, "--template", "{}".format(temp_file_name),
str(book_id)] str(book_id)]
p = process_open(opf_command, quotes) p = process_open(opf_command, quotes, my_env)
_, err = p.communicate() _, err = p.communicate()
if err: if err:
log.error('Metadata embedder encountered an error: %s', err) log.error('Metadata embedder encountered an error: %s', err)
@ -44,6 +51,7 @@ def do_calibre_export(book_id, book_format):
log.error_or_exception(ex) log.error_or_exception(ex)
return None, None return None, None
def get_calibre_binarypath(binary): def get_calibre_binarypath(binary):
binariesdir = config.config_binariesdir binariesdir = config.config_binariesdir
if binariesdir: if binariesdir:

View File

@ -124,7 +124,7 @@ def formatseriesindex_filter(series_index):
return int(series_index) return int(series_index)
else: else:
return series_index return series_index
except ValueError: except (ValueError, TypeError):
return series_index return series_index
return 0 return 0

17
cps/redirect.py Normal file → Executable file
View File

@ -29,7 +29,7 @@
from urllib.parse import urlparse, urljoin from urllib.parse import urlparse, urljoin
from flask import request, url_for, redirect from flask import request, url_for, redirect, current_app
def is_safe_url(target): def is_safe_url(target):
@ -38,16 +38,15 @@ def is_safe_url(target):
return test_url.scheme in ('http', 'https') and ref_url.netloc == test_url.netloc return test_url.scheme in ('http', 'https') and ref_url.netloc == test_url.netloc
def get_redirect_target(): def remove_prefix(text, prefix):
for target in request.values.get('next'), request.referrer: if text.startswith(prefix):
if not target: return text[len(prefix):]
continue return ""
if is_safe_url(target):
return target
def redirect_back(endpoint, **values): def redirect_back(endpoint, **values):
target = request.form['next'] target = request.form.get('next', None) or url_for(endpoint, **values)
if not target or not is_safe_url(target): adapter = current_app.url_map.bind(urlparse(request.host_url).netloc)
if not len(adapter.allowed_methods(remove_prefix(target, request.environ.get('HTTP_X_SCRIPT_NAME',"")))):
target = url_for(endpoint, **values) target = url_for(endpoint, **values)
return redirect(target) return redirect(target)

View File

@ -24,7 +24,7 @@ import socket
import asyncio import asyncio
try: try:
from gevent_.pywsgi import WSGIServer from gevent.pywsgi import WSGIServer
from .gevent_wsgi import MyWSGIHandler from .gevent_wsgi import MyWSGIHandler
from gevent.pool import Pool from gevent.pool import Pool
from gevent.socket import socket as GeventSocket from gevent.socket import socket as GeventSocket

Binary file not shown.

Before

Width:  |  Height:  |  Size: 60 KiB

After

Width:  |  Height:  |  Size: 8.9 KiB

BIN
cps/static/icon.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 27 KiB

5
cps/static/icon.svg Normal file
View File

@ -0,0 +1,5 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<svg xmlns="http://www.w3.org/2000/svg" version="1.1" width="140px" height="140px" style="shape-rendering:geometricPrecision; text-rendering:geometricPrecision; image-rendering:optimizeQuality; fill-rule:evenodd; clip-rule:evenodd" xmlns:xlink="http://www.w3.org/1999/xlink">
<g><path style="opacity:1" fill="#45b29d" d="M 70.5,5.5 C 87.7691,3.12603 97.4358,10.4594 99.5,27.5C 95.637,46.6972 84.3037,59.1972 65.5,65C 60.9053,66.3929 56.2387,66.7262 51.5,66C 50.0692,65.5348 48.9025,64.7014 48,63.5C 47.3333,60.5 47.3333,57.5 48,54.5C 62.2513,56.0484 73.5846,50.715 82,38.5C 85.0332,33.8945 86.0332,28.8945 85,23.5C 83.0488,19.2854 79.7155,17.2854 75,17.5C 65.5257,19.0759 57.859,23.7425 52,31.5C 38.306,51.6368 33.9727,73.6368 39,97.5C 44.5639,116.532 56.7306,122.699 75.5,116C 80.6017,113.385 85.2684,110.218 89.5,106.5C 95.1927,108.891 96.6927,112.891 94,118.5C 78.4211,132.151 61.2544,134.651 42.5,126C 31.5182,117.21 25.3516,105.71 24,91.5C 20.9978,65.8515 27.3311,42.8515 43,22.5C 50.6154,14.1193 59.7821,8.45258 70.5,5.5 Z"/></g>
</svg>

After

Width:  |  Height:  |  Size: 1.2 KiB

View File

@ -252,9 +252,16 @@ class TaskConvert(CalibreTask):
quotes = [3, 5] quotes = [3, 5]
tmp_dir = get_temp_dir() tmp_dir = get_temp_dir()
calibredb_binarypath = os.path.join(config.config_binariesdir, SUPPORTED_CALIBRE_BINARIES["calibredb"]) calibredb_binarypath = os.path.join(config.config_binariesdir, SUPPORTED_CALIBRE_BINARIES["calibredb"])
my_env = os.environ.copy()
if config.config_calibre_split:
my_env['CALIBRE_OVERRIDE_DATABASE_PATH'] = os.path.join(config.config_calibre_dir, "metadata.db")
library_path = config.config_calibre_split_dir
else:
library_path = config.config_calibre_dir
opf_command = [calibredb_binarypath, 'show_metadata', '--as-opf', str(self.book_id), opf_command = [calibredb_binarypath, 'show_metadata', '--as-opf', str(self.book_id),
'--with-library', config.config_calibre_dir] '--with-library', library_path]
p = process_open(opf_command, quotes) p = process_open(opf_command, quotes, my_env)
p.wait() p.wait()
path_tmp_opf = os.path.join(tmp_dir, "metadata_" + str(uuid4()) + ".opf") path_tmp_opf = os.path.join(tmp_dir, "metadata_" + str(uuid4()) + ".opf")
with open(path_tmp_opf, 'w') as fd: with open(path_tmp_opf, 'w') as fd:

View File

@ -18,7 +18,7 @@
</div> </div>
<div class="form-group required"> <div class="form-group required">
<input type="checkbox" id="config_calibre_split" name="config_calibre_split" data-control="split_settings" data-t ="{{ config.config_calibre_split_dir }}" {% if config.config_calibre_split %}checked{% endif %} > <input type="checkbox" id="config_calibre_split" name="config_calibre_split" data-control="split_settings" data-t ="{{ config.config_calibre_split_dir }}" {% if config.config_calibre_split %}checked{% endif %} >
<label for="config_calibre_split">{{_('Separate Book files from Library (Highly experimental, might not work at all)')}}</label> <label for="config_calibre_split">{{_('Separate Book files from Library (Experimental, may lead to unexpected behavior)')}}</label>
</div> </div>
<div data-related="split_settings"> <div data-related="split_settings">
<div class="form-group required input-group"> <div class="form-group required input-group">

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@ -613,10 +613,9 @@ def render_ratings_books(page, book_id, order):
db_filter, db_filter,
[order[0][0]], [order[0][0]],
True, config.config_read_column, True, config.config_read_column,
db.books_series_link, db.books_ratings_link,
db.Books.id == db.books_series_link.c.book, db.Books.id == db.books_ratings_link.c.book,
db.Series, db.Ratings)
db.books_ratings_link, db.Ratings)
title = _("Rating: None") title = _("Rating: None")
else: else:
name = calibre_db.session.query(db.Ratings).filter(db.Ratings.id == book_id).first() name = calibre_db.session.query(db.Ratings).filter(db.Ratings.id == book_id).first()
@ -634,19 +633,32 @@ def render_ratings_books(page, book_id, order):
def render_formats_books(page, book_id, order): def render_formats_books(page, book_id, order):
name = calibre_db.session.query(db.Data).filter(db.Data.format == book_id.upper()).first() if book_id == '-1':
if name: name = _("None")
entries, random, pagination = calibre_db.fill_indexpage(page, 0, entries, random, pagination = calibre_db.fill_indexpage(page, 0,
db.Books, db.Books,
db.Books.data.any(db.Data.format == book_id.upper()), db.Data.format == None,
[order[0][0]], [order[0][0]],
True, config.config_read_column) True, config.config_read_column,
return render_title_template('index.html', random=random, pagination=pagination, entries=entries, id=book_id, db.Data)
title=_("File format: %(format)s", format=name.format),
page="formats",
order=order[1])
else: else:
abort(404) name = calibre_db.session.query(db.Data).filter(db.Data.format == book_id.upper()).first()
if name:
name = name.format
entries, random, pagination = calibre_db.fill_indexpage(page, 0,
db.Books,
db.Books.data.any(
db.Data.format == book_id.upper()),
[order[0][0]],
True, config.config_read_column)
else:
abort(404)
return render_title_template('index.html', random=random, pagination=pagination, entries=entries, id=book_id,
title=_("File format: %(format)s", format=name),
page="formats",
order=order[1])
def render_category_books(page, book_id, order): def render_category_books(page, book_id, order):
@ -1057,7 +1069,7 @@ def ratings_list():
@login_required_if_no_ano @login_required_if_no_ano
def formats_list(): def formats_list():
if current_user.check_visibility(constants.SIDEBAR_FORMAT): if current_user.check_visibility(constants.SIDEBAR_FORMAT):
if current_user.get_view_property('ratings', 'dir') == 'desc': if current_user.get_view_property('formats', 'dir') == 'desc':
order = db.Data.format.desc() order = db.Data.format.desc()
order_no = 0 order_no = 0
else: else:
@ -1322,7 +1334,7 @@ def handle_login_user(user, remember, message, category):
ub.store_user_session() ub.store_user_session()
flash(message, category=category) flash(message, category=category)
[limiter.limiter.storage.clear(k.key) for k in limiter.current_limits] [limiter.limiter.storage.clear(k.key) for k in limiter.current_limits]
return redirect_back(url_for("web.index")) return redirect_back("web.index")
def render_login(username="", password=""): def render_login(username="", password=""):
@ -1396,7 +1408,7 @@ def login_post():
if user is not None and user.name != "Guest": if user is not None and user.name != "Guest":
ret, __ = reset_password(user.id) ret, __ = reset_password(user.id)
if ret == 1: if ret == 1:
flash(_(u"New Password was send to your email address"), category="info") flash(_(u"New Password was sent to your email address"), category="info")
log.info('Password reset for user "%s" IP-address: %s', username, ip_address) log.info('Password reset for user "%s" IP-address: %s', username, ip_address)
else: else:
log.error(u"An unknown error occurred. Please try again later") log.error(u"An unknown error occurred. Please try again later")

File diff suppressed because it is too large Load Diff

View File

@ -1,7 +1,7 @@
# GDrive Integration # GDrive Integration
google-api-python-client>=1.7.11,<2.108.0 google-api-python-client>=1.7.11,<2.108.0
gevent>20.6.0,<24.0.0 gevent>20.6.0,<24.0.0
greenlet>=0.4.17,<2.1.0 greenlet>=0.4.17,<3.1.0
httplib2>=0.9.2,<0.23.0 httplib2>=0.9.2,<0.23.0
oauth2client>=4.0.0,<4.1.4 oauth2client>=4.0.0,<4.1.4
uritemplate>=3.0.0,<4.2.0 uritemplate>=3.0.0,<4.2.0
@ -38,8 +38,8 @@ faust-cchardet>=2.1.18,<2.1.20
py7zr>=0.15.0,<0.21.0 py7zr>=0.15.0,<0.21.0
# Comics # Comics
natsort>=2.2.0,<8.4.0 natsort>=2.2.0,<8.5.0
comicapi>=2.2.0,<3.3.0 comicapi>=2.2.0,<3.3.0
# Kobo integration # Kobo integration
jsonschema>=3.2.0,<4.20.0 jsonschema>=3.2.0,<4.22.0

View File

@ -68,7 +68,7 @@ include = cps/services*
gdrive = gdrive =
google-api-python-client>=1.7.11,<2.108.0 google-api-python-client>=1.7.11,<2.108.0
gevent>20.6.0,<24.0.0 gevent>20.6.0,<24.0.0
greenlet>=0.4.17,<2.1.0 greenlet>=0.4.17,<3.1.0
httplib2>=0.9.2,<0.23.0 httplib2>=0.9.2,<0.23.0
oauth2client>=4.0.0,<4.1.4 oauth2client>=4.0.0,<4.1.4
uritemplate>=3.0.0,<4.2.0 uritemplate>=3.0.0,<4.2.0
@ -99,8 +99,8 @@ metadata =
faust-cchardet>=2.1.18,<2.1.20 faust-cchardet>=2.1.18,<2.1.20
py7zr>=0.15.0,<0.21.0 py7zr>=0.15.0,<0.21.0
comics = comics =
natsort>=2.2.0,<8.4.0 natsort>=2.2.0,<8.5.0
comicapi>=2.2.0,<3.3.0 comicapi>=2.2.0,<3.3.0
kobo = kobo =
jsonschema>=3.2.0,<4.20.0 jsonschema>=3.2.0,<4.22.0

File diff suppressed because it is too large Load Diff