Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Allow to create custom pages and add them to the menu (with fixes from tests) #2948

Open
wants to merge 16 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -35,3 +35,5 @@ gdrive_credentials
client_secrets.json
gmail.json
/.key

pages/
5 changes: 4 additions & 1 deletion cps/admin.py
Original file line number Diff line number Diff line change
Expand Up @@ -1705,7 +1705,7 @@ def _db_configuration_update_helper():
return _db_configuration_result('{}'.format(ex), gdrive_error)

if db_change or not db_valid or not config.db_configured \
or config.config_calibre_dir != to_save["config_calibre_dir"]:
or config.config_calibre_dir != to_save["config_calibre_dir"]:
if not os.path.exists(metadata_db) or not to_save['config_calibre_dir']:
return _db_configuration_result(_('DB Location is not Valid, Please Enter Correct Path'), gdrive_error)
else:
Expand All @@ -1728,6 +1728,9 @@ def _db_configuration_update_helper():
calibre_db.update_config(config)
if not os.access(os.path.join(config.config_calibre_dir, "metadata.db"), os.W_OK):
flash(_("DB is not Writeable"), category="warning")
_config_string(to_save, "config_calibre_split_dir")
config.config_calibre_split = to_save.get('config_calibre_split', 0) == "on"
calibre_db.update_config(config)
config.save()
return _db_configuration_result(None, gdrive_error)

Expand Down
5 changes: 5 additions & 0 deletions cps/config_sql.py
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,8 @@ class _Settings(_Base):

config_calibre_dir = Column(String)
config_calibre_uuid = Column(String)
config_calibre_split = Column(Boolean, default=False)
config_calibre_split_dir = Column(String)
config_port = Column(Integer, default=constants.DEFAULT_PORT)
config_external_port = Column(Integer, default=constants.DEFAULT_PORT)
config_certfile = Column(String)
Expand Down Expand Up @@ -389,6 +391,9 @@ def invalidate(self, error=None):
self.db_configured = False
self.save()

def get_book_path(self):
return self.config_calibre_split_dir if self.config_calibre_split_dir else self.config_calibre_dir

def store_calibre_uuid(self, calibre_db, Library_table):
try:
calibre_uuid = calibre_db.session.query(Library_table).one_or_none()
Expand Down
2 changes: 2 additions & 0 deletions cps/constants.py
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,7 @@
ROLE_EDIT_SHELFS = 1 << 6
ROLE_DELETE_BOOKS = 1 << 7
ROLE_VIEWER = 1 << 8
ROLE_SEND_TO_EREADER = 1 << 9

ALL_ROLES = {
"admin_role": ROLE_ADMIN,
Expand All @@ -78,6 +79,7 @@
"edit_shelf_role": ROLE_EDIT_SHELFS,
"delete_role": ROLE_DELETE_BOOKS,
"viewer_role": ROLE_VIEWER,
"send_to_ereader": ROLE_SEND_TO_EREADER,
}

DETAIL_RANDOM = 1 << 0
Expand Down
22 changes: 11 additions & 11 deletions cps/editbooks.py
Original file line number Diff line number Diff line change
Expand Up @@ -135,7 +135,7 @@ def edit_book(book_id):
edited_books_id = book.id
modify_date = True
title_author_error = helper.update_dir_structure(edited_books_id,
config.config_calibre_dir,
config.get_book_path(),
input_authors[0],
renamed_author=renamed)
if title_author_error:
Expand Down Expand Up @@ -280,7 +280,7 @@ def upload():
meta.extension.lower())
else:
error = helper.update_dir_structure(book_id,
config.config_calibre_dir,
config.get_book_path(),
input_authors[0],
meta.file_path,
title_dir + meta.extension.lower(),
Expand Down Expand Up @@ -330,7 +330,7 @@ def convert_bookformat(book_id):
return redirect(url_for('edit-book.show_edit_book', book_id=book_id))

log.info('converting: book id: %s from: %s to: %s', book_id, book_format_from, book_format_to)
rtn = helper.convert_book_format(book_id, config.config_calibre_dir, book_format_from.upper(),
rtn = helper.convert_book_format(book_id, config.get_book_path(), book_format_from.upper(),
book_format_to.upper(), current_user.name)

if rtn is None:
Expand Down Expand Up @@ -400,7 +400,7 @@ def edit_list_book(param):
elif param == 'title':
sort_param = book.sort
if handle_title_on_edit(book, vals.get('value', "")):
rename_error = helper.update_dir_structure(book.id, config.config_calibre_dir)
rename_error = helper.update_dir_structure(book.id, config.get_book_path())
if not rename_error:
ret = Response(json.dumps({'success': True, 'newValue': book.title}),
mimetype='application/json')
Expand All @@ -418,7 +418,7 @@ def edit_list_book(param):
mimetype='application/json')
elif param == 'authors':
input_authors, __, renamed = handle_author_on_edit(book, vals['value'], vals.get('checkA', None) == "true")
rename_error = helper.update_dir_structure(book.id, config.config_calibre_dir, input_authors[0],
rename_error = helper.update_dir_structure(book.id, config.get_book_path(), input_authors[0],
renamed_author=renamed)
if not rename_error:
ret = Response(json.dumps({
Expand Down Expand Up @@ -522,10 +522,10 @@ def merge_list_book():
for element in from_book.data:
if element.format not in to_file:
# create new data entry with: book_id, book_format, uncompressed_size, name
filepath_new = os.path.normpath(os.path.join(config.config_calibre_dir,
filepath_new = os.path.normpath(os.path.join(config.get_book_path(),
to_book.path,
to_name + "." + element.format.lower()))
filepath_old = os.path.normpath(os.path.join(config.config_calibre_dir,
filepath_old = os.path.normpath(os.path.join(config.get_book_path(),
from_book.path,
element.name + "." + element.format.lower()))
copyfile(filepath_old, filepath_new)
Expand Down Expand Up @@ -565,7 +565,7 @@ def table_xchange_author_title():

if edited_books_id:
# toDo: Handle error
edit_error = helper.update_dir_structure(edited_books_id, config.config_calibre_dir, input_authors[0],
edit_error = helper.update_dir_structure(edited_books_id, config.get_book_path(), input_authors[0],
renamed_author=renamed)
if modify_date:
book.last_modified = datetime.utcnow()
Expand Down Expand Up @@ -762,7 +762,7 @@ def move_coverfile(meta, db_book):
cover_file = meta.cover
else:
cover_file = os.path.join(constants.STATIC_DIR, 'generic_cover.jpg')
new_cover_path = os.path.join(config.config_calibre_dir, db_book.path)
new_cover_path = os.path.join(config.get_book_path(), db_book.path)
try:
os.makedirs(new_cover_path, exist_ok=True)
copyfile(cover_file, os.path.join(new_cover_path, "cover.jpg"))
Expand Down Expand Up @@ -848,7 +848,7 @@ def delete_book_from_table(book_id, book_format, json_response):
book = calibre_db.get_book(book_id)
if book:
try:
result, error = helper.delete_book(book, config.config_calibre_dir, book_format=book_format.upper())
result, error = helper.delete_book(book, config.get_book_path(), book_format=book_format.upper())
if not result:
if json_response:
return json.dumps([{"location": url_for("edit-book.show_edit_book", book_id=book_id),
Expand Down Expand Up @@ -1184,7 +1184,7 @@ def upload_single_file(file_request, book, book_id):
return False

file_name = book.path.rsplit('/', 1)[-1]
filepath = os.path.normpath(os.path.join(config.config_calibre_dir, book.path))
filepath = os.path.normpath(os.path.join(config.get_book_path(), book.path))
saved_filename = os.path.join(filepath, file_name + '.' + file_ext)

# check if file path exists, otherwise create it, copy file to calibre path and delete temp file
Expand Down
108 changes: 108 additions & 0 deletions cps/editpage.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,108 @@
import os
import flask
from flask import Blueprint, Flask, abort, request
from functools import wraps
from pathlib import Path
from flask_login import current_user, login_required
from werkzeug.exceptions import NotFound

from .render_template import render_title_template
from . import logger, config, ub
from .constants import CONFIG_DIR as _CONFIG_DIR

log = logger.create()

editpage = Blueprint('editpage', __name__)

def edit_required(f):
@wraps(f)
def inner(*args, **kwargs):
if current_user.role_edit() or current_user.role_admin():
return f(*args, **kwargs)
abort(403)

return inner

def _get_checkbox(dictionary, field, default):
new_value = dictionary.get(field, default)
convertor = lambda y: y == "on"
new_value = convertor(new_value)

return new_value

@editpage.route("/admin/page/<string:file>", methods=["GET", "POST"])
@login_required
@edit_required
def edit_page(file):
doc = ""
title = ""
name = ""
icon = "file"
is_enabled = True
order = 0
position = "0"

page = ub.session.query(ub.Page).filter(ub.Page.id == file).first()

try:
title = page.title
name = page.name
icon = page.icon
is_enabled = page.is_enabled
order = page.order
position = page.position
except AttributeError:
if file != "new":
abort(404)

if request.method == "POST":
to_save = request.form.to_dict()
title = to_save.get("title", "").strip()
name = to_save.get("name", "").strip()
icon = to_save.get("icon", "").strip()
position = to_save.get("position", "").strip()
order = int(to_save.get("order", 0))
content = to_save.get("content", "").strip()
is_enabled = _get_checkbox(to_save, "is_enabled", True)

if page:
page.title = title
page.name = name
page.icon = icon
page.is_enabled = is_enabled
page.order = order
page.position = position
ub.session_commit("Page edited {}".format(file))
else:
new_page = ub.Page(title=title, name=name, icon=icon, is_enabled=is_enabled, order=order, position=position)
ub.session.add(new_page)
ub.session_commit("Page added {}".format(file))

if (file == "new"):
file = str(new_page.id)
dir_config_path = os.path.join(_CONFIG_DIR, 'pages')
file_name = Path(name + '.md')
file_path = dir_config_path / file_name
os.makedirs(dir_config_path, exist_ok=True)

try:
with open(file_path, 'w') as f:
f.write(content)
f.close()
except Exception as ex:
log.error(ex)

if file != "new":
try:
dir_config_path = Path(_CONFIG_DIR) / 'pages'
file_path = dir_config_path / f"{name}.md"

with open(file_path, 'r') as f:
doc = f.read()
except NotFound:
log.error("'%s' was accessed but file doesn't exists." % file)

else:
doc = "## New file\n\nInformation"

return render_title_template("edit_page.html", title=title, name=name, icon=icon, is_enabled=is_enabled, order=order, position=position, content=doc, file=file)
3 changes: 2 additions & 1 deletion cps/epub.py
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,8 @@ def get_epub_layout(book, book_data):
'n': 'urn:oasis:names:tc:opendocument:xmlns:container',
'pkg': 'http://www.idpf.org/2007/opf',
}
file_path = os.path.normpath(os.path.join(config.config_calibre_dir, book.path, book_data.name + "." + book_data.format.lower()))
file_path = os.path.normpath(os.path.join(config.get_book_path(),
book.path, book_data.name + "." + book_data.format.lower()))

try:
epubZip = zipfile.ZipFile(file_path)
Expand Down
6 changes: 3 additions & 3 deletions cps/helper.py
Original file line number Diff line number Diff line change
Expand Up @@ -781,7 +781,7 @@ def get_book_cover_internal(book, resolution=None):

# Send the book cover from the Calibre directory
else:
cover_file_path = os.path.join(config.config_calibre_dir, book.path)
cover_file_path = os.path.join(config.get_book_path(), book.path)
if os.path.isfile(os.path.join(cover_file_path, "cover.jpg")):
return send_from_directory(cover_file_path, "cover.jpg")
else:
Expand Down Expand Up @@ -934,7 +934,7 @@ def save_cover(img, book_path):
else:
return False, message
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.get_book_path(), book_path), "cover.jpg", img)


def do_download_file(book, book_format, client, data, headers):
Expand All @@ -947,7 +947,7 @@ def do_download_file(book, book_format, client, data, headers):
else:
abort(404)
else:
filename = os.path.join(config.config_calibre_dir, book.path)
filename = os.path.join(config.get_book_path(), book.path)
if not os.path.isfile(os.path.join(filename, data.name + "." + book_format)):
# ToDo: improve error handling
log.error('File not found: %s', os.path.join(filename, data.name + "." + book_format))
Expand Down
2 changes: 1 addition & 1 deletion cps/kobo.py
Original file line number Diff line number Diff line change
Expand Up @@ -205,7 +205,7 @@ def HandleSyncRequest():
for book in books:
formats = [data.format for data in book.Books.data]
if 'KEPUB' not in formats and config.config_kepubifypath and 'EPUB' in formats:
helper.convert_book_format(book.Books.id, config.config_calibre_dir, 'EPUB', 'KEPUB', current_user.name)
helper.convert_book_format(book.Books.id, config.get_book_path(), 'EPUB', 'KEPUB', current_user.name)

kobo_reading_state = get_or_create_reading_state(book.Books.id)
entitlement = {
Expand Down
28 changes: 28 additions & 0 deletions cps/listpages.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
import flask
import json
from flask import Blueprint, jsonify, make_response,abort
from flask_login import current_user, login_required
from functools import wraps
from flask_babel import gettext as _

from .render_template import render_title_template
from . import ub, db

listpages = Blueprint('listpages', __name__)

def edit_required(f):
@wraps(f)
def inner(*args, **kwargs):
if current_user.role_edit() or current_user.role_admin():
return f(*args, **kwargs)
abort(403)

return inner

@listpages.route("/admin/pages/", methods=["GET"])
@login_required
@edit_required
def show_list():
pages = ub.session.query(ub.Page).order_by(ub.Page.position).order_by(ub.Page.order).all()

return render_title_template('list_pages.html', title=_("Pages List"), page="book_table", pages=pages)
6 changes: 6 additions & 0 deletions cps/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,9 @@ def main():
from .gdrive import gdrive
from .editbooks import editbook
from .about import about
from .page import page
from .listpages import listpages
from .editpage import editpage
from .search import search
from .search_metadata import meta
from .shelf import shelf
Expand Down Expand Up @@ -65,6 +68,9 @@ def main():
limiter.limit("3/minute",key_func=request_username)(opds)
app.register_blueprint(jinjia)
app.register_blueprint(about)
app.register_blueprint(page)
app.register_blueprint(listpages)
app.register_blueprint(editpage)
app.register_blueprint(shelf)
app.register_blueprint(admi)
app.register_blueprint(remotelogin)
Expand Down
2 changes: 1 addition & 1 deletion cps/opds.py
Original file line number Diff line number Diff line change
Expand Up @@ -502,7 +502,7 @@ def render_element_index(database_column, linked_table, folder):
entries = entries.join(linked_table).join(db.Books)
entries = entries.filter(calibre_db.common_filters()).group_by(func.upper(func.substr(database_column, 1, 1))).all()
elements = []
if off == 0:
if off == 0 and entries:
elements.append({'id': "00", 'name': _("All")})
shift = 1
for entry in entries[
Expand Down
Loading