|
|
@ -1649,6 +1649,8 @@ def shutdown():
|
|
|
|
db.engine.dispose()
|
|
|
|
db.engine.dispose()
|
|
|
|
ub.session.close()
|
|
|
|
ub.session.close()
|
|
|
|
ub.engine.dispose()
|
|
|
|
ub.engine.dispose()
|
|
|
|
|
|
|
|
# stop gevent server
|
|
|
|
|
|
|
|
# gevent_server.stop()
|
|
|
|
# stop tornado server
|
|
|
|
# stop tornado server
|
|
|
|
server = IOLoop.instance()
|
|
|
|
server = IOLoop.instance()
|
|
|
|
server.add_callback(server.stop)
|
|
|
|
server.add_callback(server.stop)
|
|
|
@ -3030,6 +3032,7 @@ def edit_book(book_id):
|
|
|
|
is_format = db.session.query(db.Data).filter(db.Data.book == book_id).filter(db.Data.format == file_ext.upper()).first()
|
|
|
|
is_format = db.session.query(db.Data).filter(db.Data.book == book_id).filter(db.Data.format == file_ext.upper()).first()
|
|
|
|
if is_format:
|
|
|
|
if is_format:
|
|
|
|
# Format entry already exists, no need to update the database
|
|
|
|
# Format entry already exists, no need to update the database
|
|
|
|
|
|
|
|
app.logger.info('Bokk format already existing')
|
|
|
|
pass
|
|
|
|
pass
|
|
|
|
else:
|
|
|
|
else:
|
|
|
|
db_format = db.Data(book_id, file_ext.upper(), file_size, file_name)
|
|
|
|
db_format = db.Data(book_id, file_ext.upper(), file_size, file_name)
|
|
|
@ -3037,188 +3040,194 @@ def edit_book(book_id):
|
|
|
|
|
|
|
|
|
|
|
|
to_save = request.form.to_dict()
|
|
|
|
to_save = request.form.to_dict()
|
|
|
|
|
|
|
|
|
|
|
|
if book.title != to_save["book_title"]:
|
|
|
|
try:
|
|
|
|
book.title = to_save["book_title"]
|
|
|
|
if book.title != to_save["book_title"]:
|
|
|
|
edited_books_id.add(book.id)
|
|
|
|
book.title = to_save["book_title"]
|
|
|
|
|
|
|
|
|
|
|
|
input_authors = to_save["author_name"].split('&')
|
|
|
|
|
|
|
|
input_authors = list(map(lambda it: it.strip().replace(',', '|'), input_authors))
|
|
|
|
|
|
|
|
# we have all author names now
|
|
|
|
|
|
|
|
if input_authors == ['']:
|
|
|
|
|
|
|
|
input_authors = [_(u'unknown')] # prevent empty Author
|
|
|
|
|
|
|
|
if book.authors:
|
|
|
|
|
|
|
|
author0_before_edit = book.authors[0].name
|
|
|
|
|
|
|
|
else:
|
|
|
|
|
|
|
|
author0_before_edit = db.Authors(_(u'unknown'), '', 0)
|
|
|
|
|
|
|
|
modify_database_object(input_authors, book.authors, db.Authors, db.session, 'author')
|
|
|
|
|
|
|
|
if book.authors:
|
|
|
|
|
|
|
|
if author0_before_edit != book.authors[0].name:
|
|
|
|
|
|
|
|
edited_books_id.add(book.id)
|
|
|
|
edited_books_id.add(book.id)
|
|
|
|
book.author_sort = helper.get_sorted_author(input_authors[0])
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
error = False
|
|
|
|
input_authors = to_save["author_name"].split('&')
|
|
|
|
for b in edited_books_id:
|
|
|
|
input_authors = list(map(lambda it: it.strip().replace(',', '|'), input_authors))
|
|
|
|
if config.config_use_google_drive:
|
|
|
|
# we have all author names now
|
|
|
|
error = helper.update_dir_structure_gdrive(b)
|
|
|
|
if input_authors == ['']:
|
|
|
|
|
|
|
|
input_authors = [_(u'unknown')] # prevent empty Author
|
|
|
|
|
|
|
|
if book.authors:
|
|
|
|
|
|
|
|
author0_before_edit = book.authors[0].name
|
|
|
|
else:
|
|
|
|
else:
|
|
|
|
error = helper.update_dir_stucture(b, config.config_calibre_dir)
|
|
|
|
author0_before_edit = db.Authors(_(u'unknown'), '', 0)
|
|
|
|
if error: # stop on error
|
|
|
|
modify_database_object(input_authors, book.authors, db.Authors, db.session, 'author')
|
|
|
|
break
|
|
|
|
if book.authors:
|
|
|
|
if config.config_use_google_drive:
|
|
|
|
if author0_before_edit != book.authors[0].name:
|
|
|
|
updateGdriveCalibreFromLocal()
|
|
|
|
edited_books_id.add(book.id)
|
|
|
|
|
|
|
|
book.author_sort = helper.get_sorted_author(input_authors[0])
|
|
|
|
if not error:
|
|
|
|
|
|
|
|
if to_save["cover_url"]:
|
|
|
|
error = False
|
|
|
|
if save_cover(to_save["cover_url"], book.path) is true:
|
|
|
|
for b in edited_books_id:
|
|
|
|
book.has_cover = 1
|
|
|
|
if config.config_use_google_drive:
|
|
|
|
|
|
|
|
error = helper.update_dir_structure_gdrive(b)
|
|
|
|
else:
|
|
|
|
else:
|
|
|
|
flash(_(u"Cover is not a jpg file, can't save"), category="error")
|
|
|
|
error = helper.update_dir_stucture(b, config.config_calibre_dir)
|
|
|
|
|
|
|
|
if error: # stop on error
|
|
|
|
|
|
|
|
break
|
|
|
|
|
|
|
|
if config.config_use_google_drive:
|
|
|
|
|
|
|
|
updateGdriveCalibreFromLocal()
|
|
|
|
|
|
|
|
|
|
|
|
if book.series_index != to_save["series_index"]:
|
|
|
|
if not error:
|
|
|
|
book.series_index = to_save["series_index"]
|
|
|
|
if to_save["cover_url"]:
|
|
|
|
|
|
|
|
if save_cover(to_save["cover_url"], book.path) is true:
|
|
|
|
|
|
|
|
book.has_cover = 1
|
|
|
|
|
|
|
|
else:
|
|
|
|
|
|
|
|
flash(_(u"Cover is not a jpg file, can't save"), category="error")
|
|
|
|
|
|
|
|
|
|
|
|
if len(book.comments):
|
|
|
|
if book.series_index != to_save["series_index"]:
|
|
|
|
book.comments[0].text = to_save["description"]
|
|
|
|
book.series_index = to_save["series_index"]
|
|
|
|
else:
|
|
|
|
|
|
|
|
book.comments.append(db.Comments(text=to_save["description"], book=book.id))
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
input_tags = to_save["tags"].split(',')
|
|
|
|
if len(book.comments):
|
|
|
|
input_tags = list(map(lambda it: it.strip(), input_tags))
|
|
|
|
book.comments[0].text = to_save["description"]
|
|
|
|
modify_database_object(input_tags, book.tags, db.Tags, db.session, 'tags')
|
|
|
|
else:
|
|
|
|
|
|
|
|
book.comments.append(db.Comments(text=to_save["description"], book=book.id))
|
|
|
|
|
|
|
|
|
|
|
|
input_series = [to_save["series"].strip()]
|
|
|
|
input_tags = to_save["tags"].split(',')
|
|
|
|
input_series = [x for x in input_series if x != '']
|
|
|
|
input_tags = list(map(lambda it: it.strip(), input_tags))
|
|
|
|
modify_database_object(input_series, book.series, db.Series, db.session, 'series')
|
|
|
|
modify_database_object(input_tags, book.tags, db.Tags, db.session, 'tags')
|
|
|
|
|
|
|
|
|
|
|
|
input_languages = to_save["languages"].split(',')
|
|
|
|
input_series = [to_save["series"].strip()]
|
|
|
|
input_languages = list(map(lambda it: it.strip().lower(), input_languages))
|
|
|
|
input_series = [x for x in input_series if x != '']
|
|
|
|
|
|
|
|
modify_database_object(input_series, book.series, db.Series, db.session, 'series')
|
|
|
|
|
|
|
|
|
|
|
|
if to_save["pubdate"]:
|
|
|
|
input_languages = to_save["languages"].split(',')
|
|
|
|
try:
|
|
|
|
input_languages = list(map(lambda it: it.strip().lower(), input_languages))
|
|
|
|
book.pubdate = datetime.datetime.strptime(to_save["pubdate"], "%Y-%m-%d")
|
|
|
|
|
|
|
|
except ValueError:
|
|
|
|
if to_save["pubdate"]:
|
|
|
|
|
|
|
|
try:
|
|
|
|
|
|
|
|
book.pubdate = datetime.datetime.strptime(to_save["pubdate"], "%Y-%m-%d")
|
|
|
|
|
|
|
|
except ValueError:
|
|
|
|
|
|
|
|
book.pubdate = db.Books.DEFAULT_PUBDATE
|
|
|
|
|
|
|
|
else:
|
|
|
|
book.pubdate = db.Books.DEFAULT_PUBDATE
|
|
|
|
book.pubdate = db.Books.DEFAULT_PUBDATE
|
|
|
|
else:
|
|
|
|
|
|
|
|
book.pubdate = db.Books.DEFAULT_PUBDATE
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
# retranslate displayed text to language codes
|
|
|
|
# retranslate displayed text to language codes
|
|
|
|
languages = db.session.query(db.Languages).all()
|
|
|
|
languages = db.session.query(db.Languages).all()
|
|
|
|
input_l = []
|
|
|
|
input_l = []
|
|
|
|
for lang in languages:
|
|
|
|
for lang in languages:
|
|
|
|
try:
|
|
|
|
try:
|
|
|
|
lang.name = LC.parse(lang.lang_code).get_language_name(get_locale()).lower()
|
|
|
|
lang.name = LC.parse(lang.lang_code).get_language_name(get_locale()).lower()
|
|
|
|
except Exception:
|
|
|
|
except Exception:
|
|
|
|
lang.name = _(isoLanguages.get(part3=lang.lang_code).name).lower()
|
|
|
|
lang.name = _(isoLanguages.get(part3=lang.lang_code).name).lower()
|
|
|
|
for inp_lang in input_languages:
|
|
|
|
for inp_lang in input_languages:
|
|
|
|
if inp_lang == lang.name:
|
|
|
|
if inp_lang == lang.name:
|
|
|
|
input_l.append(lang.lang_code)
|
|
|
|
input_l.append(lang.lang_code)
|
|
|
|
modify_database_object(input_l, book.languages, db.Languages, db.session, 'languages')
|
|
|
|
modify_database_object(input_l, book.languages, db.Languages, db.session, 'languages')
|
|
|
|
|
|
|
|
|
|
|
|
if to_save["rating"].strip():
|
|
|
|
if to_save["rating"].strip():
|
|
|
|
old_rating = False
|
|
|
|
old_rating = False
|
|
|
|
if len(book.ratings) > 0:
|
|
|
|
if len(book.ratings) > 0:
|
|
|
|
old_rating = book.ratings[0].rating
|
|
|
|
old_rating = book.ratings[0].rating
|
|
|
|
ratingx2 = int(float(to_save["rating"]) * 2)
|
|
|
|
ratingx2 = int(float(to_save["rating"]) * 2)
|
|
|
|
if ratingx2 != old_rating:
|
|
|
|
if ratingx2 != old_rating:
|
|
|
|
is_rating = db.session.query(db.Ratings).filter(db.Ratings.rating == ratingx2).first()
|
|
|
|
is_rating = db.session.query(db.Ratings).filter(db.Ratings.rating == ratingx2).first()
|
|
|
|
if is_rating:
|
|
|
|
if is_rating:
|
|
|
|
book.ratings.append(is_rating)
|
|
|
|
book.ratings.append(is_rating)
|
|
|
|
else:
|
|
|
|
else:
|
|
|
|
new_rating = db.Ratings(rating=ratingx2)
|
|
|
|
new_rating = db.Ratings(rating=ratingx2)
|
|
|
|
book.ratings.append(new_rating)
|
|
|
|
book.ratings.append(new_rating)
|
|
|
|
if old_rating:
|
|
|
|
if old_rating:
|
|
|
|
|
|
|
|
book.ratings.remove(book.ratings[0])
|
|
|
|
|
|
|
|
else:
|
|
|
|
|
|
|
|
if len(book.ratings) > 0:
|
|
|
|
book.ratings.remove(book.ratings[0])
|
|
|
|
book.ratings.remove(book.ratings[0])
|
|
|
|
else:
|
|
|
|
|
|
|
|
if len(book.ratings) > 0:
|
|
|
|
for c in cc:
|
|
|
|
book.ratings.remove(book.ratings[0])
|
|
|
|
cc_string = "custom_column_" + str(c.id)
|
|
|
|
|
|
|
|
if not c.is_multiple:
|
|
|
|
for c in cc:
|
|
|
|
if len(getattr(book, cc_string)) > 0:
|
|
|
|
cc_string = "custom_column_" + str(c.id)
|
|
|
|
cc_db_value = getattr(book, cc_string)[0].value
|
|
|
|
if not c.is_multiple:
|
|
|
|
else:
|
|
|
|
if len(getattr(book, cc_string)) > 0:
|
|
|
|
cc_db_value = None
|
|
|
|
cc_db_value = getattr(book, cc_string)[0].value
|
|
|
|
if to_save[cc_string].strip():
|
|
|
|
else:
|
|
|
|
if c.datatype == 'bool':
|
|
|
|
cc_db_value = None
|
|
|
|
if to_save[cc_string] == 'None':
|
|
|
|
if to_save[cc_string].strip():
|
|
|
|
to_save[cc_string] = None
|
|
|
|
if c.datatype == 'bool':
|
|
|
|
|
|
|
|
if to_save[cc_string] == 'None':
|
|
|
|
|
|
|
|
to_save[cc_string] = None
|
|
|
|
|
|
|
|
else:
|
|
|
|
|
|
|
|
to_save[cc_string] = 1 if to_save[cc_string] == 'True' else 0
|
|
|
|
|
|
|
|
if to_save[cc_string] != cc_db_value:
|
|
|
|
|
|
|
|
if cc_db_value is not None:
|
|
|
|
|
|
|
|
if to_save[cc_string] is not None:
|
|
|
|
|
|
|
|
setattr(getattr(book, cc_string)[0], 'value', to_save[cc_string])
|
|
|
|
|
|
|
|
else:
|
|
|
|
|
|
|
|
del_cc = getattr(book, cc_string)[0]
|
|
|
|
|
|
|
|
getattr(book, cc_string).remove(del_cc)
|
|
|
|
|
|
|
|
db.session.delete(del_cc)
|
|
|
|
|
|
|
|
else:
|
|
|
|
else:
|
|
|
|
cc_class = db.cc_classes[c.id]
|
|
|
|
to_save[cc_string] = 1 if to_save[cc_string] == 'True' else 0
|
|
|
|
new_cc = cc_class(value=to_save[cc_string], book=book_id)
|
|
|
|
if to_save[cc_string] != cc_db_value:
|
|
|
|
db.session.add(new_cc)
|
|
|
|
if cc_db_value is not None:
|
|
|
|
elif c.datatype == 'int':
|
|
|
|
if to_save[cc_string] is not None:
|
|
|
|
if to_save[cc_string] == 'None':
|
|
|
|
setattr(getattr(book, cc_string)[0], 'value', to_save[cc_string])
|
|
|
|
to_save[cc_string] = None
|
|
|
|
else:
|
|
|
|
if to_save[cc_string] != cc_db_value:
|
|
|
|
del_cc = getattr(book, cc_string)[0]
|
|
|
|
if cc_db_value is not None:
|
|
|
|
getattr(book, cc_string).remove(del_cc)
|
|
|
|
if to_save[cc_string] is not None:
|
|
|
|
db.session.delete(del_cc)
|
|
|
|
setattr(getattr(book, cc_string)[0], 'value', to_save[cc_string])
|
|
|
|
|
|
|
|
else:
|
|
|
|
else:
|
|
|
|
|
|
|
|
cc_class = db.cc_classes[c.id]
|
|
|
|
|
|
|
|
new_cc = cc_class(value=to_save[cc_string], book=book_id)
|
|
|
|
|
|
|
|
db.session.add(new_cc)
|
|
|
|
|
|
|
|
elif c.datatype == 'int':
|
|
|
|
|
|
|
|
if to_save[cc_string] == 'None':
|
|
|
|
|
|
|
|
to_save[cc_string] = None
|
|
|
|
|
|
|
|
if to_save[cc_string] != cc_db_value:
|
|
|
|
|
|
|
|
if cc_db_value is not None:
|
|
|
|
|
|
|
|
if to_save[cc_string] is not None:
|
|
|
|
|
|
|
|
setattr(getattr(book, cc_string)[0], 'value', to_save[cc_string])
|
|
|
|
|
|
|
|
else:
|
|
|
|
|
|
|
|
del_cc = getattr(book, cc_string)[0]
|
|
|
|
|
|
|
|
getattr(book, cc_string).remove(del_cc)
|
|
|
|
|
|
|
|
db.session.delete(del_cc)
|
|
|
|
|
|
|
|
else:
|
|
|
|
|
|
|
|
cc_class = db.cc_classes[c.id]
|
|
|
|
|
|
|
|
new_cc = cc_class(value=to_save[cc_string], book=book_id)
|
|
|
|
|
|
|
|
db.session.add(new_cc)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
else:
|
|
|
|
|
|
|
|
if c.datatype == 'rating':
|
|
|
|
|
|
|
|
to_save[cc_string] = str(int(float(to_save[cc_string]) * 2))
|
|
|
|
|
|
|
|
if to_save[cc_string].strip() != cc_db_value:
|
|
|
|
|
|
|
|
if cc_db_value is not None:
|
|
|
|
|
|
|
|
# remove old cc_val
|
|
|
|
del_cc = getattr(book, cc_string)[0]
|
|
|
|
del_cc = getattr(book, cc_string)[0]
|
|
|
|
getattr(book, cc_string).remove(del_cc)
|
|
|
|
getattr(book, cc_string).remove(del_cc)
|
|
|
|
db.session.delete(del_cc)
|
|
|
|
if len(del_cc.books) == 0:
|
|
|
|
else:
|
|
|
|
db.session.delete(del_cc)
|
|
|
|
cc_class = db.cc_classes[c.id]
|
|
|
|
cc_class = db.cc_classes[c.id]
|
|
|
|
new_cc = cc_class(value=to_save[cc_string], book=book_id)
|
|
|
|
|
|
|
|
db.session.add(new_cc)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
else:
|
|
|
|
|
|
|
|
if c.datatype == 'rating':
|
|
|
|
|
|
|
|
to_save[cc_string] = str(int(float(to_save[cc_string]) * 2))
|
|
|
|
|
|
|
|
if to_save[cc_string].strip() != cc_db_value:
|
|
|
|
|
|
|
|
if cc_db_value is not None:
|
|
|
|
|
|
|
|
# remove old cc_val
|
|
|
|
|
|
|
|
del_cc = getattr(book, cc_string)[0]
|
|
|
|
|
|
|
|
getattr(book, cc_string).remove(del_cc)
|
|
|
|
|
|
|
|
if len(del_cc.books) == 0:
|
|
|
|
|
|
|
|
db.session.delete(del_cc)
|
|
|
|
|
|
|
|
cc_class = db.cc_classes[c.id]
|
|
|
|
|
|
|
|
new_cc = db.session.query(cc_class).filter(
|
|
|
|
|
|
|
|
cc_class.value == to_save[cc_string].strip()).first()
|
|
|
|
|
|
|
|
# if no cc val is found add it
|
|
|
|
|
|
|
|
if new_cc is None:
|
|
|
|
|
|
|
|
new_cc = cc_class(value=to_save[cc_string].strip())
|
|
|
|
|
|
|
|
db.session.add(new_cc)
|
|
|
|
|
|
|
|
new_cc = db.session.query(cc_class).filter(
|
|
|
|
new_cc = db.session.query(cc_class).filter(
|
|
|
|
cc_class.value == to_save[cc_string].strip()).first()
|
|
|
|
cc_class.value == to_save[cc_string].strip()).first()
|
|
|
|
# add cc value to book
|
|
|
|
# if no cc val is found add it
|
|
|
|
getattr(book, cc_string).append(new_cc)
|
|
|
|
if new_cc is None:
|
|
|
|
|
|
|
|
new_cc = cc_class(value=to_save[cc_string].strip())
|
|
|
|
|
|
|
|
db.session.add(new_cc)
|
|
|
|
|
|
|
|
new_cc = db.session.query(cc_class).filter(
|
|
|
|
|
|
|
|
cc_class.value == to_save[cc_string].strip()).first()
|
|
|
|
|
|
|
|
# add cc value to book
|
|
|
|
|
|
|
|
getattr(book, cc_string).append(new_cc)
|
|
|
|
|
|
|
|
else:
|
|
|
|
|
|
|
|
if cc_db_value is not None:
|
|
|
|
|
|
|
|
# remove old cc_val
|
|
|
|
|
|
|
|
del_cc = getattr(book, cc_string)[0]
|
|
|
|
|
|
|
|
getattr(book, cc_string).remove(del_cc)
|
|
|
|
|
|
|
|
if len(del_cc.books) == 0:
|
|
|
|
|
|
|
|
db.session.delete(del_cc)
|
|
|
|
else:
|
|
|
|
else:
|
|
|
|
if cc_db_value is not None:
|
|
|
|
input_tags = to_save[cc_string].split(',')
|
|
|
|
# remove old cc_val
|
|
|
|
input_tags = list(map(lambda it: it.strip(), input_tags))
|
|
|
|
del_cc = getattr(book, cc_string)[0]
|
|
|
|
modify_database_object(input_tags, getattr(book, cc_string), db.cc_classes[c.id], db.session, 'custom')
|
|
|
|
getattr(book, cc_string).remove(del_cc)
|
|
|
|
db.session.commit()
|
|
|
|
if len(del_cc.books) == 0:
|
|
|
|
author_names = []
|
|
|
|
db.session.delete(del_cc)
|
|
|
|
for author in book.authors:
|
|
|
|
|
|
|
|
author_names.append(author.name)
|
|
|
|
|
|
|
|
if "detail_view" in to_save:
|
|
|
|
|
|
|
|
return redirect(url_for('show_book', book_id=book.id))
|
|
|
|
else:
|
|
|
|
else:
|
|
|
|
input_tags = to_save[cc_string].split(',')
|
|
|
|
return render_title_template('book_edit.html', book=book, authors=author_names, cc=cc,
|
|
|
|
input_tags = list(map(lambda it: it.strip(), input_tags))
|
|
|
|
title=_(u"edit metadata"))
|
|
|
|
modify_database_object(input_tags, getattr(book, cc_string), db.cc_classes[c.id], db.session, 'custom')
|
|
|
|
|
|
|
|
db.session.commit()
|
|
|
|
|
|
|
|
author_names = []
|
|
|
|
|
|
|
|
for author in book.authors:
|
|
|
|
|
|
|
|
author_names.append(author.name)
|
|
|
|
|
|
|
|
if "detail_view" in to_save:
|
|
|
|
|
|
|
|
return redirect(url_for('show_book', book_id=book.id))
|
|
|
|
|
|
|
|
else:
|
|
|
|
else:
|
|
|
|
|
|
|
|
db.session.rollback()
|
|
|
|
|
|
|
|
flash( error, category="error")
|
|
|
|
return render_title_template('book_edit.html', book=book, authors=author_names, cc=cc,
|
|
|
|
return render_title_template('book_edit.html', book=book, authors=author_names, cc=cc,
|
|
|
|
title=_(u"edit metadata"))
|
|
|
|
title=_(u"edit metadata"))
|
|
|
|
else:
|
|
|
|
except Exception as e:
|
|
|
|
|
|
|
|
app.logger.exception(e)
|
|
|
|
db.session.rollback()
|
|
|
|
db.session.rollback()
|
|
|
|
flash( error, category="error")
|
|
|
|
flash(_("Error editing book, please check logfile for details"), category="error")
|
|
|
|
return render_title_template('book_edit.html', book=book, authors=author_names, cc=cc,
|
|
|
|
return redirect(url_for('show_book', book_id=book.id))
|
|
|
|
title=_(u"edit metadata"))
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def save_cover(url, book_path):
|
|
|
|
def save_cover(url, book_path):
|
|
|
|