|
|
|
@ -103,6 +103,7 @@ global_task = None
|
|
|
|
|
|
|
|
|
|
ALLOWED_EXTENSIONS = set(['txt', 'pdf', 'epub', 'mobi', 'azw', 'azw3', 'cbr', 'cbz', 'cbt', 'djvu', 'prc', 'doc', 'docx', 'fb2'])
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def md5(fname):
|
|
|
|
|
hash_md5 = hashlib.md5()
|
|
|
|
|
with open(fname, "rb") as f:
|
|
|
|
@ -110,6 +111,7 @@ def md5(fname):
|
|
|
|
|
hash_md5.update(chunk)
|
|
|
|
|
return hash_md5.hexdigest()
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class Singleton:
|
|
|
|
|
"""
|
|
|
|
|
A non-thread-safe helper class to ease implementing singletons.
|
|
|
|
@ -600,7 +602,7 @@ def modify_database_object(input_elements, db_book_object, db_object, db_session
|
|
|
|
|
# if no element is found add it
|
|
|
|
|
if new_element is None:
|
|
|
|
|
if db_type == 'author':
|
|
|
|
|
new_element = db_object(add_element, add_element.replace('|',','), "")
|
|
|
|
|
new_element = db_object(add_element, add_element.replace('|', ','), "")
|
|
|
|
|
elif db_type == 'series':
|
|
|
|
|
new_element = db_object(add_element, add_element)
|
|
|
|
|
elif db_type == 'custom':
|
|
|
|
@ -831,7 +833,7 @@ def feed_series(book_id):
|
|
|
|
|
if not off:
|
|
|
|
|
off = 0
|
|
|
|
|
entries, random, pagination = fill_indexpage((int(off) / (int(config.config_books_per_page)) + 1),
|
|
|
|
|
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)
|
|
|
|
|
xml = render_title_template('feed.xml', entries=entries, pagination=pagination)
|
|
|
|
|
response = make_response(xml)
|
|
|
|
|
response.headers["Content-Type"] = "application/atom+xml; charset=utf-8"
|
|
|
|
@ -850,6 +852,7 @@ def do_gdrive_download(df, headers):
|
|
|
|
|
total_size = int(df.metadata.get('fileSize'))
|
|
|
|
|
download_url = df.metadata.get('downloadUrl')
|
|
|
|
|
s = partial(total_size, 1024 * 1024) # I'm downloading BIG files, so 100M chunk size is fine for me
|
|
|
|
|
|
|
|
|
|
def stream():
|
|
|
|
|
for byte in s:
|
|
|
|
|
headers = {"Range": 'bytes=%s-%s' % (byte[0], byte[1])}
|
|
|
|
@ -969,13 +972,13 @@ def get_updater_status():
|
|
|
|
|
"6": _(u'Server is stopped'),
|
|
|
|
|
"7": _(u'Update finished, please press okay and reload page')
|
|
|
|
|
}
|
|
|
|
|
status['text']=text
|
|
|
|
|
status['text'] = text
|
|
|
|
|
helper.updater_thread = helper.Updater()
|
|
|
|
|
helper.updater_thread.start()
|
|
|
|
|
status['status']=helper.updater_thread.get_update_status()
|
|
|
|
|
status['status'] = helper.updater_thread.get_update_status()
|
|
|
|
|
elif request.method == "GET":
|
|
|
|
|
try:
|
|
|
|
|
status['status']=helper.updater_thread.get_update_status()
|
|
|
|
|
status['status'] = helper.updater_thread.get_update_status()
|
|
|
|
|
except Exception:
|
|
|
|
|
status['status'] = 7
|
|
|
|
|
return json.dumps(status)
|
|
|
|
@ -1055,6 +1058,7 @@ def newest_books(page):
|
|
|
|
|
else:
|
|
|
|
|
abort(404)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@app.route('/books/oldest', defaults={'page': 1})
|
|
|
|
|
@app.route('/books/oldest/page/<int:page>')
|
|
|
|
|
@login_required_if_no_ano
|
|
|
|
@ -1136,7 +1140,7 @@ def best_rated_books(page):
|
|
|
|
|
def discover(page):
|
|
|
|
|
if current_user.show_random_books():
|
|
|
|
|
entries, __, pagination = fill_indexpage(page, db.Books, True, func.randomblob(2))
|
|
|
|
|
pagination = Pagination(1, config.config_books_per_page,config.config_books_per_page)
|
|
|
|
|
pagination = Pagination(1, config.config_books_per_page, config.config_books_per_page)
|
|
|
|
|
return render_title_template('discover.html', entries=entries, pagination=pagination, title=_(u"Random Books"))
|
|
|
|
|
else:
|
|
|
|
|
abort(404)
|
|
|
|
@ -1150,7 +1154,7 @@ def author_list():
|
|
|
|
|
.join(db.books_authors_link).join(db.Books).filter(common_filters())\
|
|
|
|
|
.group_by('books_authors_link.author').order_by(db.Authors.sort).all()
|
|
|
|
|
for entry in entries:
|
|
|
|
|
entry.Authors.name=entry.Authors.name.replace('|',',')
|
|
|
|
|
entry.Authors.name = entry.Authors.name.replace('|', ',')
|
|
|
|
|
return render_title_template('list.html', entries=entries, folder='author', title=_(u"Author list"))
|
|
|
|
|
else:
|
|
|
|
|
abort(404)
|
|
|
|
@ -1166,7 +1170,7 @@ def author(book_id, page):
|
|
|
|
|
flash(_(u"Error opening eBook. File does not exist or file is not accessible:"), category="error")
|
|
|
|
|
return redirect(url_for("index"))
|
|
|
|
|
|
|
|
|
|
name = (db.session.query(db.Authors).filter(db.Authors.id == book_id).first().name).replace('|',',')
|
|
|
|
|
name = (db.session.query(db.Authors).filter(db.Authors.id == book_id).first().name).replace('|', ',')
|
|
|
|
|
|
|
|
|
|
author_info = None
|
|
|
|
|
other_books = []
|
|
|
|
@ -1198,7 +1202,6 @@ def get_unique_other_books(library_books, author_books):
|
|
|
|
|
return other_books
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@app.route("/series")
|
|
|
|
|
@login_required_if_no_ano
|
|
|
|
|
def series_list():
|
|
|
|
@ -1438,7 +1441,7 @@ def delete_book(book_id):
|
|
|
|
|
if config.config_use_google_drive:
|
|
|
|
|
helper.delete_book_gdrive(book) # ToDo really delete file
|
|
|
|
|
else:
|
|
|
|
|
helper.delete_book(book,config.config_calibre_dir)
|
|
|
|
|
helper.delete_book(book, config.config_calibre_dir)
|
|
|
|
|
# check if only this book links to:
|
|
|
|
|
# author, language, series, tags, custom columns
|
|
|
|
|
modify_database_object([u''], book.authors, db.Authors, db.session, 'author')
|
|
|
|
@ -1466,7 +1469,7 @@ def delete_book(book_id):
|
|
|
|
|
getattr(book, cc_string).remove(del_cc)
|
|
|
|
|
db.session.delete(del_cc)
|
|
|
|
|
else:
|
|
|
|
|
modify_database_object([u''], getattr(book, cc_string),db.cc_classes[c.id], db.session, 'custom')
|
|
|
|
|
modify_database_object([u''], getattr(book, cc_string), db.cc_classes[c.id], db.session, 'custom')
|
|
|
|
|
db.session.query(db.Books).filter(db.Books.id == book_id).delete()
|
|
|
|
|
db.session.commit()
|
|
|
|
|
else:
|
|
|
|
@ -1474,6 +1477,7 @@ def delete_book(book_id):
|
|
|
|
|
app.logger.info('Book with id "'+str(book_id)+'" could not be deleted')
|
|
|
|
|
return redirect(url_for('index'))
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@app.route("/gdrive/authenticate")
|
|
|
|
|
@login_required
|
|
|
|
|
@admin_required
|
|
|
|
@ -1619,10 +1623,10 @@ def search():
|
|
|
|
|
db.Books.publishers.any(db.Publishers.name.ilike("%" + term + "%")),
|
|
|
|
|
db.Books.title.ilike("%" + term + "%")))\
|
|
|
|
|
.filter(common_filters()).all()
|
|
|
|
|
# entries = db.session.query(db.Books).with_entities(db.Books.title).filter(db.Books.title.ilike("%" + term + "%")).all()
|
|
|
|
|
#result = db.session.execute("select name from authors where lower(name) like '%" + term.lower() + "%'")
|
|
|
|
|
#entries = result.fetchall()
|
|
|
|
|
#result.close()
|
|
|
|
|
# entries = db.session.query(db.Books).with_entities(db.Books.title).filter(db.Books.title.ilike("%" + term + "%")).all()
|
|
|
|
|
# result = db.session.execute("select name from authors where lower(name) like '%" + term.lower() + "%'")
|
|
|
|
|
# entries = result.fetchall()
|
|
|
|
|
# result.close()
|
|
|
|
|
return render_title_template('search.html', searchterm=term, entries=entries)
|
|
|
|
|
else:
|
|
|
|
|
return render_title_template('search.html', searchterm="")
|
|
|
|
@ -1724,9 +1728,10 @@ def get_cover(cover_path):
|
|
|
|
|
else:
|
|
|
|
|
return send_from_directory(os.path.join(config.config_calibre_dir, cover_path), "cover.jpg")
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@app.route("/show/<book_id>/<book_format>")
|
|
|
|
|
@login_required_if_no_ano
|
|
|
|
|
def serve_book(book_id,book_format):
|
|
|
|
|
def serve_book(book_id, book_format):
|
|
|
|
|
book_format = book_format.split(".")[0]
|
|
|
|
|
book = db.session.query(db.Books).filter(db.Books.id == book_id).first()
|
|
|
|
|
data = db.session.query(db.Data).filter(db.Data.book == book.id).filter(db.Data.format == book_format.upper()).first()
|
|
|
|
@ -1858,7 +1863,7 @@ def read_book(book_id, book_format):
|
|
|
|
|
elif book_format.lower() == "txt":
|
|
|
|
|
return render_title_template('readtxt.html', txtfile=book_id, title=_(u"Read a Book"))
|
|
|
|
|
else:
|
|
|
|
|
for fileext in ["cbr","cbt","cbz"]:
|
|
|
|
|
for fileext in ["cbr", "cbt", "cbz"]:
|
|
|
|
|
if book_format.lower() == fileext:
|
|
|
|
|
all_name = str(book_id) + "/" + book.data[0].name + "." + fileext
|
|
|
|
|
tmp_file = os.path.join(book_dir, book.data[0].name) + "." + fileext
|
|
|
|
@ -1960,7 +1965,7 @@ def login():
|
|
|
|
|
flash(_(u"you are now logged in as: '%(nickname)s'", nickname=user.nickname), category="success")
|
|
|
|
|
return redirect_back(url_for("index"))
|
|
|
|
|
else:
|
|
|
|
|
ipAdress=request.headers.get('X-Forwarded-For', request.remote_addr)
|
|
|
|
|
ipAdress = request.headers.get('X-Forwarded-For', request.remote_addr)
|
|
|
|
|
app.logger.info('Login failed for user "' + form['username'] + '" IP-adress: ' + ipAdress)
|
|
|
|
|
flash(_(u"Wrong Username or Password"), category="error")
|
|
|
|
|
|
|
|
|
@ -2265,7 +2270,6 @@ def show_shelf(shelf_id):
|
|
|
|
|
return redirect(url_for("index"))
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@app.route("/shelf/order/<int:shelf_id>", methods=["GET", "POST"])
|
|
|
|
|
@login_required
|
|
|
|
|
def order_shelf(shelf_id):
|
|
|
|
@ -2806,7 +2810,7 @@ def edit_book(book_id):
|
|
|
|
|
except Exception:
|
|
|
|
|
book.languages[index].language_name = _(isoLanguages.get(part3=book.languages[index].lang_code).name)
|
|
|
|
|
for author in book.authors:
|
|
|
|
|
author_names.append(author.name.replace('|',','))
|
|
|
|
|
author_names.append(author.name.replace('|', ','))
|
|
|
|
|
|
|
|
|
|
# Show form
|
|
|
|
|
if request.method != 'POST':
|
|
|
|
@ -2845,24 +2849,38 @@ def edit_book(book_id):
|
|
|
|
|
db.session.add(db_format)
|
|
|
|
|
|
|
|
|
|
to_save = request.form.to_dict()
|
|
|
|
|
|
|
|
|
|
if book.title != to_save["book_title"]:
|
|
|
|
|
book.title = to_save["book_title"]
|
|
|
|
|
edited_books_id.add(book.id)
|
|
|
|
|
|
|
|
|
|
input_authors = to_save["author_name"].split('&')
|
|
|
|
|
input_authors = map(lambda it: it.strip().replace(',','|'), input_authors)
|
|
|
|
|
input_authors = 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)
|
|
|
|
|
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)
|
|
|
|
|
book.author_sort = helper.get_sorted_author(input_authors[0])
|
|
|
|
|
|
|
|
|
|
error = False
|
|
|
|
|
for b in edited_books_id:
|
|
|
|
|
if config.config_use_google_drive:
|
|
|
|
|
error = helper.update_dir_structure_gdrive(b)
|
|
|
|
|
else:
|
|
|
|
|
error = helper.update_dir_stucture(b, config.config_calibre_dir)
|
|
|
|
|
if error: # stop on error
|
|
|
|
|
break
|
|
|
|
|
if config.config_use_google_drive:
|
|
|
|
|
updateGdriveCalibreFromLocal()
|
|
|
|
|
|
|
|
|
|
if not error:
|
|
|
|
|
if to_save["cover_url"] and save_cover(to_save["cover_url"], book.path):
|
|
|
|
|
book.has_cover = 1
|
|
|
|
|
|
|
|
|
@ -2996,23 +3014,21 @@ def edit_book(book_id):
|
|
|
|
|
else:
|
|
|
|
|
input_tags = to_save[cc_string].split(',')
|
|
|
|
|
input_tags = map(lambda it: it.strip(), input_tags)
|
|
|
|
|
modify_database_object(input_tags, getattr(book, cc_string),db.cc_classes[c.id], db.session, 'custom')
|
|
|
|
|
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)
|
|
|
|
|
for b in edited_books_id:
|
|
|
|
|
if config.config_use_google_drive:
|
|
|
|
|
helper.update_dir_structure_gdrive(b)
|
|
|
|
|
else:
|
|
|
|
|
helper.update_dir_stucture(b, config.config_calibre_dir)
|
|
|
|
|
if config.config_use_google_drive:
|
|
|
|
|
updateGdriveCalibreFromLocal()
|
|
|
|
|
if "detail_view" in to_save:
|
|
|
|
|
return redirect(url_for('show_book', book_id=book.id))
|
|
|
|
|
else:
|
|
|
|
|
return render_title_template('book_edit.html', book=book, authors=author_names, cc=cc,
|
|
|
|
|
title=_(u"edit metadata"))
|
|
|
|
|
else:
|
|
|
|
|
db.session.rollback()
|
|
|
|
|
flash( error, category="error")
|
|
|
|
|
return render_title_template('book_edit.html', book=book, authors=author_names, cc=cc,
|
|
|
|
|
title=_(u"edit metadata"))
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def save_cover(url, book_path):
|
|
|
|
@ -3173,4 +3189,3 @@ def start_gevent():
|
|
|
|
|
app.logger.info('Unable to listen on \'\', trying on IPv4 only...')
|
|
|
|
|
gevent_server = WSGIServer(('0.0.0.0', ub.config.config_port), app)
|
|
|
|
|
gevent_server.serve_forever()
|
|
|
|
|
|
|
|
|
|