from __future__ import division, print_function, unicode_literals import sys import os import re from glob import glob from shutil import copyfile from cps.services.worker import CalibreTask from cps import calibre_db, db from cps import logger, config from cps.subproc_wrapper import process_open from flask_babel import gettext as _ log = logger.create() class TaskConvert(CalibreTask): def __init__(self, file_path, bookid, taskMessage, settings, kindle_mail): super().__init__(taskMessage) self.file_path = file_path self.bookid = bookid self.settings = settings self.kindle_mail = kindle_mail self.results = dict() def run(self, worker_thread): self.worker_thread = worker_thread filename = self._convert_ebook_format() # todo: re-enable this (need to set up gdrive to test with) # if filename: # if config.config_use_google_drive: # gdriveutils.updateGdriveCalibreFromLocal() # if curr_task == TASK_CONVERT: # self.add_email(self.queue[index]['settings']['subject'], self.queue[index]['path'], # filename, self.queue[index]['settings'], self.queue[index]['kindle'], # self.UIqueue[index]['user'], self.queue[index]['title'], # self.queue[index]['settings']['body'], internal=True) self._handleSuccess() pass def _convert_ebook_format(self): error_message = None file_path = self.file_path book_id = self.bookid format_old_ext = u'.' + self.settings['old_book_format'].lower() format_new_ext = u'.' + self.settings['new_book_format'].lower() # check to see if destination format already exists - # 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): log.info("Book id %d already converted to %s", book_id, format_new_ext) cur_book = calibre_db.get_book(book_id) self.results['path'] = file_path self.results['title'] = cur_book.title self._handleSuccess() return file_path + format_new_ext else: log.info("Book id %d - target format of %s does not exist. Moving forward with convert.", book_id, format_new_ext) if config.config_kepubifypath and format_old_ext == '.epub' and format_new_ext == '.kepub': check, error_message = self._convert_kepubify(file_path, format_old_ext, format_new_ext) 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)) return check, error_message = self._convert_calibre(file_path, format_old_ext, format_new_ext) if check == 0: cur_book = calibre_db.get_book(book_id) if os.path.isfile(file_path + format_new_ext): # self.db_queue.join() new_format = db.Data(name=cur_book.data[0].name, book_format=self.settings['new_book_format'].upper(), book=book_id, uncompressed_size=os.path.getsize(file_path + format_new_ext)) # todo: this may not be needed anymore, might be able to access the DB directly now. See #1565 task = {'task':'add_format','id': book_id, 'format': new_format} self.worker_thread.db_queue.put(task) # To Do how to handle error? '''cur_book.data.append(new_format) try: # db.session.merge(cur_book) calibre_db.session.commit() except OperationalError as e: calibre_db.session.rollback() log.error("Database error: %s", e) self._handleError(_(u"Database error: %(error)s.", error=e)) return''' self.results['path'] = cur_book.path self.results['title'] = cur_book.title if config.config_use_google_drive: os.remove(file_path + format_old_ext) self._handleSuccess() return file_path + format_new_ext else: error_message = format_new_ext.upper() + ' format not found on disk' log.info("ebook converter failed with error while converting book") if not error_message: error_message = 'Ebook converter failed with unknown error' self._handleError(error_message) return def _convert_kepubify(self, file_path, format_old_ext, format_new_ext): quotes = [1, 3] command = [config.config_kepubifypath, (file_path + format_old_ext), '-o', os.path.dirname(file_path)] try: p = process_open(command, quotes) except OSError as e: return 1, _(u"Kepubify-converter failed: %(error)s", error=e) self.progress = 0.01 while True: nextline = p.stdout.readlines() nextline = [x.strip('\n') for x in nextline if x != '\n'] if sys.version_info < (3, 0): nextline = [x.decode('utf-8') for x in nextline] for line in nextline: log.debug(line) if p.poll() is not None: break # ToD Handle # process returncode check = p.returncode # move file if check == 0: converted_file = glob(os.path.join(os.path.dirname(file_path), "*.kepub.epub")) if len(converted_file) == 1: 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", folder=os.path.dirname(file_path)) return check, None def _convert_calibre(self, file_path, format_old_ext, format_new_ext): try: # Linux py2.7 encode as list without quotes no empty element for parameters # linux py3.x no encode and as list without quotes no empty element for parameters # windows py2.7 encode as string with quotes empty element for parameters is okay # windows py 3.x no encode and as string with quotes empty element for parameters is okay # separate handling for windows and linux quotes = [1, 2] command = [config.config_converterpath, (file_path + format_old_ext), (file_path + format_new_ext)] quotes_index = 3 if config.config_calibre: parameters = config.config_calibre.split(" ") for param in parameters: command.append(param) quotes.append(quotes_index) quotes_index += 1 p = process_open(command, quotes) except OSError as e: return 1, _(u"Ebook-converter failed: %(error)s", error=e) while p.poll() is None: nextline = p.stdout.readline() if os.name == 'nt' and sys.version_info < (3, 0): nextline = nextline.decode('windows-1252') elif os.name == 'posix' and sys.version_info < (3, 0): nextline = nextline.decode('utf-8') log.debug(nextline.strip('\r\n')) # parse progress string from calibre-converter progress = re.search(r"(\d+)%\s.*", nextline) if progress: self.progress = int(progress.group(1)) / 100 # process returncode check = p.returncode calibre_traceback = p.stderr.readlines() error_message = "" for ele in calibre_traceback: if sys.version_info < (3, 0): ele = ele.decode('utf-8') log.debug(ele.strip('\n')) if not ele.startswith('Traceback') and not ele.startswith(' File'): error_message = "Calibre failed with error: %s" % ele.strip('\n') return check, error_message @property def name(self): return "Convert"