1
0
mirror of https://github.com/janeczku/calibre-web synced 2024-12-25 09:30:31 +00:00

Fixes for Kobo sync

Better output on upload cover
Fix for download after access to opds/fileformat
Fix osd search link
Added ratings to opds feed
Change for kobo sync for testing
This commit is contained in:
Ozzieisaacs 2020-03-07 11:07:35 +01:00
parent fb83bfb363
commit d267338837
7 changed files with 88 additions and 60 deletions

View File

@ -369,11 +369,11 @@ def upload_cover(request, book):
requested_file = request.files['btn-upload-cover'] requested_file = request.files['btn-upload-cover']
# check for empty request # check for empty request
if requested_file.filename != '': if requested_file.filename != '':
if helper.save_cover(requested_file, book.path) is True: ret, message = helper.save_cover(requested_file, book.path)
if ret is True:
return True return True
else: else:
# ToDo Message not always coorect flash(message, category="error")
flash(_(u"Cover is not a supported imageformat (jpg/png/webp), can't save"), category="error")
return False return False
return None return None

View File

@ -508,16 +508,16 @@ def save_cover_from_filestorage(filepath, saved_filename, img):
os.makedirs(filepath) os.makedirs(filepath)
except OSError: except OSError:
log.error(u"Failed to create path for cover") log.error(u"Failed to create path for cover")
return False return False, _(u"Failed to create path for cover")
try: try:
img.save(os.path.join(filepath, saved_filename)) img.save(os.path.join(filepath, saved_filename))
except IOError: except IOError:
log.error(u"Cover-file is not a valid image file") log.error(u"Cover-file is not a valid image file")
return False return False, _(u"Cover-file is not a valid image file")
except OSError: except OSError:
log.error(u"Failed to store cover-file") log.error(u"Failed to store cover-file")
return False return False, _(u"Failed to store cover-file")
return True return True, None
# saves book cover to gdrive or locally # saves book cover to gdrive or locally
@ -527,7 +527,7 @@ def save_cover(img, book_path):
if use_PIL: if use_PIL:
if content_type not in ('image/jpeg', 'image/png', 'image/webp'): if content_type not in ('image/jpeg', 'image/png', 'image/webp'):
log.error("Only jpg/jpeg/png/webp files are supported as coverfile") log.error("Only jpg/jpeg/png/webp files are supported as coverfile")
return False return False, _("Only jpg/jpeg/png/webp files are supported as coverfile")
# convert to jpg because calibre only supports jpg # convert to jpg because calibre only supports jpg
if content_type in ('image/png', 'image/webp'): if content_type in ('image/png', 'image/webp'):
if hasattr(img,'stream'): if hasattr(img,'stream'):
@ -541,17 +541,18 @@ def save_cover(img, book_path):
else: else:
if content_type not in ('image/jpeg'): if content_type not in ('image/jpeg'):
log.error("Only jpg/jpeg files are supported as coverfile") log.error("Only jpg/jpeg files are supported as coverfile")
return False return False, _("Only jpg/jpeg files are supported as coverfile")
if config.config_use_google_drive: if config.config_use_google_drive:
tmpDir = gettempdir() tmpDir = gettempdir()
if save_cover_from_filestorage(tmpDir, "uploaded_cover.jpg", img) is True: ret, message = save_cover_from_filestorage(tmpDir, "uploaded_cover.jpg", img)
if ret is True:
gd.uploadFileToEbooksFolder(os.path.join(book_path, 'cover.jpg'), gd.uploadFileToEbooksFolder(os.path.join(book_path, 'cover.jpg'),
os.path.join(tmpDir, "uploaded_cover.jpg")) os.path.join(tmpDir, "uploaded_cover.jpg"))
log.info("Cover is saved on Google Drive") log.info("Cover is saved on Google Drive")
return True return True, None
else: else:
return False return False, message
else: else:
return save_cover_from_filestorage(os.path.join(config.config_calibre_dir, book_path), "cover.jpg", img) return save_cover_from_filestorage(os.path.join(config.config_calibre_dir, book_path), "cover.jpg", img)
@ -818,11 +819,11 @@ def get_download_link(book_id, book_format):
book_format = book_format.split(".")[0] book_format = book_format.split(".")[0]
book = db.session.query(db.Books).filter(db.Books.id == book_id).filter(common_filters()).first() book = db.session.query(db.Books).filter(db.Books.id == book_id).filter(common_filters()).first()
if book: if book:
data = db.session.query(db.Data).filter(db.Data.book == book.id)\ data1 = db.session.query(db.Data).filter(db.Data.book == book.id)\
.filter(db.Data.format == book_format.upper()).first() .filter(db.Data.format == book_format.upper()).first()
else: else:
abort(404) abort(404)
if data: if data1:
# collect downloaded books only for registered user and not for anonymous user # collect downloaded books only for registered user and not for anonymous user
if current_user.is_authenticated: if current_user.is_authenticated:
ub.update_download(book_id, int(current_user.id)) ub.update_download(book_id, int(current_user.id))
@ -834,7 +835,7 @@ def get_download_link(book_id, book_format):
headers["Content-Type"] = mimetypes.types_map.get('.' + book_format, "application/octet-stream") headers["Content-Type"] = mimetypes.types_map.get('.' + book_format, "application/octet-stream")
headers["Content-Disposition"] = "attachment; filename=%s.%s; filename*=UTF-8''%s.%s" % ( headers["Content-Disposition"] = "attachment; filename=%s.%s; filename*=UTF-8''%s.%s" % (
quote(file_name.encode('utf-8')), book_format, quote(file_name.encode('utf-8')), book_format) quote(file_name.encode('utf-8')), book_format, quote(file_name.encode('utf-8')), book_format)
return do_download_file(book, book_format, data, headers) return do_download_file(book, book_format, data1, headers)
else: else:
abort(404) abort(404)

View File

@ -214,29 +214,19 @@ def HandleMetadataRequest(book_uuid):
def get_download_url_for_book(book, book_format): def get_download_url_for_book(book, book_format):
if not current_app.wsgi_app.is_proxied: if not current_app.wsgi_app.is_proxied:
if request.environ['SERVER_NAME'] != '::': return "{url_scheme}://{url_base}:{url_port}/download/{book_id}/{book_format}".format(
return "{url_scheme}://{url_base}:{url_port}/download/{book_id}/{book_format}".format( url_scheme=request.scheme,
url_scheme=request.environ['wsgi.url_scheme'], url_base=request.host,
url_base=request.environ['SERVER_NAME'], url_port=config.config_port,
url_port=config.config_port,
book_id=book.id,
book_format=book_format.lower()
)
else:
return "{url_scheme}://{url_base}:{url_port}/download/{book_id}/{book_format}".format(
url_scheme=request.environ['wsgi.url_scheme'],
url_base=request.host, # ToDo: both server ??
url_port=config.config_port,
book_id=book.id,
book_format=book_format.lower()
)
else:
return url_for(
"web.download_link",
book_id=book.id, book_id=book.id,
book_format=book_format.lower(), book_format=book_format.lower()
_external=True,
) )
return url_for(
"web.download_link",
book_id=book.id,
book_format=book_format.lower(),
_external=True,
)
def create_book_entitlement(book): def create_book_entitlement(book):
@ -466,15 +456,11 @@ def HandleInitRequest():
if not current_app.wsgi_app.is_proxied: if not current_app.wsgi_app.is_proxied:
log.debug('Kobo: Received unproxied request, changed request port to server port') log.debug('Kobo: Received unproxied request, changed request port to server port')
if request.environ['SERVER_NAME'] != '::': calibre_web_url = "{url_scheme}://{url_base}:{url_port}".format(
calibre_web_url = "{url_scheme}://{url_base}:{url_port}".format( url_scheme=request.scheme,
url_scheme=request.environ['wsgi.url_scheme'], url_base=request.host,
url_base=request.environ['SERVER_NAME'], url_port=config.config_port
url_port=config.config_port )
)
else:
log.debug('Kobo: Received unproxied request, on IPV6 host')
calibre_web_url = url_for("web.index", _external=True).strip("/")
else: else:
calibre_web_url = url_for("web.index", _external=True).strip("/") calibre_web_url = url_for("web.index", _external=True).strip("/")

View File

@ -56,6 +56,20 @@ def requires_basic_auth_if_no_ano(f):
return decorated return decorated
class FeedObject():
def __init__(self,rating_id , rating_name):
self.rating_id = rating_id
self.rating_name = rating_name
@property
def id(self):
return self.rating_id
@property
def name(self):
return self.rating_name
@opds.route("/opds/") @opds.route("/opds/")
@opds.route("/opds") @opds.route("/opds")
@requires_basic_auth_if_no_ano @requires_basic_auth_if_no_ano
@ -214,6 +228,31 @@ def feed_series(book_id):
db.Books, db.Books.series.any(db.Series.id == book_id), [db.Books.series_index]) db.Books, db.Books.series.any(db.Series.id == book_id), [db.Books.series_index])
return render_xml_template('feed.xml', entries=entries, pagination=pagination) return render_xml_template('feed.xml', entries=entries, pagination=pagination)
@opds.route("/opds/ratings")
@requires_basic_auth_if_no_ano
def feed_ratingindex():
off = request.args.get("offset") or 0
entries = db.session.query(db.Ratings, func.count('books_ratings_link.book').label('count'),
(db.Ratings.rating / 2).label('name')) \
.join(db.books_ratings_link).join(db.Books).filter(common_filters()) \
.group_by(text('books_ratings_link.rating')).order_by(db.Ratings.rating).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[0].id, "{} Stars".format(entry.name)))
return render_xml_template('feed.xml', listelements=element, folder='opds.feed_ratings', pagination=pagination)
@opds.route("/opds/ratings/<book_id>")
@requires_basic_auth_if_no_ano
def feed_ratings(book_id):
off = request.args.get("offset") or 0
entries, __, pagination = fill_indexpage((int(off) / (int(config.config_books_per_page)) + 1),
db.Books, db.Books.ratings.any(db.Ratings.id == book_id),[db.Books.timestamp.desc()])
return render_xml_template('feed.xml', entries=entries, pagination=pagination)
@opds.route("/opds/formats") @opds.route("/opds/formats")
@requires_basic_auth_if_no_ano @requires_basic_auth_if_no_ano
def feed_formatindex(): def feed_formatindex():
@ -222,10 +261,11 @@ def feed_formatindex():
.group_by(db.Data.format).order_by(db.Data.format).all() .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, pagination = Pagination((int(off) / (int(config.config_books_per_page)) + 1), config.config_books_per_page,
len(entries)) len(entries))
element = list()
for entry in entries: for entry in entries:
entry.name = entry.format element.append(FeedObject(entry.format, entry.format))
entry.id = entry.format return render_xml_template('feed.xml', listelements=element, folder='opds.feed_format', pagination=pagination)
return render_xml_template('feed.xml', listelements=entries, folder='opds.feed_format', pagination=pagination)
@opds.route("/opds/formats/<book_id>") @opds.route("/opds/formats/<book_id>")
@ -265,16 +305,9 @@ def feed_languages(book_id):
off = request.args.get("offset") or 0 off = request.args.get("offset") or 0
entries, __, pagination = fill_indexpage((int(off) / (int(config.config_books_per_page)) + 1), entries, __, pagination = fill_indexpage((int(off) / (int(config.config_books_per_page)) + 1),
db.Books, db.Books.languages.any(db.Languages.id == book_id), [db.Books.timestamp.desc()]) db.Books, db.Books.languages.any(db.Languages.id == book_id), [db.Books.timestamp.desc()])
'''for entry in entries:
for index in range(0, len(entry.languages)):
try:
entry.languages[index].language_name = LC.parse(entry.languages[index].lang_code).get_language_name(
get_locale())
except UnknownLocaleError:
entry.languages[index].language_name = _(
isoLanguages.get(part3=entry.languages[index].lang_code).name)'''
return render_xml_template('feed.xml', entries=entries, pagination=pagination) return render_xml_template('feed.xml', entries=entries, pagination=pagination)
@opds.route("/opds/shelfindex", defaults={'public': 0}) @opds.route("/opds/shelfindex", defaults={'public': 0})
@opds.route("/opds/shelfindex/<string:public>") @opds.route("/opds/shelfindex/<string:public>")
@requires_basic_auth_if_no_ano @requires_basic_auth_if_no_ano
@ -319,11 +352,11 @@ def feed_shelf(book_id):
@requires_basic_auth_if_no_ano @requires_basic_auth_if_no_ano
@download_required @download_required
def opds_download_link(book_id, book_format): def opds_download_link(book_id, book_format):
return get_download_link(book_id,book_format) return get_download_link(book_id,book_format.lower())
@opds.route("/ajax/book/<string:uuid>/<library>") @opds.route("/ajax/book/<string:uuid>/<library>")
@opds.route("/ajax/book/<string:uuid>") @opds.route("/ajax/book/<string:uuid>",defaults={'library': ""})
@requires_basic_auth_if_no_ano @requires_basic_auth_if_no_ano
def get_metadata_calibre_companion(uuid, library): def get_metadata_calibre_companion(uuid, library):
entry = db.session.query(db.Books).filter(db.Books.uuid.like("%" + uuid + "%")).first() entry = db.session.query(db.Books).filter(db.Books.uuid.like("%" + uuid + "%")).first()

View File

@ -92,6 +92,14 @@
<updated>{{ current_time }}</updated> <updated>{{ current_time }}</updated>
<content type="text">{{_('Books ordered by Languages')}}</content> <content type="text">{{_('Books ordered by Languages')}}</content>
</entry> </entry>
<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>
<entry> <entry>
<title>{{_('File formats')}}</title> <title>{{_('File formats')}}</title>
<link href="{{url_for('opds.feed_formatindex')}}" type="application/atom+xml;profile=opds-catalog"/> <link href="{{url_for('opds.feed_formatindex')}}" type="application/atom+xml;profile=opds-catalog"/>

View File

@ -6,7 +6,7 @@
<Developer>Janeczku</Developer> <Developer>Janeczku</Developer>
<Contact>https://github.com/janeczku/calibre-web</Contact> <Contact>https://github.com/janeczku/calibre-web</Contact>
<Url type="text/html" <Url type="text/html"
template="{{url_for('opds.feed_cc_search')}}{searchTerms}"/> template="{{url_for('opds.feed_cc_search')}}/{searchTerms}"/>
<Url type="application/atom+xml" <Url type="application/atom+xml"
template="{{url_for('opds.feed_normal_search')}}?query={searchTerms}"/> template="{{url_for('opds.feed_normal_search')}}?query={searchTerms}"/>
<SyndicationRight>open</SyndicationRight> <SyndicationRight>open</SyndicationRight>

View File

@ -1044,7 +1044,7 @@ def serve_book(book_id, book_format, anyname):
@login_required_if_no_ano @login_required_if_no_ano
@download_required @download_required
def download_link(book_id, book_format): def download_link(book_id, book_format):
return get_download_link(book_id, book_format) return get_download_link(book_id, book_format.lower())
@web.route('/send/<int:book_id>/<book_format>/<int:convert>') @web.route('/send/<int:book_id>/<book_format>/<int:convert>')