Skip to content

Commit

Permalink
Merge remote-tracking branch 'cover_images/thumbnails' into cover_thu…
Browse files Browse the repository at this point in the history
…mbnail

# Conflicts:
#	cps/admin.py
#	cps/config_sql.py
#	cps/helper.py
#	cps/tasks/upload.py
#	cps/updater.py
#	cps/web.py
  • Loading branch information
OzzieIsaacs committed Feb 8, 2022
2 parents e22b3da + 62ff6f7 commit 4a0dde0
Show file tree
Hide file tree
Showing 39 changed files with 1,673 additions and 5,077 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ vendor/
# calibre-web
*.db
*.log
cps/cache

.idea/
*.bak
Expand Down
6 changes: 6 additions & 0 deletions cps.py
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@
from cps.remotelogin import remotelogin
from cps.search_metadata import meta
from cps.error_handler import init_errorhandler
from cps.schedule import register_scheduled_tasks, register_startup_tasks

try:
from cps.kobo import kobo, get_kobo_activated
Expand Down Expand Up @@ -79,6 +80,11 @@ def main():
app.register_blueprint(kobo_auth)
if oauth_available:
app.register_blueprint(oauth)

# Register scheduled tasks
register_scheduled_tasks()
register_startup_tasks()

success = web_server.start()
sys.exit(0 if success else 1)

Expand Down
1 change: 1 addition & 0 deletions cps/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -159,6 +159,7 @@ def create_app():
config.store_calibre_uuid(calibre_db, db.Library_Id)
return app


@babel.localeselector
def get_locale():
# if a user is logged in, use the locale from the user settings
Expand Down
53 changes: 52 additions & 1 deletion cps/admin.py
Original file line number Diff line number Diff line change
Expand Up @@ -40,11 +40,13 @@
from sqlalchemy.sql.expression import func, or_, text

from . import constants, logger, helper, services, cli
from . import db, calibre_db, ub, web_server, get_locale, config, updater_thread, babel, gdriveutils, kobo_sync_status
from . import db, calibre_db, ub, web_server, get_locale, config, updater_thread, babel, gdriveutils, \
kobo_sync_status, schedule
from .helper import check_valid_domain, send_test_mail, reset_password, generate_password_hash, check_email, \
valid_email, check_username
from .gdriveutils import is_gdrive_ready, gdrive_support
from .render_template import render_title_template, get_sidebar_config
from .services.worker import WorkerThread
from . import debug_info, _BABEL_TRANSLATIONS

from functools import wraps
Expand Down Expand Up @@ -1635,6 +1637,45 @@ def update_mailsettings():
return edit_mailsettings()


@admi.route("/admin/scheduledtasks")
@login_required
@admin_required
def edit_scheduledtasks():
content = config.get_scheduled_task_settings()
return render_title_template("schedule_edit.html", config=content, title=_(u"Edit Scheduled Tasks Settings"))


@admi.route("/admin/scheduledtasks", methods=["POST"])
@login_required
@admin_required
def update_scheduledtasks():
to_save = request.form.to_dict()
_config_int(to_save, "schedule_start_time")
_config_int(to_save, "schedule_end_time")
_config_checkbox(to_save, "schedule_generate_book_covers")
_config_checkbox(to_save, "schedule_generate_series_covers")

try:
config.save()
flash(_(u"Scheduled tasks settings updated"), category="success")

# Cancel any running tasks
schedule.end_scheduled_tasks()

# Re-register tasks with new settings
schedule.register_scheduled_tasks()
except IntegrityError as ex:
ub.session.rollback()
log.error("An unknown error occurred while saving scheduled tasks settings")
flash(_(u"An unknown error occurred. Please try again later."), category="error")
except OperationalError:
ub.session.rollback()
log.error("Settings DB is not Writeable")
flash(_("Settings DB is not Writeable"), category="error")

return edit_scheduledtasks()


@admi.route("/admin/user/<int:user_id>", methods=["GET", "POST"])
@login_required
@admin_required
Expand Down Expand Up @@ -1911,3 +1952,13 @@ def extract_dynamic_field_from_filter(user, filtr):
def extract_user_identifier(user, filtr):
dynamic_field = extract_dynamic_field_from_filter(user, filtr)
return extract_user_data_from_field(user, dynamic_field)


@admi.route("/ajax/canceltask", methods=['POST'])
@login_required
@admin_required
def cancel_task():
task_id = request.get_json().get('task_id', None)
worker = WorkerThread.get_instance()
worker.end_task(task_id)
return ""
14 changes: 11 additions & 3 deletions cps/config_sql.py
Original file line number Diff line number Diff line change
Expand Up @@ -134,13 +134,18 @@ class _Settings(_Base):
config_calibre = Column(String)
config_rarfile_location = Column(String, default=None)
config_upload_formats = Column(String, default=','.join(constants.EXTENSIONS_UPLOAD))
config_unicode_filename =Column(Boolean, default=False)
config_unicode_filename = Column(Boolean, default=False)

config_updatechannel = Column(Integer, default=constants.UPDATE_STABLE)

config_reverse_proxy_login_header_name = Column(String)
config_allow_reverse_proxy_header_login = Column(Boolean, default=False)

schedule_start_time = Column(Integer, default=4)
schedule_end_time = Column(Integer, default=6)
schedule_generate_book_covers = Column(Boolean, default=False)
schedule_generate_series_covers = Column(Boolean, default=False)

def __repr__(self):
return self.__class__.__name__

Expand Down Expand Up @@ -171,7 +176,6 @@ def __init__(self, session):
if change:
self.save()


def _read_from_storage(self):
if self._settings is None:
log.debug("_ConfigSQL._read_from_storage")
Expand Down Expand Up @@ -255,6 +259,8 @@ def get_mail_server_configured(self):
return bool((self.mail_server != constants.DEFAULT_MAIL_SERVER and self.mail_server_type == 0)
or (self.mail_gmail_token != {} and self.mail_server_type == 1))

def get_scheduled_task_settings(self):
return {k:v for k, v in self.__dict__.items() if k.startswith('schedule_')}

def set_from_dictionary(self, dictionary, field, convertor=None, default=None, encode=None):
"""Possibly updates a field of this object.
Expand Down Expand Up @@ -290,7 +296,6 @@ def toDict(self):
storage[k] = v
return storage


def load(self):
'''Load all configuration values from the underlying storage.'''
s = self._read_from_storage() # type: _Settings
Expand Down Expand Up @@ -411,6 +416,7 @@ def autodetect_calibre_binary():
return element
return ""


def autodetect_unrar_binary():
if sys.platform == "win32":
calibre_path = ["C:\\program files\\WinRar\\unRAR.exe",
Expand All @@ -422,6 +428,7 @@ def autodetect_unrar_binary():
return element
return ""


def autodetect_kepubify_binary():
if sys.platform == "win32":
calibre_path = ["C:\\program files\\kepubify\\kepubify-windows-64Bit.exe",
Expand All @@ -433,6 +440,7 @@ def autodetect_kepubify_binary():
return element
return ""


def _migrate_database(session):
# make sure the table is created, if it does not exist
_Base.metadata.create_all(session.bind)
Expand Down
20 changes: 20 additions & 0 deletions cps/constants.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,9 @@

sqlalchemy_version2 = ([int(x) for x in sql_version.split('.')] >= [2, 0, 0])

# APP_MODE - production, development, or test
APP_MODE = os.environ.get('APP_MODE', 'production')

# if installed via pip this variable is set to true (empty file with name .HOMEDIR present)
HOME_CONFIG = os.path.isfile(os.path.join(os.path.dirname(os.path.abspath(__file__)), '.HOMEDIR'))

Expand All @@ -35,6 +38,10 @@
TEMPLATES_DIR = os.path.join(BASE_DIR, 'cps', 'templates')
TRANSLATIONS_DIR = os.path.join(BASE_DIR, 'cps', 'translations')

# Cache dir - use CACHE_DIR environment variable, otherwise use the default directory: cps/cache
DEFAULT_CACHE_DIR = os.path.join(BASE_DIR, 'cps', 'cache')
CACHE_DIR = os.environ.get('CACHE_DIR', DEFAULT_CACHE_DIR)

if HOME_CONFIG:
home_dir = os.path.join(os.path.expanduser("~"), ".calibre-web")
if not os.path.exists(home_dir):
Expand Down Expand Up @@ -162,6 +169,19 @@ def selected_roles(dictionary):
# NIGHTLY_VERSION[0] = 'bb7d2c6273ae4560e83950d36d64533343623a57'
# NIGHTLY_VERSION[1] = '2018-09-09T10:13:08+02:00'

# CACHE
CACHE_TYPE_THUMBNAILS = 'thumbnails'

# Thumbnail Types
THUMBNAIL_TYPE_COVER = 1
THUMBNAIL_TYPE_SERIES = 2
THUMBNAIL_TYPE_AUTHOR = 3

# Thumbnails Sizes
COVER_THUMBNAIL_ORIGINAL = 0
COVER_THUMBNAIL_SMALL = 1
COVER_THUMBNAIL_MEDIUM = 2
COVER_THUMBNAIL_LARGE = 3

# clean-up the module namespace
del sys, os, namedtuple
9 changes: 5 additions & 4 deletions cps/db.py
Original file line number Diff line number Diff line change
Expand Up @@ -450,11 +450,11 @@ def __init__(self, expire_on_commit=True):
"""
self.session = None
if self._init:
self.initSession(expire_on_commit)
self.init_session(expire_on_commit)

self.instances.add(self)

def initSession(self, expire_on_commit=True):
def init_session(self, expire_on_commit=True):
self.session = self.session_factory()
self.session.expire_on_commit = expire_on_commit
self.update_title_sort(self.config)
Expand Down Expand Up @@ -603,7 +603,7 @@ def setup_db(cls, config_calibre_dir, app_db_path):
autoflush=True,
bind=cls.engine))
for inst in cls.instances:
inst.initSession()
inst.init_session()

cls._init = True
return True
Expand Down Expand Up @@ -720,7 +720,8 @@ def fill_indexpage_with_archived_books(self, page, database, pagesize, db_filter
randm = self.session.query(Books) \
.filter(self.common_filters(allow_show_archived)) \
.order_by(func.random()) \
.limit(self.config.config_random_books).all()
.limit(self.config.config_random_books) \
.all()
else:
randm = false()
if join_archive_read:
Expand Down
2 changes: 2 additions & 0 deletions cps/editbooks.py
Original file line number Diff line number Diff line change
Expand Up @@ -686,6 +686,7 @@ def upload_cover(request, book):
abort(403)
ret, message = helper.save_cover(requested_file, book.path)
if ret is True:
helper.clear_cover_thumbnail_cache(book.id)
return True
else:
flash(message, category="error")
Expand Down Expand Up @@ -809,6 +810,7 @@ def edit_book(book_id):
if result is True:
book.has_cover = 1
modif_date = True
helper.clear_cover_thumbnail_cache(book.id)
else:
flash(error, category="error")

Expand Down
96 changes: 96 additions & 0 deletions cps/fs.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
# -*- coding: utf-8 -*-

# This file is part of the Calibre-Web (https://github.com/janeczku/calibre-web)
# Copyright (C) 2020 mmonkey
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.

from __future__ import division, print_function, unicode_literals
from . import logger
from .constants import CACHE_DIR
from os import makedirs, remove
from os.path import isdir, isfile, join
from shutil import rmtree


class FileSystem:
_instance = None
_cache_dir = CACHE_DIR

def __new__(cls):
if cls._instance is None:
cls._instance = super(FileSystem, cls).__new__(cls)
cls.log = logger.create()
return cls._instance

def get_cache_dir(self, cache_type=None):
if not isdir(self._cache_dir):
try:
makedirs(self._cache_dir)
except OSError:
self.log.info(f'Failed to create path {self._cache_dir} (Permission denied).')
return False

path = join(self._cache_dir, cache_type)
if cache_type and not isdir(path):
try:
makedirs(path)
except OSError:
self.log.info(f'Failed to create path {path} (Permission denied).')
return False

return path if cache_type else self._cache_dir

def get_cache_file_dir(self, filename, cache_type=None):
path = join(self.get_cache_dir(cache_type), filename[:2])
if not isdir(path):
try:
makedirs(path)
except OSError:
self.log.info(f'Failed to create path {path} (Permission denied).')
return False

return path

def get_cache_file_path(self, filename, cache_type=None):
return join(self.get_cache_file_dir(filename, cache_type), filename) if filename else None

def get_cache_file_exists(self, filename, cache_type=None):
path = self.get_cache_file_path(filename, cache_type)
return isfile(path)

def delete_cache_dir(self, cache_type=None):
if not cache_type and isdir(self._cache_dir):
try:
rmtree(self._cache_dir)
except OSError:
self.log.info(f'Failed to delete path {self._cache_dir} (Permission denied).')
return False

path = join(self._cache_dir, cache_type)
if cache_type and isdir(path):
try:
rmtree(path)
except OSError:
self.log.info(f'Failed to delete path {path} (Permission denied).')
return False

def delete_cache_file(self, filename, cache_type=None):
path = self.get_cache_file_path(filename, cache_type)
if isfile(path):
try:
remove(path)
except OSError:
self.log.info(f'Failed to delete path {path} (Permission denied).')
return False
Loading

0 comments on commit 4a0dde0

Please sign in to comment.