Skip to content

Commit fc97e4f

Browse files
author
Krawabbel
committed
included ability to fetch books from URL directly
update language minor improvements
1 parent 87635a5 commit fc97e4f

File tree

4 files changed

+133
-74
lines changed

4 files changed

+133
-74
lines changed

CONTRIBUTING.md

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,9 @@ We can offer only very limited support regarding configuration of **Reverse-Prox
1616

1717
### **Translation**
1818

19-
Some of the user languages in Calibre-Web having missing translations. We are happy to add the missing texts if you translate them. Create a Pull Request, create an issue with the .po file attached, or write an email to "ozzie.fernandez.isaacs@googlemail.com" with attached translation file. To display all book languages in your native language an additional file is used (iso_language_names.py). The content of this file is auto-generated with the corresponding translations of Calibre, please do not edit this file on your own.
19+
Some of the user languages in Calibre-Web have missing translations. We are happy to add the missing texts if you translate them. Create a Pull Request, create an issue with the .po file attached, or write an email to "ozzie.fernandez.isaacs@googlemail.com" with attached translation file. To display all book languages in your native language an additional file is used (iso_language_names.py). The content of this file is auto-generated with the corresponding translations of Calibre, please do not edit this file on your own.
20+
21+
If your contribution has changed any line numbers of messages that require translation (very likely), you can update the main messages.pot file by running ```pybabel extract --no-wrap -F babel.cfg -o messages.pot cps```. Afterwards you can update the translation files via ```pybabel update -i messages.pot -d cps/translations``` and fill in the gaps where you can.
2022

2123
### **Documentation**
2224

cps/editbooks.py

Lines changed: 96 additions & 64 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,10 @@
4949
from .usermanagement import user_login_required, login_required_if_no_ano
5050
from .string_helper import strip_whitespaces
5151

52+
import tempfile
53+
import requests
54+
from werkzeug.datastructures import FileStorage
55+
5256
editbook = Blueprint('edit-book', __name__)
5357
log = logger.create()
5458

@@ -100,78 +104,19 @@ def show_edit_book(book_id):
100104
def edit_book(book_id):
101105
return do_edit_book(book_id)
102106

103-
104107
@editbook.route("/upload", methods=["POST"])
105108
@login_required_if_no_ano
106109
@upload_required
107110
def upload():
108-
if len(request.files.getlist("btn-upload-format")):
111+
if request.form.get("upload_url"):
112+
return do_upload_url(request.form.get("upload_url"))
113+
elif len(request.files.getlist("btn-upload")):
114+
return do_upload_file_list(request.files.getlist("btn-upload"))
115+
elif len(request.files.getlist("btn-upload-format")):
109116
book_id = request.form.get('book_id', -1)
110117
return do_edit_book(book_id, request.files.getlist("btn-upload-format"))
111-
elif len(request.files.getlist("btn-upload")):
112-
for requested_file in request.files.getlist("btn-upload"):
113-
try:
114-
modify_date = False
115-
# create the function for sorting...
116-
calibre_db.create_functions(config)
117-
meta, error = file_handling_on_upload(requested_file)
118-
if error:
119-
return error
120-
121-
db_book, input_authors, title_dir = create_book_on_upload(modify_date, meta)
122-
123-
# Comments need book id therefore only possible after flush
124-
modify_date |= edit_book_comments(Markup(meta.description).unescape(), db_book)
125-
126-
book_id = db_book.id
127-
title = db_book.title
128-
if config.config_use_google_drive:
129-
helper.upload_new_file_gdrive(book_id,
130-
input_authors[0],
131-
title,
132-
title_dir,
133-
meta.file_path,
134-
meta.extension.lower())
135-
for file_format in db_book.data:
136-
file_format.name = (helper.get_valid_filename(title, chars=42) + ' - '
137-
+ helper.get_valid_filename(input_authors[0], chars=42))
138-
else:
139-
error = helper.update_dir_structure(book_id,
140-
config.get_book_path(),
141-
input_authors[0],
142-
meta.file_path,
143-
title_dir + meta.extension.lower())
144-
move_coverfile(meta, db_book)
145-
if modify_date:
146-
calibre_db.set_metadata_dirty(book_id)
147-
# save data to database, reread data
148-
calibre_db.session.commit()
149-
150-
if config.config_use_google_drive:
151-
gdriveutils.updateGdriveCalibreFromLocal()
152-
if error:
153-
flash(error, category="error")
154-
link = '<a href="{}">{}</a>'.format(url_for('web.show_book', book_id=book_id), escape(title))
155-
upload_text = N_("File %(file)s uploaded", file=link)
156-
WorkerThread.add(current_user.name, TaskUpload(upload_text, escape(title)))
157-
helper.add_book_to_thumbnail_cache(book_id)
158-
159-
if len(request.files.getlist("btn-upload")) < 2:
160-
if current_user.role_edit() or current_user.role_admin():
161-
resp = {"location": url_for('edit-book.show_edit_book', book_id=book_id)}
162-
return make_response(jsonify(resp))
163-
else:
164-
resp = {"location": url_for('web.show_book', book_id=book_id)}
165-
return Response(json.dumps(resp), mimetype='application/json')
166-
except (OperationalError, IntegrityError, StaleDataError) as e:
167-
calibre_db.session.rollback()
168-
log.error_or_exception("Database error: {}".format(e))
169-
flash(_("Oops! Database Error: %(error)s.", error=e.orig if hasattr(e, "orig") else e),
170-
category="error")
171-
return make_response(jsonify(location=url_for("web.index")))
172118
abort(404)
173119

174-
175120
@editbook.route("/admin/book/convert/<int:book_id>", methods=['POST'])
176121
@login_required_if_no_ano
177122
@edit_required
@@ -613,6 +558,93 @@ def table_xchange_author_title():
613558
return make_response(jsonify(success=True))
614559
return ""
615560

561+
def do_upload_url(url: str) -> FileStorage:
562+
try:
563+
r = requests.get(url, stream=True, timeout=30)
564+
r.raise_for_status()
565+
url_end = r.url.split('/')[-1]
566+
filename_sanitized = "".join(c for c in url_end if c.isalnum() or c in "._-") # sanitize filename
567+
filename = filename_sanitized if len(filename_sanitized) > 0 else "uploaded_file"
568+
569+
with tempfile.NamedTemporaryFile(delete=False) as tmp:
570+
for chunk in r.iter_content():
571+
tmp.write(chunk)
572+
tmp.flush()
573+
tmp_name = tmp.name
574+
575+
except Exception as e:
576+
log.error_or_exception("File download error: {}".format(e))
577+
flash(_("Oops! Download failed: %(error)s.", error=e.orig if hasattr(e, "orig") else e),
578+
category="error")
579+
return make_response(jsonify(location=url_for("web.index")))
580+
581+
with open(tmp_name, 'rb') as f:
582+
file_storage = FileStorage(f, filename=filename)
583+
resp = do_upload_file_list([file_storage])
584+
585+
return resp
586+
587+
def do_upload_file_list(file_list: list[FileStorage]):
588+
for requested_file in file_list:
589+
try:
590+
modify_date = False
591+
# create the function for sorting...
592+
calibre_db.create_functions(config)
593+
meta, error = file_handling_on_upload(requested_file)
594+
if error:
595+
return error
596+
597+
db_book, input_authors, title_dir = create_book_on_upload(modify_date, meta)
598+
599+
# Comments need book id therefore only possible after flush
600+
modify_date |= edit_book_comments(Markup(meta.description).unescape(), db_book)
601+
602+
book_id = db_book.id
603+
title = db_book.title
604+
if config.config_use_google_drive:
605+
helper.upload_new_file_gdrive(book_id,
606+
input_authors[0],
607+
title,
608+
title_dir,
609+
meta.file_path,
610+
meta.extension.lower())
611+
for file_format in db_book.data:
612+
file_format.name = (helper.get_valid_filename(title, chars=42) + ' - '
613+
+ helper.get_valid_filename(input_authors[0], chars=42))
614+
else:
615+
error = helper.update_dir_structure(book_id,
616+
config.get_book_path(),
617+
input_authors[0],
618+
meta.file_path,
619+
title_dir + meta.extension.lower())
620+
move_coverfile(meta, db_book)
621+
if modify_date:
622+
calibre_db.set_metadata_dirty(book_id)
623+
# save data to database, reread data
624+
calibre_db.session.commit()
625+
626+
if config.config_use_google_drive:
627+
gdriveutils.updateGdriveCalibreFromLocal()
628+
if error:
629+
flash(error, category="error")
630+
link = '<a href="{}">{}</a>'.format(url_for('web.show_book', book_id=book_id), escape(title))
631+
upload_text = N_("File %(file)s uploaded", file=link)
632+
WorkerThread.add(current_user.name, TaskUpload(upload_text, escape(title)))
633+
helper.add_book_to_thumbnail_cache(book_id)
634+
635+
if len(file_list) < 2:
636+
if current_user.role_edit() or current_user.role_admin():
637+
resp = {"location": url_for('edit-book.show_edit_book', book_id=book_id)}
638+
return make_response(jsonify(resp))
639+
else:
640+
resp = {"location": url_for('web.show_book', book_id=book_id)}
641+
return Response(json.dumps(resp), mimetype='application/json')
642+
except (OperationalError, IntegrityError, StaleDataError) as e:
643+
calibre_db.session.rollback()
644+
log.error_or_exception("Database error: {}".format(e))
645+
flash(_("Oops! Database Error: %(error)s.", error=e.orig if hasattr(e, "orig") else e),
646+
category="error")
647+
return make_response(jsonify(location=url_for("web.index")))
616648

617649
def do_edit_book(book_id, upload_formats=None):
618650
modify_date = False

cps/static/js/main.js

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -166,6 +166,14 @@ $("#form-upload-format").uploadprogress({
166166
modalTitleFailed: $("#form-upload-format").data("failed")
167167
});
168168

169+
$("#form-upload-url").uploadprogress({
170+
redirect_url: getPath() + "/",
171+
uploadedMsg: $("#form-upload-url").data("message"),
172+
modalTitle: $("#form-upload-url").data("title"),
173+
modalFooter: $("#form-upload-url").data("footer"),
174+
modalTitleFailed: $("#form-upload-url").data("failed")
175+
});
176+
169177
$(document).ready(function() {
170178
var inp = $('#query').first()
171179
if (inp.length) {

cps/templates/layout.html

Lines changed: 26 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -75,15 +75,32 @@
7575
</li>
7676
{% endif %}
7777
{% if current_user.role_upload() and g.allow_upload %}
78-
<li>
79-
<form id="form-upload" class="navbar-form" action="{{ url_for('edit-book.upload') }}" data-title="{{_('Uploading...')}}" data-footer="{{_('Close')}}" data-failed="{{_('Error')}}" data-message="{{_('Upload done, processing, please wait...')}}" method="post" enctype="multipart/form-data">
80-
<input type="hidden" name="csrf_token" value="{{ csrf_token() }}">
81-
<div class="form-group">
82-
<span class="btn btn-default btn-file">{{_('Upload')}}<input id="btn-upload" name="btn-upload"
83-
type="file" accept="{% for format in accept %}.{% if format != ''%}{{format}}{% else %}*{% endif %}{{ ',' if not loop.last }}{% endfor %}" multiple></span>
84-
<input class="hide" id="btn-upload2" name="btn-upload2" type="file" accept="{% for format in accept %}.{% if format != ''%}{{format}}{% else %}*{% endif %}{{ ',' if not loop.last }}{% endfor %}">
85-
</div>
86-
</form>
78+
<li class="dropdown" id="upload-dropdown">
79+
<a href="#" class="dropdown-toggle" data-toggle="dropdown" role="button" aria-haspopup="true" aria-expanded="false">
80+
<span class="glyphicon glyphicon-upload"></span> {{_('Upload')}} <span class="caret"></span>
81+
</a>
82+
<ul class="dropdown-menu">
83+
<li>
84+
<form id="form-upload" class="navbar-form" action="{{ url_for('edit-book.upload') }}" data-title="{{_('Uploading...')}}" data-footer="{{_('Close')}}" data-failed="{{_('Error')}}" data-message="{{_('Upload done, processing, please wait...')}}" method="post" enctype="multipart/form-data">
85+
<input type="hidden" name="csrf_token" value="{{ csrf_token() }}">
86+
<div class="form-group">
87+
<span class="btn btn-default btn-file" style="min-width: 140px; display: inline-block; text-align: center;"> {{_('Select File')}}
88+
<input id="btn-upload" name="btn-upload" type="file" accept="{% for format in accept %}.{% if format != ''%}{{format}}{% else %}*{% endif %}{{ ',' if not loop.last }}{% endfor %}" multiple>
89+
</span>
90+
<input class="hide" id="btn-upload2" name="btn-upload2" type="file" accept="{% for format in accept %}.{% if format != ''%}{{format}}{% else %}*{% endif %}{{ ',' if not loop.last }}{% endfor %}">
91+
</div>
92+
</form>
93+
</li>
94+
<li>
95+
<form id="form-upload-url" class="navbar-form" action="{{ url_for('edit-book.upload') }}" data-title="{{_('Uploading...')}}" data-footer="{{_('Close')}}" data-failed="{{_('Error')}}" data-message="{{_('Upload done, processing, please wait...')}}" method="post">
96+
<input type="hidden" name="csrf_token" value="{{ csrf_token() }}">
97+
<div class="form-group" style="display: flex; gap: 8px; align-items: center;">
98+
<button type="submit" class="btn btn-default" style="min-width: 140px;">{{_('Fetch from URL')}}</button>
99+
<input type="url" class="form-control" name="upload_url" placeholder="{{_('Paste eBook URL')}}" required>
100+
</div>
101+
</form>
102+
</li>
103+
</ul>
87104
</li>
88105
{% endif %}
89106
{% if not current_user.is_anonymous and not simple%}

0 commit comments

Comments
 (0)