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

Improved sorting for rated,random, hot books, read/unread book

This commit is contained in:
Ozzieisaacs 2019-03-16 15:48:09 +01:00
parent a66873a9e2
commit 9c1b3f136f
9 changed files with 469 additions and 450 deletions

View File

@ -25,8 +25,8 @@ import os
from flask import Blueprint, flash, redirect, url_for from flask import Blueprint, flash, redirect, url_for
from flask import abort, request, make_response from flask import abort, request, make_response
from flask_login import login_required, current_user, logout_user from flask_login import login_required, current_user, logout_user
from web import admin_required, render_title_template, before_request, speaking_language, unconfigured, \ from web import admin_required, render_title_template, before_request, unconfigured, \
login_required_if_no_ano, check_valid_domain login_required_if_no_ano
from cps import db, ub, Server, get_locale, config, app, updater_thread, babel from cps import db, ub, Server, get_locale, config, app, updater_thread, babel
import json import json
from datetime import datetime, timedelta from datetime import datetime, timedelta
@ -37,6 +37,7 @@ from babel import Locale as LC
from sqlalchemy.exc import IntegrityError from sqlalchemy.exc import IntegrityError
from gdriveutils import is_gdrive_ready, gdrive_support, downloadFile, deleteDatabaseOnChange, listRootFolders from gdriveutils import is_gdrive_ready, gdrive_support, downloadFile, deleteDatabaseOnChange, listRootFolders
import helper import helper
from helper import speaking_language, check_valid_domain
from werkzeug.security import generate_password_hash from werkzeug.security import generate_password_hash
try: try:
from imp import reload from imp import reload

View File

@ -31,8 +31,9 @@ import json
from flask_babel import gettext as _ from flask_babel import gettext as _
from uuid import uuid4 from uuid import uuid4
import helper import helper
from helper import order_authors, common_filters
from flask_login import current_user from flask_login import current_user
from web import login_required_if_no_ano, common_filters, order_authors, render_title_template, edit_required, \ from web import login_required_if_no_ano, render_title_template, edit_required, \
upload_required, login_required, EXTENSIONS_UPLOAD upload_required, login_required, EXTENSIONS_UPLOAD
import gdriveutils import gdriveutils
from shutil import move, copyfile from shutil import move, copyfile

View File

@ -32,9 +32,15 @@ from flask import send_from_directory, make_response, redirect, abort
from flask_babel import gettext as _ from flask_babel import gettext as _
from flask_login import current_user from flask_login import current_user
from babel.dates import format_datetime from babel.dates import format_datetime
from babel.core import UnknownLocaleError
from datetime import datetime from datetime import datetime
from babel import Locale as LC
import shutil import shutil
import requests import requests
from sqlalchemy.sql.expression import true, and_, false, text, func
from iso639 import languages as isoLanguages
from pagination import Pagination
try: try:
import gdriveutils as gd import gdriveutils as gd
except ImportError: except ImportError:
@ -558,3 +564,109 @@ def render_task_status(tasklist):
renderedtasklist.append(task) renderedtasklist.append(task)
return renderedtasklist return renderedtasklist
# Language and content filters for displaying in the UI
def common_filters():
if current_user.filter_language() != "all":
lang_filter = db.Books.languages.any(db.Languages.lang_code == current_user.filter_language())
else:
lang_filter = true()
content_rating_filter = false() if current_user.mature_content else \
db.Books.tags.any(db.Tags.name.in_(config.mature_content_tags()))
return and_(lang_filter, ~content_rating_filter)
# Creates for all stored languages a translated speaking name in the array for the UI
def speaking_language(languages=None):
if not languages:
languages = db.session.query(db.Languages).all()
for lang in languages:
try:
cur_l = LC.parse(lang.lang_code)
lang.name = cur_l.get_language_name(get_locale())
except UnknownLocaleError:
lang.name = _(isoLanguages.get(part3=lang.lang_code).name)
return languages
# checks if domain is in database (including wildcards)
# example SELECT * FROM @TABLE WHERE 'abcdefg' LIKE Name;
# from https://code.luasoftware.com/tutorials/flask/execute-raw-sql-in-flask-sqlalchemy/
def check_valid_domain(domain_text):
domain_text = domain_text.split('@', 1)[-1].lower()
sql = "SELECT * FROM registration WHERE :domain LIKE domain;"
result = ub.session.query(ub.Registration).from_statement(text(sql)).params(domain=domain_text).all()
return len(result)
# Orders all Authors in the list according to authors sort
def order_authors(entry):
sort_authors = entry.author_sort.split('&')
authors_ordered = list()
error = False
for auth in sort_authors:
# ToDo: How to handle not found authorname
result = db.session.query(db.Authors).filter(db.Authors.sort == auth.lstrip().strip()).first()
if not result:
error = True
break
authors_ordered.append(result)
if not error:
entry.authors = authors_ordered
return entry
# Fill indexpage with all requested data from database
def fill_indexpage(page, database, db_filter, order, *join):
if current_user.show_detail_random():
randm = db.session.query(db.Books).filter(common_filters())\
.order_by(func.random()).limit(config.config_random_books)
else:
randm = false()
off = int(int(config.config_books_per_page) * (page - 1))
pagination = Pagination(page, config.config_books_per_page,
len(db.session.query(database).filter(db_filter).filter(common_filters()).all()))
entries = db.session.query(database).join(*join, isouter=True).filter(db_filter).filter(common_filters()).\
order_by(*order).offset(off).limit(config.config_books_per_page).all()
for book in entries:
book = order_authors(book)
return entries, randm, pagination
# read search results from calibre-database and return it (function is used for feed and simple search
def get_search_results(term):
q = list()
authorterms = re.split("[, ]+", term)
for authorterm in authorterms:
q.append(db.Books.authors.any(db.Authors.name.ilike("%" + authorterm + "%")))
db.session.connection().connection.connection.create_function("lower", 1, db.lcase)
db.Books.authors.any(db.Authors.name.ilike("%" + term + "%"))
return db.session.query(db.Books).filter(common_filters()).filter(
db.or_(db.Books.tags.any(db.Tags.name.ilike("%" + term + "%")),
db.Books.series.any(db.Series.name.ilike("%" + term + "%")),
db.Books.authors.any(and_(*q)),
db.Books.publishers.any(db.Publishers.name.ilike("%" + term + "%")),
db.Books.title.ilike("%" + term + "%"))).all()
def get_unique_other_books(library_books, author_books):
# Get all identifiers (ISBN, Goodreads, etc) and filter author's books by that list so we show fewer duplicates
# Note: Not all images will be shown, even though they're available on Goodreads.com.
# See https://www.goodreads.com/topic/show/18213769-goodreads-book-images
identifiers = reduce(lambda acc, book: acc + map(lambda identifier: identifier.val, book.identifiers),
library_books, [])
other_books = filter(lambda book: book.isbn not in identifiers and book.gid["#text"] not in identifiers,
author_books)
# Fuzzy match book titles
if feature_support['levenshtein']:
library_titles = reduce(lambda acc, book: acc + [book.title], library_books, [])
other_books = filter(lambda author_book: not filter(
lambda library_book:
# Remove items in parentheses before comparing
Levenshtein.ratio(re.sub(r"\(.*\)", "", author_book.title), library_book) > 0.7,
library_titles
), other_books)
return other_books

View File

@ -30,12 +30,12 @@ import datetime
import ub import ub
from flask_login import current_user from flask_login import current_user
from functools import wraps from functools import wraps
from web import login_required_if_no_ano, fill_indexpage, common_filters, get_search_results, render_read_books from web import login_required_if_no_ano, common_filters, get_search_results, render_read_books, download_required
from sqlalchemy.sql.expression import func, text from sqlalchemy.sql.expression import func, text
import helper import helper
from werkzeug.security import check_password_hash from werkzeug.security import check_password_hash
from werkzeug.datastructures import Headers from werkzeug.datastructures import Headers
from web import download_required from helper import fill_indexpage
import sys import sys
try: try:

View File

@ -3,7 +3,6 @@
<div class="discover load-more"> <div class="discover load-more">
<h2>{{title}}</h2> <h2>{{title}}</h2>
<div class="row"> <div class="row">
{% for entry in entries %} {% for entry in entries %}
<div class="col-sm-3 col-lg-2 col-xs-6 book"> <div class="col-sm-3 col-lg-2 col-xs-6 book">
<div class="cover"> <div class="cover">

View File

@ -58,14 +58,14 @@ web. {% for entry in random %}
<div class="discover load-more"> <div class="discover load-more">
<h2 class="{{title}}">{{_(title)}}</h2> <h2 class="{{title}}">{{_(title)}}</h2>
<div class="filterheader hidden-xs hidden-sm"> <div class="filterheader hidden-xs hidden-sm">
<a id="new" class="btn btn-primary" href="{{url_for('web.newest_books')}}"><span class="glyphicon glyphicon-sort-by-order"></span></a> <a id="new" class="btn btn-primary" href="{{url_for('web.books_list', data=page, sort='new')}}"><span class="glyphicon glyphicon-sort-by-order"></span></a>
<a id="old" class="btn btn-primary" href="{{url_for('web.oldest_books')}}"><span class="glyphicon glyphicon-sort-by-order-alt"></span></a> <a id="old" class="btn btn-primary" href="{{url_for('web.books_list', data=page, sort='old')}}"><span class="glyphicon glyphicon-sort-by-order-alt"></span></a>
<a id="asc" class="btn btn-primary" href="{{url_for('web.titles_ascending')}}"><span class="glyphicon glyphicon-font"></span><span class="glyphicon glyphicon-sort-by-alphabet"></span></a> <a id="asc" class="btn btn-primary" href="{{url_for('web.books_list', data=page, sort='abc')}}"><span class="glyphicon glyphicon-font"></span><span class="glyphicon glyphicon-sort-by-alphabet"></span></a>
<a id="desc" class="btn btn-primary" href="{{url_for('web.titles_descending')}}"><span class="glyphicon glyphicon-font"></span><span class="glyphicon glyphicon-sort-by-alphabet-alt"></span></a> <a id="desc" class="btn btn-primary" href="{{url_for('web.books_list', data=page, sort='zyx')}}"><span class="glyphicon glyphicon-font"></span><span class="glyphicon glyphicon-sort-by-alphabet-alt"></span></a>
<a id="pub_new" class="btn btn-primary" href="{{url_for('web.titles_descending')}}"><span class="glyphicon glyphicon-calendar"></span><span class="glyphicon glyphicon-sort-by-order"></a> <a id="pub_new" class="btn btn-primary" href="{{url_for('web.books_list', data=page, sort='pubnew')}}"><span class="glyphicon glyphicon-calendar"></span><span class="glyphicon glyphicon-sort-by-order"></span></a>
<a id="pub_old" class="btn btn-primary" href="{{url_for('web.titles_descending')}}"><span class="glyphicon glyphicon-calendar"></span><span class="glyphicon glyphicon-sort-by-order-alt"></a> <a id="pub_old" class="btn btn-primary" href="{{url_for('web.books_list', data=page, sort='pubold')}}"><span class="glyphicon glyphicon-calendar"></span><span class="glyphicon glyphicon-sort-by-order-alt"></span></a>
<div class="btn-group character" role="group"> <div class="btn-group character" role="group">
<a id="no_shelf" class="btn btn-primary" href="{{url_for('web.titles_descending')}}"><span class="glyphicon glyphicon-list"></span><b>?</b></a> <a id="no_shelf" class="btn btn-primary" href="{{url_for('web.books_list', data=page, sort='pubold')}}"><span class="glyphicon glyphicon-list"></span><b>?</b></a>
<div id="all" class="btn btn-primary">{{_('All')}}</div> <div id="all" class="btn btn-primary">{{_('All')}}</div>
</div> </div>
</div> </div>

View File

@ -121,22 +121,10 @@
<ul class="list-unstyled" id="scnd-nav" intent in-standard-append="nav.navigation" in-mobile-after="#main-nav" in-mobile-class="nav navbar-nav"> <ul class="list-unstyled" id="scnd-nav" intent in-standard-append="nav.navigation" in-mobile-after="#main-nav" in-mobile-class="nav navbar-nav">
<li class="nav-head hidden-xs">{{_('Browse')}}</li> <li class="nav-head hidden-xs">{{_('Browse')}}</li>
{% if g.user.check_visibility(1024) %} <!-- ToDo: eliminate --> {% if g.user.check_visibility(1024) %} <!-- ToDo: eliminate -->
<!--li id="nav_sort" class="dropdown">
<a href="#" class="dropdown-toggle" data-toggle="dropdown" role="button" aria-haspopup="true" aria-expanded="false">
<span class="glyphicon glyphicon-sort-by-attributes"></span>{{_('Sorted Books')}}
<span class="caret"></span>
</a>
<ul class="dropdown-menu">
<li id="nav_sort_old" {% if page == 'newest' %}class="active"{% endif %}><a href="{{url_for('web.newest_books')}}">{{_('Sort By')}} {{_('Newest')}}</a></li>
<li id="nav_sort_new" {% if page == 'oldest' %}class="active"{% endif %}><a href="{{url_for('web.oldest_books')}}">{{_('Sort By')}} {{_('Oldest')}}</a></li>
<li id="nav_sort_asc" {% if page == 'a-z' %}class="active"{% endif %}><a href="{{url_for('web.titles_ascending')}}">{{_('Sort By')}} {{_('Title')}} ({{_('Ascending')}})</a></li>
<li id="nav_sort_desc" {% if page == 'z-a' %}class="active"{% endif %}><a href="{{url_for('web.titles_descending')}}">{{_('Sort By')}} {{_('Title')}} ({{_('Descending')}})</a></li>
</ul>
</li-->
{%endif%} {%endif%}
{% for element in sidebar %} {% for element in sidebar %}
{% if g.user.check_visibility(element['visibility']) and element['public'] %} {% if g.user.check_visibility(element['visibility']) and element['public'] %}
<li id="nav_{{element['id']}}" {% if page == element['page'] %}class="active"{% endif %}><a href="{{url_for(element['link'])}}"><span class="glyphicon {{element['glyph']}}"></span>{{_(element['text'])}}</a></li> <li id="nav_{{element['id']}}" {% if page == element['page'] %}class="active"{% endif %}><a href="{{url_for(element['link'], data=element['page'], sort='new')}}"><span class="glyphicon {{element['glyph']}}"></span>{{_(element['text'])}}</a></li>
{% endif %} {% endif %}
{% endfor %} {% endfor %}

View File

@ -107,21 +107,21 @@ def get_sidebar_config(kwargs=[]):
sidebar.append({"glyph": "glyphicon-book", "text": _('Recently Added'), "link": 'web.index', "id": "new", sidebar.append({"glyph": "glyphicon-book", "text": _('Recently Added'), "link": 'web.index', "id": "new",
"visibility": SIDEBAR_RECENT, 'public': True, "page": "root", "visibility": SIDEBAR_RECENT, 'public': True, "page": "root",
"show_text": _('Show recent books'), "config_show":True}) "show_text": _('Show recent books'), "config_show":True})
sidebar.append({"glyph": "glyphicon-fire", "text": _('Hot Books'), "link": 'web.hot_books', "id": "hot", sidebar.append({"glyph": "glyphicon-fire", "text": _('Hot Books'), "link": 'web.books_list', "id": "hot",
"visibility": SIDEBAR_HOT, 'public': True, "page": "hot", "show_text": _('Show hot books'), "visibility": SIDEBAR_HOT, 'public': True, "page": "hot", "show_text": _('Show hot books'),
"config_show":True}) "config_show":True})
sidebar.append( sidebar.append(
{"glyph": "glyphicon-star", "text": _('Best rated Books'), "link": 'web.best_rated_books', "id": "rated", {"glyph": "glyphicon-star", "text": _('Best rated Books'), "link": 'web.books_list', "id": "rated",
"visibility": SIDEBAR_BEST_RATED, 'public': True, "page": "rated", "visibility": SIDEBAR_BEST_RATED, 'public': True, "page": "rated",
"show_text": _('Show best rated books'), "config_show":True}) "show_text": _('Show best rated books'), "config_show":True})
sidebar.append({"glyph": "glyphicon-eye-open", "text": _('Read Books'), "link": 'web.read_books', "id": "read", sidebar.append({"glyph": "glyphicon-eye-open", "text": _('Read Books'), "link": 'web.books_list', "id": "read",
"visibility": SIDEBAR_READ_AND_UNREAD, 'public': (not g.user.is_anonymous), "page": "read", "visibility": SIDEBAR_READ_AND_UNREAD, 'public': (not g.user.is_anonymous), "page": "read",
"show_text": _('Show read and unread'), "config_show": content}) "show_text": _('Show read and unread'), "config_show": content})
sidebar.append( sidebar.append(
{"glyph": "glyphicon-eye-close", "text": _('Unread Books'), "link": 'web.unread_books', "id": "unread", {"glyph": "glyphicon-eye-close", "text": _('Unread Books'), "link": 'web.books_list', "id": "unread",
"visibility": SIDEBAR_READ_AND_UNREAD, 'public': (not g.user.is_anonymous), "page": "read", "visibility": SIDEBAR_READ_AND_UNREAD, 'public': (not g.user.is_anonymous), "page": "unread",
"show_text": _('Show unread'), "config_show":False}) "show_text": _('Show unread'), "config_show":False})
sidebar.append({"glyph": "glyphicon-random", "text": _('Discover'), "link": 'web.discover', "id": "rand", sidebar.append({"glyph": "glyphicon-random", "text": _('Discover'), "link": 'web.books_list', "id": "rand",
"visibility": SIDEBAR_RANDOM, 'public': True, "page": "discover", "visibility": SIDEBAR_RANDOM, 'public': True, "page": "discover",
"show_text": _('Show random books'), "config_show":True}) "show_text": _('Show random books'), "config_show":True})
sidebar.append({"glyph": "glyphicon-inbox", "text": _('Categories'), "link": 'web.category_list', "id": "cat", sidebar.append({"glyph": "glyphicon-inbox", "text": _('Categories'), "link": 'web.category_list', "id": "cat",

View File

@ -22,10 +22,11 @@
# along with this program. If not, see <http://www.gnu.org/licenses/>. # along with this program. If not, see <http://www.gnu.org/licenses/>.
from cps import mimetypes, global_WorkerThread, searched_ids from cps import mimetypes, global_WorkerThread, searched_ids
from flask import render_template, request, redirect, send_from_directory, make_response, g, flash, abort, \ from flask import render_template, request, redirect, send_from_directory, make_response, g, flash, abort, url_for
url_for
from werkzeug.exceptions import default_exceptions from werkzeug.exceptions import default_exceptions
import helper import helper
from helper import common_filters, get_search_results, fill_indexpage, speaking_language, check_valid_domain, \
order_authors
import os import os
from sqlalchemy.exc import IntegrityError from sqlalchemy.exc import IntegrityError
from flask_login import login_user, logout_user, login_required, current_user from flask_login import login_user, logout_user, login_required, current_user
@ -36,9 +37,7 @@ from babel import Locale as LC
from babel.dates import format_date from babel.dates import format_date
from babel.core import UnknownLocaleError from babel.core import UnknownLocaleError
import base64 import base64
# from sqlalchemy.sql import * from sqlalchemy.sql.expression import text, func, true, and_, false, not_
from sqlalchemy import String as SQLString
from sqlalchemy.sql.expression import text, func, cast, true, and_, false
import json import json
import datetime import datetime
from iso639 import languages as isoLanguages from iso639 import languages as isoLanguages
@ -135,6 +134,7 @@ for ex in default_exceptions:
web = Blueprint('web', __name__) web = Blueprint('web', __name__)
# ################################### Login logic and rights management ###############################################
@lm.user_loader @lm.user_loader
def load_user(user_id): def load_user(user_id):
@ -239,89 +239,7 @@ def edit_required(f):
return inner return inner
# ################################### Helper functions ################################################################
# Language and content filters for displaying in the UI
def common_filters():
if current_user.filter_language() != "all":
lang_filter = db.Books.languages.any(db.Languages.lang_code == current_user.filter_language())
else:
lang_filter = true()
content_rating_filter = false() if current_user.mature_content else \
db.Books.tags.any(db.Tags.name.in_(config.mature_content_tags()))
return and_(lang_filter, ~content_rating_filter)
# Creates for all stored languages a translated speaking name in the array for the UI
def speaking_language(languages=None):
if not languages:
languages = db.session.query(db.Languages).all()
for lang in languages:
try:
cur_l = LC.parse(lang.lang_code)
lang.name = cur_l.get_language_name(get_locale())
except UnknownLocaleError:
lang.name = _(isoLanguages.get(part3=lang.lang_code).name)
return languages
# checks if domain is in database (including wildcards)
# example SELECT * FROM @TABLE WHERE 'abcdefg' LIKE Name;
# from https://code.luasoftware.com/tutorials/flask/execute-raw-sql-in-flask-sqlalchemy/
def check_valid_domain(domain_text):
domain_text = domain_text.split('@', 1)[-1].lower()
sql = "SELECT * FROM registration WHERE :domain LIKE domain;"
result = ub.session.query(ub.Registration).from_statement(text(sql)).params(domain=domain_text).all()
return len(result)
# Orders all Authors in the list according to authors sort
def order_authors(entry):
sort_authors = entry.author_sort.split('&')
authors_ordered = list()
error = False
for auth in sort_authors:
# ToDo: How to handle not found authorname
result = db.session.query(db.Authors).filter(db.Authors.sort == auth.lstrip().strip()).first()
if not result:
error = True
break
authors_ordered.append(result)
if not error:
entry.authors = authors_ordered
return entry
# Fill indexpage with all requested data from database
def fill_indexpage(page, database, db_filter, order, *join):
if current_user.show_detail_random():
randm = db.session.query(db.Books).filter(common_filters())\
.order_by(func.random()).limit(config.config_random_books)
else:
randm = false()
off = int(int(config.config_books_per_page) * (page - 1))
pagination = Pagination(page, config.config_books_per_page,
len(db.session.query(database).filter(db_filter).filter(common_filters()).all()))
entries = db.session.query(database).join(*join, isouter=True).filter(db_filter).filter(common_filters()).\
order_by(*order).offset(off).limit(config.config_books_per_page).all()
for book in entries:
book = order_authors(book)
return entries, randm, pagination
# read search results from calibre-database and return it (function is used for feed and simple search
def get_search_results(term):
q = list()
authorterms = re.split("[, ]+", term)
for authorterm in authorterms:
q.append(db.Books.authors.any(db.Authors.name.ilike("%" + authorterm + "%")))
db.session.connection().connection.connection.create_function("lower", 1, db.lcase)
db.Books.authors.any(db.Authors.name.ilike("%" + term + "%"))
return db.session.query(db.Books).filter(common_filters()).filter(
db.or_(db.Books.tags.any(db.Tags.name.ilike("%" + term + "%")),
db.Books.series.any(db.Series.name.ilike("%" + term + "%")),
db.Books.authors.any(and_(*q)),
db.Books.publishers.any(db.Publishers.name.ilike("%" + term + "%")),
db.Books.title.ilike("%" + term + "%"))).all()
# Returns the template for rendering and includes the instance name # Returns the template for rendering and includes the instance name
@ -343,6 +261,9 @@ def before_request():
return redirect(url_for('admin.basic_configuration')) return redirect(url_for('admin.basic_configuration'))
# ################################### data provider functions #########################################################
@web.route("/ajax/emailstat") @web.route("/ajax/emailstat")
@login_required @login_required
def get_email_status_json(): def get_email_status_json():
@ -354,8 +275,59 @@ def get_email_status_json():
return response return response
@web.route("/ajax/bookmark/<int:book_id>/<book_format>", methods=['POST'])
@login_required
def bookmark(book_id, book_format):
bookmark_key = request.form["bookmark"]
ub.session.query(ub.Bookmark).filter(ub.and_(ub.Bookmark.user_id == int(current_user.id),
ub.Bookmark.book_id == book_id,
ub.Bookmark.format == book_format)).delete()
if not bookmark_key:
ub.session.commit()
return "", 204
lbookmark = ub.Bookmark(user_id=current_user.id,
book_id=book_id,
format=book_format,
bookmark_key=bookmark_key)
ub.session.merge(lbookmark)
ub.session.commit()
return "", 201
@web.route("/ajax/toggleread/<int:book_id>", methods=['POST'])
@login_required
def toggle_read(book_id):
if not config.config_read_column:
book = ub.session.query(ub.ReadBook).filter(ub.and_(ub.ReadBook.user_id == int(current_user.id),
ub.ReadBook.book_id == book_id)).first()
if book:
book.is_read = not book.is_read
else:
readBook = ub.ReadBook()
readBook.user_id = int(current_user.id)
readBook.book_id = book_id
readBook.is_read = True
book = readBook
ub.session.merge(book)
ub.session.commit()
else:
try:
db.session.connection().connection.connection.create_function("title_sort", 1, db.title_sort)
book = db.session.query(db.Books).filter(db.Books.id == book_id).filter(common_filters()).first()
read_status = getattr(book, 'custom_column_' + str(config.config_read_column))
if len(read_status):
read_status[0].value = not read_status[0].value
db.session.commit()
else:
cc_class = db.cc_classes[config.config_read_column]
new_cc = cc_class(value=1, book=book_id)
db.session.add(new_cc)
db.session.commit()
except KeyError:
app.logger.error(
u"Custom Column No.%d is not exisiting in calibre database" % config.config_read_column)
return ""
''' '''
@web.route("/ajax/getcomic/<int:book_id>/<book_format>/<int:page>") @web.route("/ajax/getcomic/<int:book_id>/<book_format>/<int:page>")
@ -408,6 +380,7 @@ def get_comic_book(book_id, book_format, page):
return "", 204 return "", 204
''' '''
# ################################### Typeahead ##################################################################
@web.route("/get_authors_json", methods=['GET', 'POST']) @web.route("/get_authors_json", methods=['GET', 'POST'])
@login_required_if_no_ano @login_required_if_no_ano
@ -491,6 +464,8 @@ def get_matching_tags():
return json_dumps return json_dumps
# ################################### View Books list ##################################################################
@web.route("/", defaults={'page': 1}) @web.route("/", defaults={'page': 1})
@web.route('/page/<int:page>') @web.route('/page/<int:page>')
@login_required_if_no_ano @login_required_if_no_ano
@ -500,71 +475,82 @@ def index(page):
title=_(u"Recently Added Books"), page="root") title=_(u"Recently Added Books"), page="root")
@web.route('/books/newest', defaults={'page': 1}) @web.route('/<data>/<sort>', defaults={'page': 1})
@web.route('/books/newest/page/<int:page>') @web.route('/<data>/<sort>/<int:page>')
@login_required_if_no_ano @login_required_if_no_ano
def newest_books(page): def books_list(data,sort, page):
entries, random, pagination = fill_indexpage(page, db.Books, True, [db.Books.pubdate.desc()]) order = [db.Books.timestamp.desc()]
return render_title_template('index.html', random=random, entries=entries, pagination=pagination, if sort == 'pubnew':
title=_(u"Newest Books"), page="newest") order = [db.Books.pubdate.desc()]
if sort == 'pubold':
order = [db.Books.pubdate]
@web.route('/books/oldest', defaults={'page': 1}) if sort == 'abc':
@web.route('/books/oldest/page/<int:page>') [db.Books.sort]
@login_required_if_no_ano if sort == 'zyx':
def oldest_books(page): [db.Books.sort.desc()]
entries, random, pagination = fill_indexpage(page, db.Books, True, [db.Books.pubdate]) if sort == 'new':
return render_title_template('index.html', random=random, entries=entries, pagination=pagination, order = [db.Books.timestamp.desc()]
title=_(u"Oldest Books"), page="oldest") if sort == 'old':
order = [db.Books.timestamp]
@web.route('/books/a-z', defaults={'page': 1}) if data == "rated":
@web.route('/books/a-z/page/<int:page>') if current_user.check_visibility(ub.SIDEBAR_BEST_RATED):
@login_required_if_no_ano entries, random, pagination = fill_indexpage(page, db.Books, db.Books.ratings.any(db.Ratings.rating > 9),
def titles_ascending(page): order)
entries, random, pagination = fill_indexpage(page, db.Books, True, [db.Books.sort]) return render_title_template('index.html', random=random, entries=entries, pagination=pagination,
return render_title_template('index.html', random=random, entries=entries, pagination=pagination, title=_(u"Best rated books"), page="rated")
title=_(u"Books (A-Z)"), page="a-z") else:
abort(404)
elif data == "discover":
@web.route('/books/z-a', defaults={'page': 1}) if current_user.check_visibility(ub.SIDEBAR_RANDOM):
@web.route('/books/z-a/page/<int:page>') entries, __, pagination = fill_indexpage(page, db.Books, True, [func.randomblob(2)])
@login_required_if_no_ano pagination = Pagination(1, config.config_books_per_page, config.config_books_per_page)
def titles_descending(page): return render_title_template('discover.html', entries=entries, pagination=pagination,
entries, random, pagination = fill_indexpage(page, db.Books, True, [db.Books.sort.desc()]) title=_(u"Random Books"), page="discover")
return render_title_template('index.html', random=random, entries=entries, pagination=pagination, else:
title=_(u"Books (Z-A)"), page="z-a") abort(404)
elif data == "unread":
return render_read_books(page, False, order=order)
elif data == "read":
return render_read_books(page, True, order=order)
elif data == "hot":
if current_user.check_visibility(ub.SIDEBAR_HOT):
if current_user.show_detail_random():
random = db.session.query(db.Books).filter(common_filters()) \
.order_by(func.random()).limit(config.config_random_books)
else:
random = false()
off = int(int(config.config_books_per_page) * (page - 1))
all_books = ub.session.query(ub.Downloads, ub.func.count(ub.Downloads.book_id)).order_by(
ub.func.count(ub.Downloads.book_id).desc()).group_by(ub.Downloads.book_id)
hot_books = all_books.offset(off).limit(config.config_books_per_page)
entries = list()
for book in hot_books:
downloadBook = db.session.query(db.Books).filter(common_filters()).filter(
db.Books.id == book.Downloads.book_id).first()
if downloadBook:
entries.append(downloadBook)
else:
ub.delete_download(book.Downloads.book_id)
# ub.session.query(ub.Downloads).filter(book.Downloads.book_id == ub.Downloads.book_id).delete()
# ub.session.commit()
numBooks = entries.__len__()
pagination = Pagination(page, config.config_books_per_page, numBooks)
return render_title_template('index.html', random=random, entries=entries, pagination=pagination,
title=_(u"Hot Books (most downloaded)"), page="hot")
else:
abort(404)
else:
entries, random, pagination = fill_indexpage(page, db.Books, True, order)
return render_title_template('index.html', random=random, entries=entries, pagination=pagination,
title=_(u"Books"), page="newest")
'''
@web.route("/hot", defaults={'page': 1}) @web.route("/hot", defaults={'page': 1})
@web.route('/hot/page/<int:page>') @web.route('/hot/page/<int:page>')
@login_required_if_no_ano @login_required_if_no_ano
def hot_books(page): def hot_books(page):
if current_user.check_visibility(ub.SIDEBAR_HOT):
if current_user.show_detail_random():
random = db.session.query(db.Books).filter(common_filters())\
.order_by(func.random()).limit(config.config_random_books)
else:
random = false()
off = int(int(config.config_books_per_page) * (page - 1))
all_books = ub.session.query(ub.Downloads, ub.func.count(ub.Downloads.book_id)).order_by(
ub.func.count(ub.Downloads.book_id).desc()).group_by(ub.Downloads.book_id)
hot_books = all_books.offset(off).limit(config.config_books_per_page)
entries = list()
for book in hot_books:
downloadBook = db.session.query(db.Books).filter(common_filters()).filter(db.Books.id == book.Downloads.book_id).first()
if downloadBook:
entries.append(downloadBook)
else:
ub.delete_download(book.Downloads.book_id)
# ub.session.query(ub.Downloads).filter(book.Downloads.book_id == ub.Downloads.book_id).delete()
# ub.session.commit()
numBooks = entries.__len__()
pagination = Pagination(page, config.config_books_per_page, numBooks)
return render_title_template('index.html', random=random, entries=entries, pagination=pagination,
title=_(u"Hot Books (most downloaded)"), page="hot")
else:
abort(404)
@web.route("/rated", defaults={'page': 1}) @web.route("/rated", defaults={'page': 1})
@ -590,7 +576,7 @@ def discover(page):
return render_title_template('discover.html', entries=entries, pagination=pagination, return render_title_template('discover.html', entries=entries, pagination=pagination,
title=_(u"Random Books"), page="discover") title=_(u"Random Books"), page="discover")
else: else:
abort(404) abort(404)'''
@web.route("/author") @web.route("/author")
@ -629,7 +615,7 @@ def author(book_id, page):
try: try:
gc = GoodreadsClient(config.config_goodreads_api_key, config.config_goodreads_api_secret) gc = GoodreadsClient(config.config_goodreads_api_key, config.config_goodreads_api_secret)
author_info = gc.find_author(author_name=name) author_info = gc.find_author(author_name=name)
other_books = get_unique_other_books(entries.all(), author_info.books) other_books = helper.get_unique_other_books(entries.all(), author_info.books)
except Exception: except Exception:
# Skip goodreads, if site is down/inaccessible # Skip goodreads, if site is down/inaccessible
app.logger.error('Goodreads website is down/inaccessible') app.logger.error('Goodreads website is down/inaccessible')
@ -670,28 +656,6 @@ def publisher(book_id, page):
abort(404) abort(404)
def get_unique_other_books(library_books, author_books):
# Get all identifiers (ISBN, Goodreads, etc) and filter author's books by that list so we show fewer duplicates
# Note: Not all images will be shown, even though they're available on Goodreads.com.
# See https://www.goodreads.com/topic/show/18213769-goodreads-book-images
identifiers = reduce(lambda acc, book: acc + map(lambda identifier: identifier.val, book.identifiers),
library_books, [])
other_books = filter(lambda book: book.isbn not in identifiers and book.gid["#text"] not in identifiers,
author_books)
# Fuzzy match book titles
if feature_support['levenshtein']:
library_titles = reduce(lambda acc, book: acc + [book.title], library_books, [])
other_books = filter(lambda author_book: not filter(
lambda library_book:
# Remove items in parentheses before comparing
Levenshtein.ratio(re.sub(r"\(.*\)", "", author_book.title), library_book) > 0.7,
library_titles
), other_books)
return other_books
@web.route("/series") @web.route("/series")
@login_required_if_no_ano @login_required_if_no_ano
def series_list(): def series_list():
@ -854,136 +818,17 @@ def category(book_id, page):
abort(404) abort(404)
@web.route("/ajax/toggleread/<int:book_id>", methods=['POST'])
@login_required
def toggle_read(book_id):
if not config.config_read_column:
book = ub.session.query(ub.ReadBook).filter(ub.and_(ub.ReadBook.user_id == int(current_user.id),
ub.ReadBook.book_id == book_id)).first()
if book:
book.is_read = not book.is_read
else:
readBook = ub.ReadBook()
readBook.user_id = int(current_user.id)
readBook.book_id = book_id
readBook.is_read = True
book = readBook
ub.session.merge(book)
ub.session.commit()
else:
try:
db.session.connection().connection.connection.create_function("title_sort", 1, db.title_sort)
book = db.session.query(db.Books).filter(db.Books.id == book_id).filter(common_filters()).first()
read_status = getattr(book, 'custom_column_' + str(config.config_read_column))
if len(read_status):
read_status[0].value = not read_status[0].value
db.session.commit()
else:
cc_class = db.cc_classes[config.config_read_column]
new_cc = cc_class(value=1, book=book_id)
db.session.add(new_cc)
db.session.commit()
except KeyError:
app.logger.error(
u"Custom Column No.%d is not exisiting in calibre database" % config.config_read_column)
return ""
@web.route("/book/<int:book_id>")
@login_required_if_no_ano
def show_book(book_id):
entries = db.session.query(db.Books).filter(db.Books.id == book_id).filter(common_filters()).first()
if entries:
for index in range(0, len(entries.languages)):
try:
entries.languages[index].language_name = LC.parse(entries.languages[index].lang_code).get_language_name(
get_locale())
except UnknownLocaleError:
entries.languages[index].language_name = _(
isoLanguages.get(part3=entries.languages[index].lang_code).name)
tmpcc = db.session.query(db.Custom_Columns).filter(db.Custom_Columns.datatype.notin_(db.cc_exceptions)).all()
if config.config_columns_to_ignore:
cc = []
for col in tmpcc:
r = re.compile(config.config_columns_to_ignore)
if r.match(col.label):
cc.append(col)
else:
cc = tmpcc
book_in_shelfs = []
shelfs = ub.session.query(ub.BookShelf).filter(ub.BookShelf.book_id == book_id).all()
for entry in shelfs:
book_in_shelfs.append(entry.shelf)
if not current_user.is_anonymous:
if not config.config_read_column:
matching_have_read_book = ub.session.query(ub.ReadBook).\
filter(ub.and_(ub.ReadBook.user_id == int(current_user.id), ub.ReadBook.book_id == book_id)).all()
have_read = len(matching_have_read_book) > 0 and matching_have_read_book[0].is_read
else:
try:
matching_have_read_book = getattr(entries, 'custom_column_'+str(config.config_read_column))
have_read = len(matching_have_read_book) > 0 and matching_have_read_book[0].value
except KeyError:
app.logger.error(
u"Custom Column No.%d is not exisiting in calibre database" % config.config_read_column)
have_read = None
else:
have_read = None
entries.tags = sort(entries.tags, key=lambda tag: tag.name)
entries = order_authors(entries)
kindle_list = helper.check_send_to_kindle(entries)
reader_list = helper.check_read_formats(entries)
audioentries = []
for media_format in entries.data:
if media_format.format.lower() in EXTENSIONS_AUDIO:
audioentries.append(media_format.format.lower())
return render_title_template('detail.html', entry=entries, audioentries=audioentries, cc=cc,
is_xhr=request.is_xhr, title=entries.title, books_shelfs=book_in_shelfs,
have_read=have_read, kindle_list=kindle_list, reader_list=reader_list, page="book")
else:
flash(_(u"Error opening eBook. File does not exist or file is not accessible:"), category="error")
return redirect(url_for("web.index"))
@web.route("/ajax/bookmark/<int:book_id>/<book_format>", methods=['POST'])
@login_required
def bookmark(book_id, book_format):
bookmark_key = request.form["bookmark"]
ub.session.query(ub.Bookmark).filter(ub.and_(ub.Bookmark.user_id == int(current_user.id),
ub.Bookmark.book_id == book_id,
ub.Bookmark.format == book_format)).delete()
if not bookmark_key:
ub.session.commit()
return "", 204
lbookmark = ub.Bookmark(user_id=current_user.id,
book_id=book_id,
format=book_format,
bookmark_key=bookmark_key)
ub.session.merge(lbookmark)
ub.session.commit()
return "", 201
@web.route("/tasks") @web.route("/tasks")
@login_required @login_required
def get_tasks_status(): def get_tasks_status():
# if current user admin, show all email, otherwise only own emails # if current user admin, show all email, otherwise only own emails
tasks = global_WorkerThread.get_taskstatus() tasks = global_WorkerThread.get_taskstatus()
# UIanswer = copy.deepcopy(answer)
answer = helper.render_task_status(tasks) answer = helper.render_task_status(tasks)
# foreach row format row
return render_title_template('tasks.html', entries=answer, title=_(u"Tasks"), page="tasks") return render_title_template('tasks.html', entries=answer, title=_(u"Tasks"), page="tasks")
# ################################### Search functions ################################################################
@web.route("/search", methods=["GET"]) @web.route("/search", methods=["GET"])
@login_required_if_no_ano @login_required_if_no_ano
def search(): def search():
@ -1148,6 +993,60 @@ def advanced_search():
series=series, title=_(u"search"), cc=cc, page="advsearch") series=series, title=_(u"search"), cc=cc, page="advsearch")
'''@web.route("/unreadbooks/", defaults={'page': 1})
@web.route("/unreadbooks/<int:page>'")
@login_required_if_no_ano
def unread_books(page):
return render_read_books(page, False)
@web.route("/readbooks/", defaults={'page': 1})
@web.route("/readbooks/<int:page>'")
@login_required_if_no_ano
def read_books(page):
return render_read_books(page, True)'''
def render_read_books(page, are_read, as_xml=False, order=[]):
if not config.config_read_column:
readBooks = ub.session.query(ub.ReadBook).filter(ub.ReadBook.user_id == int(current_user.id))\
.filter(ub.ReadBook.is_read is True).all()
readBookIds = [x.book_id for x in readBooks]
else:
try:
readBooks = db.session.query(db.cc_classes[config.config_read_column])\
.filter(db.cc_classes[config.config_read_column].value is True).all()
readBookIds = [x.book for x in readBooks]
except KeyError:
app.logger.error(u"Custom Column No.%d is not existing in calibre database" % config.config_read_column)
readBookIds = []
if are_read:
db_filter = db.Books.id.in_(readBookIds)
else:
db_filter = ~db.Books.id.in_(readBookIds)
entries, random, pagination = fill_indexpage(page, db.Books, db_filter, order)
if as_xml:
xml = render_title_template('feed.xml', entries=entries, pagination=pagination)
response = make_response(xml)
response.headers["Content-Type"] = "application/xml; charset=utf-8"
return response
else:
if are_read:
name = _(u'Read Books') + ' (' + str(len(readBookIds)) + ')'
pagename = "read"
else:
total_books = db.session.query(func.count(db.Books.id)).scalar()
name = _(u'Unread Books') + ' (' + str(total_books - len(readBookIds)) + ')'
pagename = "unread"
return render_title_template('index.html', random=random, entries=entries, pagination=pagination,
title=name, page=pagename)
# ################################### Download/Send ##################################################################
@web.route("/cover/<int:book_id>") @web.route("/cover/<int:book_id>")
@login_required_if_no_ano @login_required_if_no_ano
def get_cover(book_id): def get_cover(book_id):
@ -1175,113 +1074,6 @@ def serve_book(book_id, book_format):
return send_from_directory(os.path.join(config.config_calibre_dir, book.path), data.name + "." + book_format) return send_from_directory(os.path.join(config.config_calibre_dir, book.path), data.name + "." + book_format)
@web.route("/unreadbooks/", defaults={'page': 1})
@web.route("/unreadbooks/<int:page>'")
@login_required_if_no_ano
def unread_books(page):
return render_read_books(page, False)
@web.route("/readbooks/", defaults={'page': 1})
@web.route("/readbooks/<int:page>'")
@login_required_if_no_ano
def read_books(page):
return render_read_books(page, True)
def render_read_books(page, are_read, as_xml=False):
if not config.config_read_column:
readBooks = ub.session.query(ub.ReadBook).filter(ub.ReadBook.user_id == int(current_user.id))\
.filter(ub.ReadBook.is_read is True).all()
readBookIds = [x.book_id for x in readBooks]
else:
try:
readBooks = db.session.query(db.cc_classes[config.config_read_column])\
.filter(db.cc_classes[config.config_read_column].value is True).all()
readBookIds = [x.book for x in readBooks]
except KeyError:
app.logger.error(u"Custom Column No.%d is not existing in calibre database" % config.config_read_column)
readBookIds = []
if are_read:
db_filter = db.Books.id.in_(readBookIds)
else:
db_filter = ~db.Books.id.in_(readBookIds)
entries, random, pagination = fill_indexpage(page, db.Books, db_filter, [db.Books.timestamp.desc()])
if as_xml:
xml = render_title_template('feed.xml', entries=entries, pagination=pagination)
response = make_response(xml)
response.headers["Content-Type"] = "application/xml; charset=utf-8"
return response
else:
if are_read:
name = _(u'Read Books') + ' (' + str(len(readBookIds)) + ')'
else:
total_books = db.session.query(func.count(db.Books.id)).scalar()
name = _(u'Unread Books') + ' (' + str(total_books - len(readBookIds)) + ')'
return render_title_template('index.html', random=random, entries=entries, pagination=pagination,
title=_(name, name=name), page="read")
@web.route("/read/<int:book_id>/<book_format>")
@login_required_if_no_ano
def read_book(book_id, book_format):
book = db.session.query(db.Books).filter(db.Books.id == book_id).first()
if not book:
flash(_(u"Error opening eBook. File does not exist or file is not accessible:"), category="error")
return redirect(url_for("web.index"))
# check if book was downloaded before
bookmark = None
if current_user.is_authenticated:
bookmark = ub.session.query(ub.Bookmark).filter(ub.and_(ub.Bookmark.user_id == int(current_user.id),
ub.Bookmark.book_id == book_id,
ub.Bookmark.format == book_format.upper())).first()
if book_format.lower() == "epub":
return render_title_template('read.html', bookid=book_id, title=_(u"Read a Book"), bookmark=bookmark)
elif book_format.lower() == "pdf":
return render_title_template('readpdf.html', pdffile=book_id, title=_(u"Read a Book"))
elif book_format.lower() == "txt":
return render_title_template('readtxt.html', txtfile=book_id, title=_(u"Read a Book"))
elif book_format.lower() == "mp3":
entries = db.session.query(db.Books).filter(db.Books.id == book_id).filter(common_filters()).first()
return render_title_template('listenmp3.html', mp3file=book_id, audioformat=book_format.lower(),
title=_(u"Read a Book"), entry=entries, bookmark=bookmark)
elif book_format.lower() == "m4b":
entries = db.session.query(db.Books).filter(db.Books.id == book_id).filter(common_filters()).first()
return render_title_template('listenmp3.html', mp3file=book_id, audioformat=book_format.lower(),
title=_(u"Read a Book"), entry=entries, bookmark=bookmark)
elif book_format.lower() == "m4a":
entries = db.session.query(db.Books).filter(db.Books.id == book_id).filter(common_filters()).first()
return render_title_template('listenmp3.html', mp3file=book_id, audioformat=book_format.lower(),
title=_(u"Read a Book"), entry=entries, bookmark=bookmark)
else:
book_dir = os.path.join(config.get_main_dir, "cps", "static", str(book_id))
if not os.path.exists(book_dir):
os.mkdir(book_dir)
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
# if not os.path.exists(all_name):
# cbr_file = os.path.join(config.config_calibre_dir, book.path, book.data[0].name) + "." + fileext
# copyfile(cbr_file, tmp_file)
return render_title_template('readcbr.html', comicfile=all_name, title=_(u"Read a Book"),
extension=fileext)
'''if feature_support['rar']:
extensionList = ["cbr","cbt","cbz"]
else:
extensionList = ["cbt","cbz"]
for fileext in extensionList:
if book_format.lower() == fileext:
return render_title_template('readcbr.html', comicfile=book_id,
extension=fileext, title=_(u"Read a Book"), book=book)
flash(_(u"Error opening eBook. File does not exist or file is not accessible."), category="error")
return redirect(url_for("web.index"))'''
@web.route("/download/<int:book_id>/<book_format>") @web.route("/download/<int:book_id>/<book_format>")
@login_required_if_no_ano @login_required_if_no_ano
@download_required @download_required
@ -1317,6 +1109,29 @@ def get_download_link_ext(book_id, book_format, anyname):
return get_download_link(book_id, book_format) return get_download_link(book_id, book_format)
@web.route('/send/<int:book_id>/<book_format>/<int:convert>')
@login_required
@download_required
def send_to_kindle(book_id, book_format, convert):
settings = ub.get_mail_settings()
if settings.get("mail_server", "mail.example.com") == "mail.example.com":
flash(_(u"Please configure the SMTP mail settings first..."), category="error")
elif current_user.kindle_mail:
result = helper.send_mail(book_id, book_format, convert, current_user.kindle_mail, config.config_calibre_dir,
current_user.nickname)
if result is None:
flash(_(u"Book successfully queued for sending to %(kindlemail)s", kindlemail=current_user.kindle_mail),
category="success")
ub.update_download(book_id, int(current_user.id))
else:
flash(_(u"There was an error sending this book: %(res)s", res=result), category="error")
else:
flash(_(u"Please configure your kindle e-mail address first..."), category="error")
return redirect(request.environ["HTTP_REFERER"])
# ################################### Login Logout ##################################################################
@web.route('/register', methods=['GET', 'POST']) @web.route('/register', methods=['GET', 'POST'])
def register(): def register():
if not config.config_public_reg: if not config.config_public_reg:
@ -1502,26 +1317,7 @@ def token_verified():
return response return response
@web.route('/send/<int:book_id>/<book_format>/<int:convert>') # ################################### Users own configuration #########################################################
@login_required
@download_required
def send_to_kindle(book_id, book_format, convert):
settings = ub.get_mail_settings()
if settings.get("mail_server", "mail.example.com") == "mail.example.com":
flash(_(u"Please configure the SMTP mail settings first..."), category="error")
elif current_user.kindle_mail:
result = helper.send_mail(book_id, book_format, convert, current_user.kindle_mail, config.config_calibre_dir,
current_user.nickname)
if result is None:
flash(_(u"Book successfully queued for sending to %(kindlemail)s", kindlemail=current_user.kindle_mail),
category="success")
ub.update_download(book_id, int(current_user.id))
else:
flash(_(u"There was an error sending this book: %(res)s", res=result), category="error")
else:
flash(_(u"Please configure your kindle e-mail address first..."), category="error")
return redirect(request.environ["HTTP_REFERER"])
@web.route("/me", methods=["GET", "POST"]) @web.route("/me", methods=["GET", "POST"])
@login_required @login_required
@ -1589,3 +1385,125 @@ def profile():
name=current_user.nickname), page="me", registered_oauth=oauth_check, name=current_user.nickname), page="me", registered_oauth=oauth_check,
oauth_status=oauth_status) oauth_status=oauth_status)
# ###################################Show single book ##################################################################
@web.route("/read/<int:book_id>/<book_format>")
@login_required_if_no_ano
def read_book(book_id, book_format):
book = db.session.query(db.Books).filter(db.Books.id == book_id).first()
if not book:
flash(_(u"Error opening eBook. File does not exist or file is not accessible:"), category="error")
return redirect(url_for("web.index"))
# check if book was downloaded before
bookmark = None
if current_user.is_authenticated:
bookmark = ub.session.query(ub.Bookmark).filter(ub.and_(ub.Bookmark.user_id == int(current_user.id),
ub.Bookmark.book_id == book_id,
ub.Bookmark.format == book_format.upper())).first()
if book_format.lower() == "epub":
return render_title_template('read.html', bookid=book_id, title=_(u"Read a Book"), bookmark=bookmark)
elif book_format.lower() == "pdf":
return render_title_template('readpdf.html', pdffile=book_id, title=_(u"Read a Book"))
elif book_format.lower() == "txt":
return render_title_template('readtxt.html', txtfile=book_id, title=_(u"Read a Book"))
elif book_format.lower() == "mp3":
entries = db.session.query(db.Books).filter(db.Books.id == book_id).filter(common_filters()).first()
return render_title_template('listenmp3.html', mp3file=book_id, audioformat=book_format.lower(),
title=_(u"Read a Book"), entry=entries, bookmark=bookmark)
elif book_format.lower() == "m4b":
entries = db.session.query(db.Books).filter(db.Books.id == book_id).filter(common_filters()).first()
return render_title_template('listenmp3.html', mp3file=book_id, audioformat=book_format.lower(),
title=_(u"Read a Book"), entry=entries, bookmark=bookmark)
elif book_format.lower() == "m4a":
entries = db.session.query(db.Books).filter(db.Books.id == book_id).filter(common_filters()).first()
return render_title_template('listenmp3.html', mp3file=book_id, audioformat=book_format.lower(),
title=_(u"Read a Book"), entry=entries, bookmark=bookmark)
else:
book_dir = os.path.join(config.get_main_dir, "cps", "static", str(book_id))
if not os.path.exists(book_dir):
os.mkdir(book_dir)
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
# if not os.path.exists(all_name):
# cbr_file = os.path.join(config.config_calibre_dir, book.path, book.data[0].name) + "." + fileext
# copyfile(cbr_file, tmp_file)
return render_title_template('readcbr.html', comicfile=all_name, title=_(u"Read a Book"),
extension=fileext)
'''if feature_support['rar']:
extensionList = ["cbr","cbt","cbz"]
else:
extensionList = ["cbt","cbz"]
for fileext in extensionList:
if book_format.lower() == fileext:
return render_title_template('readcbr.html', comicfile=book_id,
extension=fileext, title=_(u"Read a Book"), book=book)
flash(_(u"Error opening eBook. File does not exist or file is not accessible."), category="error")
return redirect(url_for("web.index"))'''
@web.route("/book/<int:book_id>")
@login_required_if_no_ano
def show_book(book_id):
entries = db.session.query(db.Books).filter(db.Books.id == book_id).filter(common_filters()).first()
if entries:
for index in range(0, len(entries.languages)):
try:
entries.languages[index].language_name = LC.parse(entries.languages[index].lang_code).get_language_name(
get_locale())
except UnknownLocaleError:
entries.languages[index].language_name = _(
isoLanguages.get(part3=entries.languages[index].lang_code).name)
tmpcc = db.session.query(db.Custom_Columns).filter(db.Custom_Columns.datatype.notin_(db.cc_exceptions)).all()
if config.config_columns_to_ignore:
cc = []
for col in tmpcc:
r = re.compile(config.config_columns_to_ignore)
if r.match(col.label):
cc.append(col)
else:
cc = tmpcc
book_in_shelfs = []
shelfs = ub.session.query(ub.BookShelf).filter(ub.BookShelf.book_id == book_id).all()
for entry in shelfs:
book_in_shelfs.append(entry.shelf)
if not current_user.is_anonymous:
if not config.config_read_column:
matching_have_read_book = ub.session.query(ub.ReadBook).\
filter(ub.and_(ub.ReadBook.user_id == int(current_user.id), ub.ReadBook.book_id == book_id)).all()
have_read = len(matching_have_read_book) > 0 and matching_have_read_book[0].is_read
else:
try:
matching_have_read_book = getattr(entries, 'custom_column_'+str(config.config_read_column))
have_read = len(matching_have_read_book) > 0 and matching_have_read_book[0].value
except KeyError:
app.logger.error(
u"Custom Column No.%d is not exisiting in calibre database" % config.config_read_column)
have_read = None
else:
have_read = None
entries.tags = sort(entries.tags, key=lambda tag: tag.name)
entries = order_authors(entries)
kindle_list = helper.check_send_to_kindle(entries)
reader_list = helper.check_read_formats(entries)
audioentries = []
for media_format in entries.data:
if media_format.format.lower() in EXTENSIONS_AUDIO:
audioentries.append(media_format.format.lower())
return render_title_template('detail.html', entry=entries, audioentries=audioentries, cc=cc,
is_xhr=request.is_xhr, title=entries.title, books_shelfs=book_in_shelfs,
have_read=have_read, kindle_list=kindle_list, reader_list=reader_list, page="book")
else:
flash(_(u"Error opening eBook. File does not exist or file is not accessible:"), category="error")
return redirect(url_for("web.index"))