",
+ "
",
"",
"
"
].join("");
diff --git a/cps/tasks/convert.py b/cps/tasks/convert.py
index 028d7dbf..e65d314a 100644
--- a/cps/tasks/convert.py
+++ b/cps/tasks/convert.py
@@ -18,12 +18,12 @@
import os
import re
-
from glob import glob
from shutil import copyfile
from markupsafe import escape
from sqlalchemy.exc import SQLAlchemyError
+from flask_babel import lazy_gettext as N_
from cps.services.worker import CalibreTask
from cps import db
@@ -41,10 +41,10 @@ log = logger.create()
class TaskConvert(CalibreTask):
- def __init__(self, file_path, bookid, taskMessage, settings, kindle_mail, user=None):
- super(TaskConvert, self).__init__(taskMessage)
+ def __init__(self, file_path, book_id, task_message, settings, kindle_mail, user=None):
+ super(TaskConvert, self).__init__(task_message)
self.file_path = file_path
- self.bookid = bookid
+ self.book_id = book_id
self.title = ""
self.settings = settings
self.kindle_mail = kindle_mail
@@ -56,9 +56,9 @@ class TaskConvert(CalibreTask):
self.worker_thread = worker_thread
if config.config_use_google_drive:
worker_db = db.CalibreDB(expire_on_commit=False)
- cur_book = worker_db.get_book(self.bookid)
+ cur_book = worker_db.get_book(self.book_id)
self.title = cur_book.title
- data = worker_db.get_book_format(self.bookid, self.settings['old_book_format'])
+ data = worker_db.get_book_format(self.book_id, self.settings['old_book_format'])
df = gdriveutils.getFileFromEbooksFolder(cur_book.path,
data.name + "." + self.settings['old_book_format'].lower())
if df:
@@ -89,7 +89,7 @@ class TaskConvert(CalibreTask):
# if we're sending to kindle after converting, create a one-off task and run it immediately
# todo: figure out how to incorporate this into the progress
try:
- EmailText = _(u"%(book)s send to Kindle", book=escape(self.title))
+ EmailText = N_(u"%(book)s send to Kindle", book=escape(self.title))
worker_thread.add(self.user, TaskEmail(self.settings['subject'],
self.results["path"],
filename,
@@ -106,7 +106,7 @@ class TaskConvert(CalibreTask):
error_message = None
local_db = db.CalibreDB(expire_on_commit=False)
file_path = self.file_path
- book_id = self.bookid
+ book_id = self.book_id
format_old_ext = u'.' + self.settings['old_book_format'].lower()
format_new_ext = u'.' + self.settings['new_book_format'].lower()
@@ -114,7 +114,7 @@ class TaskConvert(CalibreTask):
# if it does - mark the conversion task as complete and return a success
# this will allow send to kindle workflow to continue to work
if os.path.isfile(file_path + format_new_ext) or\
- local_db.get_book_format(self.bookid, self.settings['new_book_format']):
+ local_db.get_book_format(self.book_id, self.settings['new_book_format']):
log.info("Book id %d already converted to %s", book_id, format_new_ext)
cur_book = local_db.get_book(book_id)
self.title = cur_book.title
@@ -133,7 +133,7 @@ class TaskConvert(CalibreTask):
local_db.session.rollback()
log.error("Database error: %s", e)
local_db.session.close()
- self._handleError(error_message)
+ self._handleError(N_("Database error: %(error)s.", error=e))
return
self._handleSuccess()
local_db.session.close()
@@ -150,8 +150,7 @@ class TaskConvert(CalibreTask):
else:
# check if calibre converter-executable is existing
if not os.path.exists(config.config_converterpath):
- # ToDo Text is not translated
- self._handleError(_(u"Calibre ebook-convert %(tool)s not found", tool=config.config_converterpath))
+ self._handleError(N_(u"Calibre ebook-convert %(tool)s not found", tool=config.config_converterpath))
return
check, error_message = self._convert_calibre(file_path, format_old_ext, format_new_ext)
@@ -184,11 +183,11 @@ class TaskConvert(CalibreTask):
self._handleSuccess()
return os.path.basename(file_path + format_new_ext)
else:
- error_message = _('%(format)s format not found on disk', format=format_new_ext.upper())
+ error_message = N_('%(format)s format not found on disk', format=format_new_ext.upper())
local_db.session.close()
log.info("ebook converter failed with error while converting book")
if not error_message:
- error_message = _('Ebook converter failed with unknown error')
+ error_message = N_('Ebook converter failed with unknown error')
self._handleError(error_message)
return
@@ -198,7 +197,7 @@ class TaskConvert(CalibreTask):
try:
p = process_open(command, quotes)
except OSError as e:
- return 1, _(u"Kepubify-converter failed: %(error)s", error=e)
+ return 1, N_(u"Kepubify-converter failed: %(error)s", error=e)
self.progress = 0.01
while True:
nextline = p.stdout.readlines()
@@ -219,7 +218,7 @@ class TaskConvert(CalibreTask):
copyfile(converted_file[0], (file_path + format_new_ext))
os.unlink(converted_file[0])
else:
- return 1, _(u"Converted file not found or more than one file in folder %(folder)s",
+ return 1, N_(u"Converted file not found or more than one file in folder %(folder)s",
folder=os.path.dirname(file_path))
return check, None
@@ -243,7 +242,7 @@ class TaskConvert(CalibreTask):
p = process_open(command, quotes, newlines=False)
except OSError as e:
- return 1, _(u"Ebook-converter failed: %(error)s", error=e)
+ return 1, N_(u"Ebook-converter failed: %(error)s", error=e)
while p.poll() is None:
nextline = p.stdout.readline()
@@ -266,15 +265,15 @@ class TaskConvert(CalibreTask):
ele = ele.decode('utf-8', errors="ignore").strip('\n')
log.debug(ele)
if not ele.startswith('Traceback') and not ele.startswith(' File'):
- error_message = _("Calibre failed with error: %(error)s", error=ele)
+ error_message = N_("Calibre failed with error: %(error)s", error=ele)
return check, error_message
@property
def name(self):
- return "Convert"
+ return N_("Convert")
def __str__(self):
- return "Convert {} {}".format(self.bookid, self.kindle_mail)
+ return "Convert {} {}".format(self.book_id, self.kindle_mail)
@property
def is_cancellable(self):
diff --git a/cps/tasks/database.py b/cps/tasks/database.py
index 0441d564..afc4db2c 100644
--- a/cps/tasks/database.py
+++ b/cps/tasks/database.py
@@ -16,24 +16,22 @@
# You should have received a copy of the GNU General Public License
# along with this program. If not, see
.
-from __future__ import division, print_function, unicode_literals
+from urllib.request import urlopen
+
+from flask_babel import lazy_gettext as N_
from cps import config, logger
from cps.services.worker import CalibreTask
-try:
- from urllib.request import urlopen
-except ImportError as e:
- from urllib2 import urlopen
-
class TaskReconnectDatabase(CalibreTask):
- def __init__(self, task_message=u'Reconnecting Calibre database'):
+ def __init__(self, task_message=N_('Reconnecting Calibre database')):
super(TaskReconnectDatabase, self).__init__(task_message)
self.log = logger.create()
self.listen_address = config.get_config_ipaddress()
self.listen_port = config.config_port
+
def run(self, worker_thread):
address = self.listen_address if self.listen_address else 'localhost'
port = self.listen_port if self.listen_port else 8083
@@ -42,7 +40,7 @@ class TaskReconnectDatabase(CalibreTask):
urlopen('http://' + address + ':' + str(port) + '/reconnect')
self._handleSuccess()
except Exception as ex:
- self._handleError(u'Unable to reconnect Calibre database: ' + str(ex))
+ self._handleError('Unable to reconnect Calibre database: ' + str(ex))
@property
def name(self):
diff --git a/cps/tasks/mail.py b/cps/tasks/mail.py
index 2a395634..ad38a400 100755
--- a/cps/tasks/mail.py
+++ b/cps/tasks/mail.py
@@ -26,9 +26,8 @@ from io import StringIO
from email.message import EmailMessage
from email.utils import parseaddr
-
-from email import encoders
-from email.utils import formatdate, make_msgid
+from flask_babel import lazy_gettext as N_
+from email.utils import formatdate
from email.generator import Generator
from cps.services.worker import CalibreTask
@@ -111,13 +110,13 @@ class EmailSSL(EmailBase, smtplib.SMTP_SSL):
class TaskEmail(CalibreTask):
- def __init__(self, subject, filepath, attachment, settings, recipient, taskMessage, text, internal=False):
- super(TaskEmail, self).__init__(taskMessage)
+ def __init__(self, subject, filepath, attachment, settings, recipient, task_message, text, internal=False):
+ super(TaskEmail, self).__init__(task_message)
self.subject = subject
self.attachment = attachment
self.settings = settings
self.filepath = filepath
- self.recipent = recipient
+ self.recipient = recipient
self.text = text
self.asyncSMTP = None
self.results = dict()
@@ -139,7 +138,7 @@ class TaskEmail(CalibreTask):
message = EmailMessage()
# message = MIMEMultipart()
message['From'] = self.settings["mail_from"]
- message['To'] = self.recipent
+ message['To'] = self.recipient
message['Subject'] = self.subject
message['Date'] = formatdate(localtime=True)
message['Message-Id'] = "{}@{}".format(uuid.uuid4(), self.get_msgid_domain()) # f"<{uuid.uuid4()}@{get_msgid_domain(from_)}>" # make_msgid('calibre-web')
@@ -212,7 +211,7 @@ class TaskEmail(CalibreTask):
gen = Generator(fp, mangle_from_=False)
gen.flatten(msg)
- self.asyncSMTP.sendmail(self.settings["mail_from"], self.recipent, fp.getvalue())
+ self.asyncSMTP.sendmail(self.settings["mail_from"], self.recipient, fp.getvalue())
self.asyncSMTP.quit()
self._handleSuccess()
log.debug("E-mail send successfully")
@@ -264,7 +263,7 @@ class TaskEmail(CalibreTask):
@property
def name(self):
- return "E-mail"
+ return N_("E-mail")
@property
def is_cancellable(self):
diff --git a/cps/tasks/thumbnail.py b/cps/tasks/thumbnail.py
index 3601ff93..dcfd4226 100644
--- a/cps/tasks/thumbnail.py
+++ b/cps/tasks/thumbnail.py
@@ -16,7 +16,6 @@
# You should have received a copy of the GNU General Public License
# along with this program. If not, see
.
-from __future__ import division, print_function, unicode_literals
import os
from urllib.request import urlopen
@@ -25,7 +24,7 @@ from cps import config, db, fs, gdriveutils, logger, ub
from cps.services.worker import CalibreTask, STAT_CANCELLED, STAT_ENDED
from datetime import datetime
from sqlalchemy import func, text, or_
-
+from flask_babel import lazy_gettext as N_
try:
from wand.image import Image
@@ -50,7 +49,7 @@ def get_best_fit(width, height, image_width, image_height):
resize_height = int(height / 2.0)
aspect_ratio = image_width / image_height
- # If this image's aspect ratio is different than the first image, then resize this image
+ # If this image's aspect ratio is different from the first image, then resize this image
# to fill the width and height of the first image
if aspect_ratio < width / height:
resize_width = int(width / 2.0)
@@ -64,9 +63,10 @@ def get_best_fit(width, height, image_width, image_height):
class TaskGenerateCoverThumbnails(CalibreTask):
- def __init__(self, task_message=''):
+ def __init__(self, book_id=-1, task_message=''):
super(TaskGenerateCoverThumbnails, self).__init__(task_message)
self.log = logger.create()
+ self.book_id = book_id
self.app_db_session = ub.get_new_session_instance()
self.calibre_db = db.CalibreDB(expire_on_commit=False)
self.cache = fs.FileSystem()
@@ -78,37 +78,21 @@ class TaskGenerateCoverThumbnails(CalibreTask):
def run(self, worker_thread):
if self.calibre_db.session and use_IM and self.stat != STAT_CANCELLED and self.stat != STAT_ENDED:
self.message = 'Scanning Books'
- books_with_covers = self.get_books_with_covers()
+ books_with_covers = self.get_books_with_covers(self.book_id)
count = len(books_with_covers)
total_generated = 0
for i, book in enumerate(books_with_covers):
- generated = 0
- book_cover_thumbnails = self.get_book_cover_thumbnails(book.id)
# Generate new thumbnails for missing covers
- resolutions = list(map(lambda t: t.resolution, book_cover_thumbnails))
- missing_resolutions = list(set(self.resolutions).difference(resolutions))
- for resolution in missing_resolutions:
- generated += 1
- self.create_book_cover_thumbnail(book, resolution)
-
- # Replace outdated or missing thumbnails
- for thumbnail in book_cover_thumbnails:
- if book.last_modified > thumbnail.generated_at:
- generated += 1
- self.update_book_cover_thumbnail(book, thumbnail)
-
- elif not self.cache.get_cache_file_exists(thumbnail.filename, constants.CACHE_TYPE_THUMBNAILS):
- generated += 1
- self.update_book_cover_thumbnail(book, thumbnail)
+ generated = self.create_book_cover_thumbnails(book)
# Increment the progress
self.progress = (1.0 / count) * i
if generated > 0:
total_generated += generated
- self.message = u'Generated {0} cover thumbnails'.format(total_generated)
+ self.message = N_(u'Generated %(count)s cover thumbnails', count=total_generated)
# Check if job has been cancelled or ended
if self.stat == STAT_CANCELLED:
@@ -125,10 +109,12 @@ class TaskGenerateCoverThumbnails(CalibreTask):
self._handleSuccess()
self.app_db_session.remove()
- def get_books_with_covers(self):
+ def get_books_with_covers(self, book_id=-1):
+ filter_exp = (db.Books.id == book_id) if book_id != -1 else True
return self.calibre_db.session \
.query(db.Books) \
.filter(db.Books.has_cover == 1) \
+ .filter(filter_exp) \
.all()
def get_book_cover_thumbnails(self, book_id):
@@ -139,7 +125,29 @@ class TaskGenerateCoverThumbnails(CalibreTask):
.filter(or_(ub.Thumbnail.expiration.is_(None), ub.Thumbnail.expiration > datetime.utcnow())) \
.all()
- def create_book_cover_thumbnail(self, book, resolution):
+ def create_book_cover_thumbnails(self, book):
+ generated = 0
+ book_cover_thumbnails = self.get_book_cover_thumbnails(book.id)
+
+ # Generate new thumbnails for missing covers
+ resolutions = list(map(lambda t: t.resolution, book_cover_thumbnails))
+ missing_resolutions = list(set(self.resolutions).difference(resolutions))
+ for resolution in missing_resolutions:
+ generated += 1
+ self.create_book_cover_single_thumbnail(book, resolution)
+
+ # Replace outdated or missing thumbnails
+ for thumbnail in book_cover_thumbnails:
+ if book.last_modified > thumbnail.generated_at:
+ generated += 1
+ self.update_book_cover_thumbnail(book, thumbnail)
+
+ elif not self.cache.get_cache_file_exists(thumbnail.filename, constants.CACHE_TYPE_THUMBNAILS):
+ generated += 1
+ self.update_book_cover_thumbnail(book, thumbnail)
+ return generated
+
+ def create_book_cover_single_thumbnail(self, book, resolution):
thumbnail = ub.Thumbnail()
thumbnail.type = constants.THUMBNAIL_TYPE_COVER
thumbnail.entity_id = book.id
@@ -151,8 +159,8 @@ class TaskGenerateCoverThumbnails(CalibreTask):
self.app_db_session.commit()
self.generate_book_thumbnail(book, thumbnail)
except Exception as ex:
- self.log.info(u'Error creating book thumbnail: ' + str(ex))
- self._handleError(u'Error creating book thumbnail: ' + str(ex))
+ self.log.info('Error creating book thumbnail: ' + str(ex))
+ self._handleError('Error creating book thumbnail: ' + str(ex))
self.app_db_session.rollback()
def update_book_cover_thumbnail(self, book, thumbnail):
@@ -163,8 +171,8 @@ class TaskGenerateCoverThumbnails(CalibreTask):
self.cache.delete_cache_file(thumbnail.filename, constants.CACHE_TYPE_THUMBNAILS)
self.generate_book_thumbnail(book, thumbnail)
except Exception as ex:
- self.log.info(u'Error updating book thumbnail: ' + str(ex))
- self._handleError(u'Error updating book thumbnail: ' + str(ex))
+ self.log.info('Error updating book thumbnail: ' + str(ex))
+ self._handleError('Error updating book thumbnail: ' + str(ex))
self.app_db_session.rollback()
def generate_book_thumbnail(self, book, thumbnail):
@@ -191,7 +199,7 @@ class TaskGenerateCoverThumbnails(CalibreTask):
img.save(filename=filename)
except Exception as ex:
# Bubble exception to calling function
- self.log.info(u'Error generating thumbnail file: ' + str(ex))
+ self.log.info('Error generating thumbnail file: ' + str(ex))
raise ex
finally:
if stream is not None:
@@ -212,10 +220,13 @@ class TaskGenerateCoverThumbnails(CalibreTask):
@property
def name(self):
- return 'GenerateCoverThumbnails'
+ return N_('Cover Thumbnails')
def __str__(self):
- return "GenerateCoverThumbnails"
+ if self.book_id > 0:
+ return "Add Cover Thumbnails for Book {}".format(self.book_id)
+ else:
+ return "Generate Cover Thumbnails"
@property
def is_cancellable(self):
@@ -268,7 +279,7 @@ class TaskGenerateSeriesThumbnails(CalibreTask):
if generated > 0:
total_generated += generated
- self.message = u'Generated {0} series thumbnails'.format(total_generated)
+ self.message = N_('Generated {0} series thumbnails').format(total_generated)
# Check if job has been cancelled or ended
if self.stat == STAT_CANCELLED:
@@ -324,8 +335,8 @@ class TaskGenerateSeriesThumbnails(CalibreTask):
self.app_db_session.commit()
self.generate_series_thumbnail(series_books, thumbnail)
except Exception as ex:
- self.log.info(u'Error creating book thumbnail: ' + str(ex))
- self._handleError(u'Error creating book thumbnail: ' + str(ex))
+ self.log.info('Error creating book thumbnail: ' + str(ex))
+ self._handleError('Error creating book thumbnail: ' + str(ex))
self.app_db_session.rollback()
def update_series_thumbnail(self, series_books, thumbnail):
@@ -336,8 +347,8 @@ class TaskGenerateSeriesThumbnails(CalibreTask):
self.cache.delete_cache_file(thumbnail.filename, constants.CACHE_TYPE_THUMBNAILS)
self.generate_series_thumbnail(series_books, thumbnail)
except Exception as ex:
- self.log.info(u'Error updating book thumbnail: ' + str(ex))
- self._handleError(u'Error updating book thumbnail: ' + str(ex))
+ self.log.info('Error updating book thumbnail: ' + str(ex))
+ self._handleError('Error updating book thumbnail: ' + str(ex))
self.app_db_session.rollback()
def generate_series_thumbnail(self, series_books, thumbnail):
@@ -380,7 +391,7 @@ class TaskGenerateSeriesThumbnails(CalibreTask):
canvas.composite(img, left, top)
except Exception as ex:
- self.log.info(u'Error generating thumbnail file: ' + str(ex))
+ self.log.info('Error generating thumbnail file: ' + str(ex))
raise ex
finally:
if stream is not None:
@@ -422,7 +433,7 @@ class TaskGenerateSeriesThumbnails(CalibreTask):
@property
def name(self):
- return 'GenerateSeriesThumbnails'
+ return N_('Cover Thumbnails')
def __str__(self):
return "GenerateSeriesThumbnails"
@@ -433,22 +444,28 @@ class TaskGenerateSeriesThumbnails(CalibreTask):
class TaskClearCoverThumbnailCache(CalibreTask):
- def __init__(self, book_id, task_message=u'Clearing cover thumbnail cache'):
+ def __init__(self, book_id, task_message=N_('Clearing cover thumbnail cache')):
super(TaskClearCoverThumbnailCache, self).__init__(task_message)
self.log = logger.create()
self.book_id = book_id
+ self.calibre_db = db.CalibreDB(expire_on_commit=False)
self.app_db_session = ub.get_new_session_instance()
self.cache = fs.FileSystem()
def run(self, worker_thread):
if self.app_db_session:
- if self.book_id > 0: # make sure all thumbnails aren't getting deleted due to a bug
+ if self.book_id == 0: # delete superfluous thumbnails
+ thumbnails = (self.calibre_db.session.query(ub.Thumbnail)
+ .join(db.Books, ub.Thumbnail.entity_id == db.Books.id, isouter=True)
+ .filter(db.Books.id == None)
+ .all())
+ elif self.book_id > 0: # make sure single book is selected
thumbnails = self.get_thumbnails_for_book(self.book_id)
+ if self.book_id < 0:
+ self.delete_all_thumbnails()
+ else:
for thumbnail in thumbnails:
self.delete_thumbnail(thumbnail)
- else:
- self.delete_all_thumbnails()
-
self._handleSuccess()
self.app_db_session.remove()
@@ -460,7 +477,6 @@ class TaskClearCoverThumbnailCache(CalibreTask):
.all()
def delete_thumbnail(self, thumbnail):
- # thumbnail.expiration = datetime.utcnow()
try:
self.cache.delete_cache_file(thumbnail.filename, constants.CACHE_TYPE_THUMBNAILS)
self.app_db_session \
@@ -470,8 +486,8 @@ class TaskClearCoverThumbnailCache(CalibreTask):
.delete()
self.app_db_session.commit()
except Exception as ex:
- self.log.info(u'Error deleting book thumbnail: ' + str(ex))
- self._handleError(u'Error deleting book thumbnail: ' + str(ex))
+ self.log.info('Error deleting book thumbnail: ' + str(ex))
+ self._handleError('Error deleting book thumbnail: ' + str(ex))
def delete_all_thumbnails(self):
try:
@@ -479,16 +495,17 @@ class TaskClearCoverThumbnailCache(CalibreTask):
self.app_db_session.commit()
self.cache.delete_cache_dir(constants.CACHE_TYPE_THUMBNAILS)
except Exception as ex:
- self.log.info(u'Error deleting thumbnail directory: ' + str(ex))
- self._handleError(u'Error deleting thumbnail directory: ' + str(ex))
+ self.log.info('Error deleting thumbnail directory: ' + str(ex))
+ self._handleError('Error deleting thumbnail directory: ' + str(ex))
@property
def name(self):
- return 'ThumbnailsClear'
+ return N_('Cover Thumbnails')
+ # needed for logging
def __str__(self):
if self.book_id > 0:
- return "Delete Thumbnail cache for book " + str(self.book_id)
+ return "Replace/Delete Cover Thumbnails for book " + str(self.book_id)
else:
return "Delete Thumbnail cache directory"
diff --git a/cps/tasks/upload.py b/cps/tasks/upload.py
index cf5a64ac..bc8ba1e0 100644
--- a/cps/tasks/upload.py
+++ b/cps/tasks/upload.py
@@ -17,11 +17,14 @@
# along with this program. If not, see
.
from datetime import datetime
+
+from flask_babel import lazy_gettext as N_
+
from cps.services.worker import CalibreTask, STAT_FINISH_SUCCESS
class TaskUpload(CalibreTask):
- def __init__(self, taskMessage, book_title):
- super(TaskUpload, self).__init__(taskMessage)
+ def __init__(self, task_message, book_title):
+ super(TaskUpload, self).__init__(task_message)
self.start_time = self.end_time = datetime.now()
self.stat = STAT_FINISH_SUCCESS
self.progress = 1
@@ -32,7 +35,7 @@ class TaskUpload(CalibreTask):
@property
def name(self):
- return "Upload"
+ return N_("Upload")
def __str__(self):
return "Upload {}".format(self.book_title)
diff --git a/cps/templates/admin.html b/cps/templates/admin.html
index cdaec9bd..8a20b73e 100644
--- a/cps/templates/admin.html
+++ b/cps/templates/admin.html
@@ -161,32 +161,40 @@
{{_('Edit UI Configuration')}}
-
+{% if feature_support['scheduler'] %}