mirror of
https://github.com/kovidgoyal/calibre.git
synced 2025-07-09 03:04:10 -04:00
Saving to disk refactored into separate process
This commit is contained in:
parent
17f4e28d82
commit
aa6067f95f
@ -30,6 +30,9 @@ every time you add an HTML file to the library.\
|
|||||||
OptionRecommendation.HIGH)])
|
OptionRecommendation.HIGH)])
|
||||||
of = self.temporary_file('_plugin_html2zip.zip')
|
of = self.temporary_file('_plugin_html2zip.zip')
|
||||||
opf = glob.glob(os.path.join(tdir, '*.opf'))[0]
|
opf = glob.glob(os.path.join(tdir, '*.opf'))[0]
|
||||||
|
ncx = glob.glob(os.path.join(tdir, '*.ncx'))
|
||||||
|
if ncx:
|
||||||
|
os.remove(ncx[0])
|
||||||
epub = initialize_container(of.name, os.path.basename(opf))
|
epub = initialize_container(of.name, os.path.basename(opf))
|
||||||
epub.add_dir(tdir)
|
epub.add_dir(tdir)
|
||||||
epub.close()
|
epub.close()
|
||||||
@ -291,7 +294,7 @@ class PDFMetadataWriter(MetadataWriterPlugin):
|
|||||||
name = 'Set PDF metadata'
|
name = 'Set PDF metadata'
|
||||||
file_types = set(['pdf'])
|
file_types = set(['pdf'])
|
||||||
description = _('Set metadata in %s files') % 'PDF'
|
description = _('Set metadata in %s files') % 'PDF'
|
||||||
author = 'John Schember'
|
author = 'Kovid Goyal'
|
||||||
|
|
||||||
def set_metadata(self, stream, mi, type):
|
def set_metadata(self, stream, mi, type):
|
||||||
from calibre.ebooks.metadata.pdf import set_metadata
|
from calibre.ebooks.metadata.pdf import set_metadata
|
||||||
|
@ -87,7 +87,7 @@ class ReadMetadata(Thread):
|
|||||||
ids.remove(id)
|
ids.remove(id)
|
||||||
except Empty:
|
except Empty:
|
||||||
break
|
break
|
||||||
job.update()
|
job.update(consume_notifications=False)
|
||||||
if not job.is_finished:
|
if not job.is_finished:
|
||||||
running = True
|
running = True
|
||||||
|
|
||||||
@ -119,3 +119,88 @@ def read_metadata(paths, result_queue, chunk=50):
|
|||||||
t = ReadMetadata(tasks, result_queue)
|
t = ReadMetadata(tasks, result_queue)
|
||||||
t.start()
|
t.start()
|
||||||
return t
|
return t
|
||||||
|
|
||||||
|
class SaveWorker(Thread):
|
||||||
|
|
||||||
|
def __init__(self, result_queue, db, ids, path, by_author=False,
|
||||||
|
single_dir=False, single_format=None):
|
||||||
|
Thread.__init__(self)
|
||||||
|
self.daemon = True
|
||||||
|
self.path, self.by_author = path, by_author
|
||||||
|
self.single_dir, self.single_format = single_dir, single_format
|
||||||
|
self.ids = ids
|
||||||
|
self.library_path = db.library_path
|
||||||
|
self.canceled = False
|
||||||
|
self.result_queue = result_queue
|
||||||
|
self.error = None
|
||||||
|
self.start()
|
||||||
|
|
||||||
|
def run(self):
|
||||||
|
server = Server()
|
||||||
|
ids = set(self.ids)
|
||||||
|
tasks = server.split(list(ids))
|
||||||
|
jobs = set([])
|
||||||
|
for i, task in enumerate(tasks):
|
||||||
|
tids = [x[-1] for x in task]
|
||||||
|
job = ParallelJob('save_book',
|
||||||
|
'Save books (%d of %d)'%(i, len(tasks)),
|
||||||
|
lambda x,y:x,
|
||||||
|
args=[tids, self.library_path, self.path, self.single_dir,
|
||||||
|
self.single_format, self.by_author])
|
||||||
|
jobs.add(job)
|
||||||
|
server.add_job(job)
|
||||||
|
|
||||||
|
|
||||||
|
while not self.canceled:
|
||||||
|
time.sleep(0.2)
|
||||||
|
running = False
|
||||||
|
for job in jobs:
|
||||||
|
job.update(consume_notifications=False)
|
||||||
|
while True:
|
||||||
|
try:
|
||||||
|
id, title, ok = job.notifications.get_nowait()[0]
|
||||||
|
if id in ids:
|
||||||
|
self.result_queue.put((id, title, ok))
|
||||||
|
ids.remove(id)
|
||||||
|
except Empty:
|
||||||
|
break
|
||||||
|
if not job.is_finished:
|
||||||
|
running = True
|
||||||
|
|
||||||
|
if not running:
|
||||||
|
break
|
||||||
|
|
||||||
|
server.close()
|
||||||
|
time.sleep(1)
|
||||||
|
|
||||||
|
if self.canceled:
|
||||||
|
return
|
||||||
|
|
||||||
|
for job in jobs:
|
||||||
|
if job.failed:
|
||||||
|
prints(job.details)
|
||||||
|
self.error = job.details
|
||||||
|
if os.path.exists(job.log_path):
|
||||||
|
os.remove(job.log_path)
|
||||||
|
|
||||||
|
|
||||||
|
def save_book(task, library_path, path, single_dir, single_format,
|
||||||
|
by_author, notification=lambda x,y:x):
|
||||||
|
from calibre.library.database2 import LibraryDatabase2
|
||||||
|
db = LibraryDatabase2(library_path)
|
||||||
|
|
||||||
|
def callback(id, title):
|
||||||
|
notification((id, title, True))
|
||||||
|
return True
|
||||||
|
|
||||||
|
if single_format is None:
|
||||||
|
failures = []
|
||||||
|
db.export_to_dir(path, task, index_is_id=True, byauthor=by_author,
|
||||||
|
callback=callback, single_dir=single_dir)
|
||||||
|
else:
|
||||||
|
failures = db.export_single_format_to_dir(path, task, single_format,
|
||||||
|
index_is_id=True, callback=callback)
|
||||||
|
|
||||||
|
for id, title in failures:
|
||||||
|
notification((id, title, False))
|
||||||
|
|
||||||
|
@ -150,7 +150,12 @@ class EbookIterator(object):
|
|||||||
|
|
||||||
if self.opf.path_to_html_toc is not None and \
|
if self.opf.path_to_html_toc is not None and \
|
||||||
self.opf.path_to_html_toc not in self.spine:
|
self.opf.path_to_html_toc not in self.spine:
|
||||||
|
try:
|
||||||
self.spine.append(SpineItem(self.opf.path_to_html_toc))
|
self.spine.append(SpineItem(self.opf.path_to_html_toc))
|
||||||
|
except:
|
||||||
|
import traceback
|
||||||
|
traceback.print_exc()
|
||||||
|
|
||||||
|
|
||||||
sizes = [i.character_count for i in self.spine]
|
sizes = [i.character_count for i in self.spine]
|
||||||
self.pages = [math.ceil(i/float(self.CHARACTERS_PER_PAGE)) for i in sizes]
|
self.pages = [math.ceil(i/float(self.CHARACTERS_PER_PAGE)) for i in sizes]
|
||||||
|
@ -65,7 +65,7 @@ class RTFInput(InputFormatPlugin):
|
|||||||
accelerators):
|
accelerators):
|
||||||
from calibre.ebooks.rtf.xsl import xhtml
|
from calibre.ebooks.rtf.xsl import xhtml
|
||||||
from calibre.ebooks.metadata.meta import get_metadata
|
from calibre.ebooks.metadata.meta import get_metadata
|
||||||
from calibre.ebooks.metadata.opf import OPFCreator
|
from calibre.ebooks.metadata.opf2 import OPFCreator
|
||||||
from calibre.ebooks.rtf2xml.ParseRtf import RtfInvalidCodeException
|
from calibre.ebooks.rtf2xml.ParseRtf import RtfInvalidCodeException
|
||||||
self.log = log
|
self.log = log
|
||||||
self.log('Converting RTF to XML...')
|
self.log('Converting RTF to XML...')
|
||||||
|
@ -41,7 +41,7 @@ class Adder(QObject):
|
|||||||
|
|
||||||
def __init__(self, parent, db, callback):
|
def __init__(self, parent, db, callback):
|
||||||
QObject.__init__(self, parent)
|
QObject.__init__(self, parent)
|
||||||
self.pd = ProgressDialog(_('Add books'), parent=parent)
|
self.pd = ProgressDialog(_('Adding...'), parent=parent)
|
||||||
self.db = db
|
self.db = db
|
||||||
self.pd.setModal(True)
|
self.pd.setModal(True)
|
||||||
self.pd.show()
|
self.pd.show()
|
||||||
@ -55,7 +55,7 @@ class Adder(QObject):
|
|||||||
|
|
||||||
def add_recursive(self, root, single=True):
|
def add_recursive(self, root, single=True):
|
||||||
self.path = root
|
self.path = root
|
||||||
self.pd.set_msg(_('Searching for books in all sub-directories...'))
|
self.pd.set_msg(_('Searching in all sub-directories...'))
|
||||||
self.pd.set_min(0)
|
self.pd.set_min(0)
|
||||||
self.pd.set_max(0)
|
self.pd.set_max(0)
|
||||||
self.pd.value = 0
|
self.pd.value = 0
|
||||||
@ -162,3 +162,64 @@ class Adder(QObject):
|
|||||||
self.add_formats(id, formats)
|
self.add_formats(id, formats)
|
||||||
self.number_of_books_added += 1
|
self.number_of_books_added += 1
|
||||||
|
|
||||||
|
class Saver(QObject):
|
||||||
|
|
||||||
|
def __init__(self, parent, db, callback, rows, path,
|
||||||
|
by_author=False, single_dir=False, single_format=None):
|
||||||
|
QObject.__init__(self, parent)
|
||||||
|
self.pd = ProgressDialog(_('Saving...'), parent=parent)
|
||||||
|
self.db = db
|
||||||
|
self.pd.setModal(True)
|
||||||
|
self.pd.show()
|
||||||
|
self.pd.set_min(0)
|
||||||
|
self._parent = parent
|
||||||
|
self.callback = callback
|
||||||
|
self.callback_called = False
|
||||||
|
self.rq = Queue()
|
||||||
|
self.ids = [x for x in map(db.id, [r.row() for r in rows]) if x is not None]
|
||||||
|
self.pd.set_max(len(self.ids))
|
||||||
|
self.pd.value = 0
|
||||||
|
self.failures = set([])
|
||||||
|
|
||||||
|
from calibre.ebooks.metadata.worker import SaveWorker
|
||||||
|
self.worker = SaveWorker(self.rq, db, self.ids, path, by_author,
|
||||||
|
single_dir, single_format)
|
||||||
|
self.connect(self.pd, SIGNAL('canceled()'), self.canceled)
|
||||||
|
self.timer = QTimer(self)
|
||||||
|
self.connect(self.timer, SIGNAL('timeout()'), self.update)
|
||||||
|
self.timer.start(200)
|
||||||
|
|
||||||
|
|
||||||
|
def canceled(self):
|
||||||
|
if self.timer is not None:
|
||||||
|
self.timer.stop()
|
||||||
|
if self.worker is not None:
|
||||||
|
self.worker.canceled = True
|
||||||
|
self.pd.hide()
|
||||||
|
if not self.callback_called:
|
||||||
|
self.callback(self.worker.path, self.failures, self.worker.error)
|
||||||
|
self.callback_called = True
|
||||||
|
|
||||||
|
|
||||||
|
def update(self):
|
||||||
|
if not self.ids or not self.worker.is_alive():
|
||||||
|
self.timer.stop()
|
||||||
|
self.pd.hide()
|
||||||
|
if not self.callback_called:
|
||||||
|
self.callback(self.worker.path, self.failures, self.worker.error)
|
||||||
|
self.callback_called = True
|
||||||
|
return
|
||||||
|
|
||||||
|
try:
|
||||||
|
id, title, ok = self.rq.get_nowait()
|
||||||
|
except Empty:
|
||||||
|
return
|
||||||
|
self.pd.value += 1
|
||||||
|
self.ids.remove(id)
|
||||||
|
if not isinstance(title, unicode):
|
||||||
|
title = str(title).decode('utf-8', preferred_encoding)
|
||||||
|
self.pd.set_msg(_('Saved')+' '+title)
|
||||||
|
if not ok:
|
||||||
|
self.failures.add(title)
|
||||||
|
|
||||||
|
|
||||||
|
@ -204,19 +204,9 @@ class BooksModel(QAbstractTableModel):
|
|||||||
''' Return list indices of all cells in index.row()'''
|
''' Return list indices of all cells in index.row()'''
|
||||||
return [ self.index(index.row(), c) for c in range(self.columnCount(None))]
|
return [ self.index(index.row(), c) for c in range(self.columnCount(None))]
|
||||||
|
|
||||||
def save_to_disk(self, rows, path, single_dir=False, single_format=None,
|
@property
|
||||||
callback=None):
|
def by_author(self):
|
||||||
rows = [row.row() for row in rows]
|
return self.sorted_on[0] == 'authors'
|
||||||
if single_format is None:
|
|
||||||
return self.db.export_to_dir(path, rows,
|
|
||||||
self.sorted_on[0] == 'authors',
|
|
||||||
single_dir=single_dir,
|
|
||||||
callback=callback)
|
|
||||||
else:
|
|
||||||
return self.db.export_single_format_to_dir(path, rows,
|
|
||||||
single_format,
|
|
||||||
callback=callback)
|
|
||||||
|
|
||||||
|
|
||||||
def delete_books(self, indices):
|
def delete_books(self, indices):
|
||||||
ids = map(self.id, indices)
|
ids = map(self.id, indices)
|
||||||
|
@ -14,7 +14,7 @@ from PyQt4.Qt import Qt, SIGNAL, QObject, QCoreApplication, QUrl, QTimer, \
|
|||||||
QProgressDialog, QMessageBox, QStackedLayout
|
QProgressDialog, QMessageBox, QStackedLayout
|
||||||
from PyQt4.QtSvg import QSvgRenderer
|
from PyQt4.QtSvg import QSvgRenderer
|
||||||
|
|
||||||
from calibre import __version__, __appname__, islinux, sanitize_file_name, \
|
from calibre import __version__, __appname__, sanitize_file_name, \
|
||||||
iswindows, isosx, prints
|
iswindows, isosx, prints
|
||||||
from calibre.ptempfile import PersistentTemporaryFile
|
from calibre.ptempfile import PersistentTemporaryFile
|
||||||
from calibre.utils.config import prefs, dynamic
|
from calibre.utils.config import prefs, dynamic
|
||||||
@ -29,7 +29,6 @@ from calibre.gui2.cover_flow import CoverFlow, DatabaseImages, pictureflowerror
|
|||||||
from calibre.gui2.widgets import ProgressIndicator
|
from calibre.gui2.widgets import ProgressIndicator
|
||||||
from calibre.gui2.dialogs.scheduler import Scheduler
|
from calibre.gui2.dialogs.scheduler import Scheduler
|
||||||
from calibre.gui2.update import CheckForUpdates
|
from calibre.gui2.update import CheckForUpdates
|
||||||
from calibre.gui2.dialogs.progress import ProgressDialog
|
|
||||||
from calibre.gui2.main_window import MainWindow, option_parser as _option_parser
|
from calibre.gui2.main_window import MainWindow, option_parser as _option_parser
|
||||||
from calibre.gui2.main_ui import Ui_MainWindow
|
from calibre.gui2.main_ui import Ui_MainWindow
|
||||||
from calibre.gui2.device import DeviceManager, DeviceMenu, DeviceGUI, Emailer
|
from calibre.gui2.device import DeviceManager, DeviceMenu, DeviceGUI, Emailer
|
||||||
@ -960,54 +959,44 @@ class Main(MainWindow, Ui_MainWindow, DeviceGUI):
|
|||||||
self.save_to_disk(checked, True)
|
self.save_to_disk(checked, True)
|
||||||
|
|
||||||
def save_to_disk(self, checked, single_dir=False, single_format=None):
|
def save_to_disk(self, checked, single_dir=False, single_format=None):
|
||||||
|
|
||||||
rows = self.current_view().selectionModel().selectedRows()
|
rows = self.current_view().selectionModel().selectedRows()
|
||||||
if not rows or len(rows) == 0:
|
if not rows or len(rows) == 0:
|
||||||
return error_dialog(self, _('Cannot save to disk'),
|
return error_dialog(self, _('Cannot save to disk'),
|
||||||
_('No books selected'), show=True)
|
_('No books selected'), show=True)
|
||||||
|
path = choose_dir(self, 'save to disk dialog',
|
||||||
progress = ProgressDialog(_('Saving to disk...'), min=0, max=len(rows),
|
|
||||||
parent=self)
|
|
||||||
|
|
||||||
def callback(count, msg):
|
|
||||||
progress.set_value(count)
|
|
||||||
progress.set_msg(_('Saved')+' '+msg)
|
|
||||||
QApplication.processEvents()
|
|
||||||
QApplication.sendPostedEvents()
|
|
||||||
QApplication.flush()
|
|
||||||
return not progress.canceled
|
|
||||||
|
|
||||||
dir = choose_dir(self, 'save to disk dialog',
|
|
||||||
_('Choose destination directory'))
|
_('Choose destination directory'))
|
||||||
if not dir:
|
if not path:
|
||||||
return
|
return
|
||||||
|
|
||||||
progress.show()
|
if self.current_view() is self.library_view:
|
||||||
QApplication.processEvents()
|
from calibre.gui2.add import Saver
|
||||||
QApplication.sendPostedEvents()
|
self._saver = Saver(self, self.library_view.model().db,
|
||||||
QApplication.flush()
|
Dispatcher(self._books_saved), rows, path,
|
||||||
try:
|
by_author=self.library_view.model().by_author,
|
||||||
if self.current_view() == self.library_view:
|
|
||||||
failures = self.current_view().model().save_to_disk(rows, dir,
|
|
||||||
single_dir=single_dir,
|
single_dir=single_dir,
|
||||||
callback=callback,
|
|
||||||
single_format=single_format)
|
single_format=single_format)
|
||||||
if failures and single_format is not None:
|
|
||||||
msg = _('Could not save the following books to disk, '
|
|
||||||
'because the %s format is not available for them')\
|
|
||||||
%single_format.upper()
|
|
||||||
det_msg = ''
|
|
||||||
for f in failures:
|
|
||||||
det_msg += '%s\n'%f[1]
|
|
||||||
warning_dialog(self, _('Could not save some ebooks'),
|
|
||||||
msg, det_msg).exec_()
|
|
||||||
QDesktopServices.openUrl(QUrl('file:'+dir))
|
|
||||||
else:
|
else:
|
||||||
paths = self.current_view().model().paths(rows)
|
paths = self.current_view().model().paths(rows)
|
||||||
self.device_manager.save_books(
|
self.device_manager.save_books(
|
||||||
Dispatcher(self.books_saved), paths, dir)
|
Dispatcher(self.books_saved), paths, path)
|
||||||
finally:
|
|
||||||
progress.hide()
|
|
||||||
|
def _books_saved(self, path, failures, error):
|
||||||
|
single_format = self._saver.worker.single_format
|
||||||
|
self._saver = None
|
||||||
|
if error:
|
||||||
|
return error_dialog(self, _('Error while saving'),
|
||||||
|
_('There was an error while saving.'),
|
||||||
|
error, show=True)
|
||||||
|
if failures and single_format:
|
||||||
|
single_format = single_format.upper()
|
||||||
|
warning_dialog(self, _('Could not save some books'),
|
||||||
|
_('Could not save some books') + ', ' +
|
||||||
|
(_('as the %s format is not available for them.')%single_format) +
|
||||||
|
_('Click the show details button to see which ones.'),
|
||||||
|
'\n'.join(failures), show=True)
|
||||||
|
QDesktopServices.openUrl(QUrl.fromLocalFile(path))
|
||||||
|
|
||||||
def books_saved(self, job):
|
def books_saved(self, job):
|
||||||
if job.failed:
|
if job.failed:
|
||||||
@ -1681,9 +1670,7 @@ path_to_ebook to the database.
|
|||||||
help=_('Log debugging information to console'))
|
help=_('Log debugging information to console'))
|
||||||
return parser
|
return parser
|
||||||
|
|
||||||
def main(args=sys.argv):
|
def init_qt(args):
|
||||||
pid = os.fork() if False and islinux else -1
|
|
||||||
if pid <= 0:
|
|
||||||
parser = option_parser()
|
parser = option_parser()
|
||||||
opts, args = parser.parse_args(args)
|
opts, args = parser.parse_args(args)
|
||||||
if opts.with_library is not None and os.path.isdir(opts.with_library):
|
if opts.with_library is not None and os.path.isdir(opts.with_library):
|
||||||
@ -1694,28 +1681,14 @@ def main(args=sys.argv):
|
|||||||
app.setWindowIcon(QIcon(':/library'))
|
app.setWindowIcon(QIcon(':/library'))
|
||||||
QCoreApplication.setOrganizationName(ORG_NAME)
|
QCoreApplication.setOrganizationName(ORG_NAME)
|
||||||
QCoreApplication.setApplicationName(APP_UID)
|
QCoreApplication.setApplicationName(APP_UID)
|
||||||
from multiprocessing.connection import Listener, Client
|
return app, opts, args, actions
|
||||||
try:
|
|
||||||
listener = Listener(address=ADDRESS)
|
|
||||||
except socket.error, err:
|
|
||||||
try:
|
|
||||||
conn = Client(ADDRESS)
|
|
||||||
if len(args) > 1:
|
|
||||||
args[1] = os.path.abspath(args[1])
|
|
||||||
conn.send('launched:'+repr(args))
|
|
||||||
conn.close()
|
|
||||||
except:
|
|
||||||
extra = '' if iswindows else \
|
|
||||||
_('If you\'re sure it is not running, delete the file %s')\
|
|
||||||
%ADDRESS
|
|
||||||
QMessageBox.critical(None, _('Cannot Start ')+__appname__,
|
|
||||||
_('<p>%s is already running. %s</p>')%(__appname__, extra))
|
|
||||||
return 1
|
|
||||||
|
|
||||||
|
def run_gui(opts, args, actions, listener, app):
|
||||||
initialize_file_icon_provider()
|
initialize_file_icon_provider()
|
||||||
main = Main(listener, opts, actions)
|
main = Main(listener, opts, actions)
|
||||||
sys.excepthook = main.unhandled_exception
|
sys.excepthook = main.unhandled_exception
|
||||||
if len(args) > 1:
|
if len(args) > 1:
|
||||||
|
args[1] = os.path.abspath(args[1])
|
||||||
main.add_filesystem_book(args[1])
|
main.add_filesystem_book(args[1])
|
||||||
ret = app.exec_()
|
ret = app.exec_()
|
||||||
if getattr(main, 'restart_after_quit', False):
|
if getattr(main, 'restart_after_quit', False):
|
||||||
@ -1729,6 +1702,79 @@ def main(args=sys.argv):
|
|||||||
except:
|
except:
|
||||||
pass
|
pass
|
||||||
return ret
|
return ret
|
||||||
|
|
||||||
|
def cant_start(msg=_('If you are sure it is not running')+', ',
|
||||||
|
what=None):
|
||||||
|
d = QMessageBox(QMessageBox.Critical, _('Cannot Start ')+__appname__,
|
||||||
|
'<p>'+(_('%s is already running.')%__appname__)+'</p>',
|
||||||
|
QMessageBox.Ok)
|
||||||
|
base = '<p>%s</p><p>%s %s'
|
||||||
|
where = __appname__ + ' '+_('may be running in the system tray, in the')+' '
|
||||||
|
if isosx:
|
||||||
|
where += _('upper right region of the screen.')
|
||||||
|
else:
|
||||||
|
where += _('lower right region of the screen.')
|
||||||
|
if what is None:
|
||||||
|
if iswindows:
|
||||||
|
what = _('try rebooting your computer.')
|
||||||
|
else:
|
||||||
|
what = _('try deleting the file')+': '+ADDRESS
|
||||||
|
|
||||||
|
d.setInformativeText(base%(where, msg, what))
|
||||||
|
d.exec_()
|
||||||
|
raise SystemExit(1)
|
||||||
|
|
||||||
|
class RC(Thread):
|
||||||
|
|
||||||
|
def run(self):
|
||||||
|
from multiprocessing.connection import Client
|
||||||
|
self.done = False
|
||||||
|
self.conn = Client(ADDRESS)
|
||||||
|
self.done = True
|
||||||
|
|
||||||
|
def communicate(args):
|
||||||
|
t = RC()
|
||||||
|
t.start()
|
||||||
|
time.sleep(3)
|
||||||
|
if not t.done:
|
||||||
|
f = os.path.expanduser('~/.calibre_calibre GUI.lock')
|
||||||
|
cant_start(what=_('try deleting the file')+': '+f)
|
||||||
|
raise SystemExit(1)
|
||||||
|
|
||||||
|
if len(args) > 1:
|
||||||
|
args[1] = os.path.abspath(args[1])
|
||||||
|
t.conn.send('launched:'+repr(args))
|
||||||
|
t.conn.close()
|
||||||
|
raise SystemExit(0)
|
||||||
|
|
||||||
|
|
||||||
|
def main(args=sys.argv):
|
||||||
|
app, opts, args, actions = init_qt(args)
|
||||||
|
from calibre.utils.lock import singleinstance
|
||||||
|
from multiprocessing.connection import Listener
|
||||||
|
si = singleinstance('calibre GUI')
|
||||||
|
if si:
|
||||||
|
try:
|
||||||
|
listener = Listener(address=ADDRESS)
|
||||||
|
except socket.error:
|
||||||
|
if iswindows:
|
||||||
|
cant_start()
|
||||||
|
os.remove(ADDRESS)
|
||||||
|
try:
|
||||||
|
listener = Listener(address=ADDRESS)
|
||||||
|
except socket.error:
|
||||||
|
cant_start()
|
||||||
|
else:
|
||||||
|
return run_gui(opts, args, actions, listener, app)
|
||||||
|
else:
|
||||||
|
return run_gui(opts, args, actions, listener, app)
|
||||||
|
try:
|
||||||
|
listener = Listener(address=ADDRESS)
|
||||||
|
except socket.error: # Good si is correct
|
||||||
|
communicate(args)
|
||||||
|
else:
|
||||||
|
return run_gui(opts, args, actions, listener, app)
|
||||||
|
|
||||||
return 0
|
return 0
|
||||||
|
|
||||||
|
|
||||||
|
@ -1491,7 +1491,7 @@ books_series_link feeds
|
|||||||
f.close()
|
f.close()
|
||||||
count += 1
|
count += 1
|
||||||
if callable(callback):
|
if callable(callback):
|
||||||
if not callback(count, mi.title):
|
if not callback(int(id), mi.title):
|
||||||
return
|
return
|
||||||
|
|
||||||
def export_single_format_to_dir(self, dir, indices, format,
|
def export_single_format_to_dir(self, dir, indices, format,
|
||||||
@ -1527,7 +1527,7 @@ books_series_link feeds
|
|||||||
pass
|
pass
|
||||||
f.close()
|
f.close()
|
||||||
if callable(callback):
|
if callable(callback):
|
||||||
if not callback(count, title):
|
if not callback(int(id), title):
|
||||||
break
|
break
|
||||||
return failures
|
return failures
|
||||||
|
|
||||||
|
@ -39,7 +39,7 @@ class BaseJob(object):
|
|||||||
self._status_text = _('Waiting...')
|
self._status_text = _('Waiting...')
|
||||||
self._done_called = False
|
self._done_called = False
|
||||||
|
|
||||||
def update(self):
|
def update(self, consume_notifications=True):
|
||||||
if self.duration is not None:
|
if self.duration is not None:
|
||||||
self._run_state = self.FINISHED
|
self._run_state = self.FINISHED
|
||||||
self.percent = 100
|
self.percent = 100
|
||||||
@ -62,7 +62,7 @@ class BaseJob(object):
|
|||||||
self._run_state = self.RUNNING
|
self._run_state = self.RUNNING
|
||||||
self._status_text = _('Working...')
|
self._status_text = _('Working...')
|
||||||
|
|
||||||
while True:
|
while consume_notifications:
|
||||||
try:
|
try:
|
||||||
self.percent, self._message = self.notifications.get_nowait()
|
self.percent, self._message = self.notifications.get_nowait()
|
||||||
self.percent *= 100.
|
self.percent *= 100.
|
||||||
|
@ -35,7 +35,7 @@ class ConnectedWorker(Thread):
|
|||||||
|
|
||||||
def start_job(self, job):
|
def start_job(self, job):
|
||||||
notification = PARALLEL_FUNCS[job.name][-1] is not None
|
notification = PARALLEL_FUNCS[job.name][-1] is not None
|
||||||
self.conn.send((job.name, job.args, job.kwargs))
|
self.conn.send((job.name, job.args, job.kwargs, job.description))
|
||||||
if notification:
|
if notification:
|
||||||
self.start()
|
self.start()
|
||||||
else:
|
else:
|
||||||
@ -204,7 +204,7 @@ class Server(Thread):
|
|||||||
'''
|
'''
|
||||||
Split a list into a list of sub lists, with the number of sub lists being
|
Split a list into a list of sub lists, with the number of sub lists being
|
||||||
no more than the number of workers this server supports. Each sublist contains
|
no more than the number of workers this server supports. Each sublist contains
|
||||||
two tuples of the form (i, x) where x is an element from the original list
|
2-tuples of the form (i, x) where x is an element from the original list
|
||||||
and i is the index of the element x in the original list.
|
and i is the index of the element x in the original list.
|
||||||
'''
|
'''
|
||||||
ans, count, pos = [], 0, 0
|
ans, count, pos = [], 0, 0
|
||||||
|
@ -12,6 +12,7 @@ from threading import Thread
|
|||||||
from Queue import Queue
|
from Queue import Queue
|
||||||
from contextlib import closing
|
from contextlib import closing
|
||||||
from binascii import unhexlify
|
from binascii import unhexlify
|
||||||
|
from calibre import prints
|
||||||
|
|
||||||
PARALLEL_FUNCS = {
|
PARALLEL_FUNCS = {
|
||||||
'lrfviewer' :
|
'lrfviewer' :
|
||||||
@ -28,6 +29,9 @@ PARALLEL_FUNCS = {
|
|||||||
|
|
||||||
'read_metadata' :
|
'read_metadata' :
|
||||||
('calibre.ebooks.metadata.worker', 'read_metadata_', 'notification'),
|
('calibre.ebooks.metadata.worker', 'read_metadata_', 'notification'),
|
||||||
|
|
||||||
|
'save_book' :
|
||||||
|
('calibre.ebooks.metadata.worker', 'save_book', 'notification'),
|
||||||
}
|
}
|
||||||
|
|
||||||
class Progress(Thread):
|
class Progress(Thread):
|
||||||
@ -64,9 +68,10 @@ def main():
|
|||||||
key = unhexlify(os.environ['CALIBRE_WORKER_KEY'])
|
key = unhexlify(os.environ['CALIBRE_WORKER_KEY'])
|
||||||
resultf = unhexlify(os.environ['CALIBRE_WORKER_RESULT'])
|
resultf = unhexlify(os.environ['CALIBRE_WORKER_RESULT'])
|
||||||
with closing(Client(address, authkey=key)) as conn:
|
with closing(Client(address, authkey=key)) as conn:
|
||||||
name, args, kwargs = conn.recv()
|
name, args, kwargs, desc = conn.recv()
|
||||||
#print (name, args, kwargs)
|
if desc:
|
||||||
#sys.stdout.flush()
|
prints(desc)
|
||||||
|
sys.stdout.flush()
|
||||||
func, notification = get_func(name)
|
func, notification = get_func(name)
|
||||||
notifier = Progress(conn)
|
notifier = Progress(conn)
|
||||||
if notification:
|
if notification:
|
||||||
|
@ -6,8 +6,6 @@
|
|||||||
#include <podofo.h>
|
#include <podofo.h>
|
||||||
using namespace PoDoFo;
|
using namespace PoDoFo;
|
||||||
|
|
||||||
#include <string.h>
|
|
||||||
|
|
||||||
class podofo_pdfmem_wrapper : public PdfMemDocument {
|
class podofo_pdfmem_wrapper : public PdfMemDocument {
|
||||||
public:
|
public:
|
||||||
inline void set_info(PdfInfo *i) { this->SetInfo(i); }
|
inline void set_info(PdfInfo *i) { this->SetInfo(i); }
|
||||||
@ -42,6 +40,12 @@ podofo_PDFDoc_new(PyTypeObject *type, PyObject *args, PyObject *kwds)
|
|||||||
return (PyObject *)self;
|
return (PyObject *)self;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static void podofo_set_exception(const PdfError &err) {
|
||||||
|
const char *msg = PdfError::ErrorMessage(err.GetError());
|
||||||
|
if (msg == NULL) msg = err.what();
|
||||||
|
PyErr_SetString(PyExc_ValueError, msg);
|
||||||
|
}
|
||||||
|
|
||||||
static PyObject *
|
static PyObject *
|
||||||
podofo_PDFDoc_load(podofo_PDFDoc *self, PyObject *args, PyObject *kwargs) {
|
podofo_PDFDoc_load(podofo_PDFDoc *self, PyObject *args, PyObject *kwargs) {
|
||||||
char *buffer; Py_ssize_t size;
|
char *buffer; Py_ssize_t size;
|
||||||
@ -50,10 +54,10 @@ podofo_PDFDoc_load(podofo_PDFDoc *self, PyObject *args, PyObject *kwargs) {
|
|||||||
try {
|
try {
|
||||||
self->doc->Load(buffer, size);
|
self->doc->Load(buffer, size);
|
||||||
} catch(const PdfError & err) {
|
} catch(const PdfError & err) {
|
||||||
PyErr_SetString(PyExc_ValueError, PdfError::ErrorMessage(err.GetError()));
|
podofo_set_exception(err);
|
||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
} else return NULL;
|
} else return NULL;
|
||||||
|
|
||||||
|
|
||||||
Py_INCREF(Py_None);
|
Py_INCREF(Py_None);
|
||||||
@ -68,7 +72,7 @@ podofo_PDFDoc_save(podofo_PDFDoc *self, PyObject *args, PyObject *kwargs) {
|
|||||||
try {
|
try {
|
||||||
self->doc->Write(buffer);
|
self->doc->Write(buffer);
|
||||||
} catch(const PdfError & err) {
|
} catch(const PdfError & err) {
|
||||||
PyErr_SetString(PyExc_ValueError, PdfError::ErrorMessage(err.GetError()));
|
podofo_set_exception(err);
|
||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
} else return NULL;
|
} else return NULL;
|
||||||
|
6
todo
6
todo
@ -3,6 +3,8 @@
|
|||||||
|
|
||||||
* Rationalize books table. Add a pubdate column, remove the uri column (and associated support in add_books) and convert series_index to a float.
|
* Rationalize books table. Add a pubdate column, remove the uri column (and associated support in add_books) and convert series_index to a float.
|
||||||
|
|
||||||
* Refactor save to disk into separate process
|
|
||||||
|
|
||||||
* Testing framework
|
* Testing framework
|
||||||
|
|
||||||
|
* Welcome wizard
|
||||||
|
|
||||||
|
* MOBI navigation indexing support
|
||||||
|
Loading…
x
Reference in New Issue
Block a user