Speed up adding/saving of small numbers of books by maintaining a spare job server

This commit is contained in:
Kovid Goyal 2009-06-02 10:20:02 -07:00
parent be9c87a062
commit e6728649be
3 changed files with 35 additions and 13 deletions

View File

@ -53,8 +53,9 @@ class Progress(object):
class ReadMetadata(Thread): class ReadMetadata(Thread):
def __init__(self, tasks, result_queue): def __init__(self, tasks, result_queue, spare_server=None):
self.tasks, self.result_queue = tasks, result_queue self.tasks, self.result_queue = tasks, result_queue
self.spare_server = spare_server
self.canceled = False self.canceled = False
Thread.__init__(self) Thread.__init__(self)
self.daemon = True self.daemon = True
@ -67,7 +68,7 @@ class ReadMetadata(Thread):
for b in t: for b in t:
ids.add(b[0]) ids.add(b[0])
progress = Progress(self.result_queue, self.tdir) progress = Progress(self.result_queue, self.tdir)
server = Server() server = Server() if self.spare_server is None else self.spare_server
for i, task in enumerate(self.tasks): for i, task in enumerate(self.tasks):
job = ParallelJob('read_metadata', job = ParallelJob('read_metadata',
'Read metadata (%d of %d)'%(i, len(self.tasks)), 'Read metadata (%d of %d)'%(i, len(self.tasks)),
@ -110,20 +111,20 @@ class ReadMetadata(Thread):
os.remove(job.log_path) os.remove(job.log_path)
def read_metadata(paths, result_queue, chunk=50): def read_metadata(paths, result_queue, chunk=50, spare_server=None):
tasks = [] tasks = []
pos = 0 pos = 0
while pos < len(paths): while pos < len(paths):
tasks.append(paths[pos:pos+chunk]) tasks.append(paths[pos:pos+chunk])
pos += chunk pos += chunk
t = ReadMetadata(tasks, result_queue) t = ReadMetadata(tasks, result_queue, spare_server=spare_server)
t.start() t.start()
return t return t
class SaveWorker(Thread): class SaveWorker(Thread):
def __init__(self, result_queue, db, ids, path, by_author=False, def __init__(self, result_queue, db, ids, path, by_author=False,
single_dir=False, single_format=None): single_dir=False, single_format=None, spare_server=None):
Thread.__init__(self) Thread.__init__(self)
self.daemon = True self.daemon = True
self.path, self.by_author = path, by_author self.path, self.by_author = path, by_author
@ -133,10 +134,11 @@ class SaveWorker(Thread):
self.canceled = False self.canceled = False
self.result_queue = result_queue self.result_queue = result_queue
self.error = None self.error = None
self.spare_server = spare_server
self.start() self.start()
def run(self): def run(self):
server = Server() server = Server() if self.spare_server is None else self.spare_server
ids = set(self.ids) ids = set(self.ids)
tasks = server.split(list(ids)) tasks = server.split(list(ids))
jobs = set([]) jobs = set([])

View File

@ -39,9 +39,10 @@ class RecursiveFind(QThread):
class Adder(QObject): class Adder(QObject):
def __init__(self, parent, db, callback): def __init__(self, parent, db, callback, spare_server=None):
QObject.__init__(self, parent) QObject.__init__(self, parent)
self.pd = ProgressDialog(_('Adding...'), parent=parent) self.pd = ProgressDialog(_('Adding...'), parent=parent)
self.spare_server = spare_server
self.db = db self.db = db
self.pd.setModal(True) self.pd.setModal(True)
self.pd.show() self.pd.show()
@ -79,7 +80,8 @@ class Adder(QObject):
tasks.append((i, b)) tasks.append((i, b))
self.ids[i] = b self.ids[i] = b
self.nmap[i] = os.path.basename(b[0]) self.nmap[i] = os.path.basename(b[0])
self.worker = read_metadata(tasks, self.rq) self.worker = read_metadata(tasks, self.rq,
spare_server=self.spare_server)
self.pd.set_min(0) self.pd.set_min(0)
self.pd.set_max(len(self.ids)) self.pd.set_max(len(self.ids))
self.pd.value = 0 self.pd.value = 0
@ -165,9 +167,11 @@ class Adder(QObject):
class Saver(QObject): class Saver(QObject):
def __init__(self, parent, db, callback, rows, path, def __init__(self, parent, db, callback, rows, path,
by_author=False, single_dir=False, single_format=None): by_author=False, single_dir=False, single_format=None,
spare_server=None):
QObject.__init__(self, parent) QObject.__init__(self, parent)
self.pd = ProgressDialog(_('Saving...'), parent=parent) self.pd = ProgressDialog(_('Saving...'), parent=parent)
self.spare_server = spare_server
self.db = db self.db = db
self.pd.setModal(True) self.pd.setModal(True)
self.pd.show() self.pd.show()
@ -183,7 +187,7 @@ class Saver(QObject):
from calibre.ebooks.metadata.worker import SaveWorker from calibre.ebooks.metadata.worker import SaveWorker
self.worker = SaveWorker(self.rq, db, self.ids, path, by_author, self.worker = SaveWorker(self.rq, db, self.ids, path, by_author,
single_dir, single_format) single_dir, single_format, spare_server=self.spare_server)
self.connect(self.pd, SIGNAL('canceled()'), self.canceled) self.connect(self.pd, SIGNAL('canceled()'), self.canceled)
self.timer = QTimer(self) self.timer = QTimer(self)
self.connect(self.timer, SIGNAL('timeout()'), self.update) self.connect(self.timer, SIGNAL('timeout()'), self.update)

View File

@ -18,6 +18,7 @@ from calibre import __version__, __appname__, sanitize_file_name, \
iswindows, isosx, prints, patheq iswindows, isosx, prints, patheq
from calibre.ptempfile import PersistentTemporaryFile from calibre.ptempfile import PersistentTemporaryFile
from calibre.utils.config import prefs, dynamic from calibre.utils.config import prefs, dynamic
from calibre.utils.ipc.server import Server
from calibre.gui2 import APP_UID, warning_dialog, choose_files, error_dialog, \ from calibre.gui2 import APP_UID, warning_dialog, choose_files, error_dialog, \
initialize_file_icon_provider, question_dialog,\ initialize_file_icon_provider, question_dialog,\
pixmap_to_data, choose_dir, ORG_NAME, \ pixmap_to_data, choose_dir, ORG_NAME, \
@ -104,6 +105,7 @@ class Main(MainWindow, Ui_MainWindow, DeviceGUI):
def __init__(self, listener, opts, actions, parent=None): def __init__(self, listener, opts, actions, parent=None):
self.preferences_action, self.quit_action = actions self.preferences_action, self.quit_action = actions
self.spare_servers = []
MainWindow.__init__(self, opts, parent) MainWindow.__init__(self, opts, parent)
# Initialize fontconfig in a separate thread as this can be a lengthy # Initialize fontconfig in a separate thread as this can be a lengthy
# process if run for the first time on this machine # process if run for the first time on this machine
@ -460,6 +462,8 @@ class Main(MainWindow, Ui_MainWindow, DeviceGUI):
self.setMaximumHeight(max_available_height()) self.setMaximumHeight(max_available_height())
####################### Start spare job server ########################
QTimer.singleShot(1000, self.add_spare_server)
####################### Setup device detection ######################## ####################### Setup device detection ########################
self.device_manager = DeviceManager(Dispatcher(self.device_detected), self.device_manager = DeviceManager(Dispatcher(self.device_detected),
@ -490,7 +494,16 @@ class Main(MainWindow, Ui_MainWindow, DeviceGUI):
self.connect(self.action_sync, SIGNAL('triggered(bool)'), self.connect(self.action_sync, SIGNAL('triggered(bool)'),
self._sync_menu.trigger_default) self._sync_menu.trigger_default)
def add_spare_server(self, *args):
self.spare_servers.append(Server())
@property
def spare_server(self):
try:
QTimer.singleShot(1000, self.add_spare_server)
return self.spare_servers.pop()
except:
pass
def no_op(self, *args): def no_op(self, *args):
pass pass
@ -730,7 +743,7 @@ class Main(MainWindow, Ui_MainWindow, DeviceGUI):
from calibre.gui2.add import Adder from calibre.gui2.add import Adder
self._adder = Adder(self, self._adder = Adder(self,
self.library_view.model().db, self.library_view.model().db,
Dispatcher(self._files_added)) Dispatcher(self._files_added), spare_server=self.spare_server)
self._adder.add_recursive(root, single) self._adder.add_recursive(root, single)
def add_recursive_single(self, checked): def add_recursive_single(self, checked):
@ -793,7 +806,7 @@ class Main(MainWindow, Ui_MainWindow, DeviceGUI):
self.__adder_func = partial(self._files_added, on_card=on_card) self.__adder_func = partial(self._files_added, on_card=on_card)
self._adder = Adder(self, self._adder = Adder(self,
None if to_device else self.library_view.model().db, None if to_device else self.library_view.model().db,
Dispatcher(self.__adder_func)) Dispatcher(self.__adder_func), spare_server=self.spare_server)
self._adder.add(paths) self._adder.add(paths)
def _files_added(self, paths=[], names=[], infos=[], on_card=None): def _files_added(self, paths=[], names=[], infos=[], on_card=None):
@ -986,7 +999,8 @@ class Main(MainWindow, Ui_MainWindow, DeviceGUI):
Dispatcher(self._books_saved), rows, path, Dispatcher(self._books_saved), rows, path,
by_author=self.library_view.model().by_author, by_author=self.library_view.model().by_author,
single_dir=single_dir, single_dir=single_dir,
single_format=single_format) single_format=single_format,
spare_server=self.spare_server)
else: else:
paths = self.current_view().model().paths(rows) paths = self.current_view().model().paths(rows)
@ -1618,6 +1632,8 @@ class Main(MainWindow, Ui_MainWindow, DeviceGUI):
self.check_messages_timer.stop() self.check_messages_timer.stop()
self.listener.close() self.listener.close()
self.job_manager.server.close() self.job_manager.server.close()
while self.spare_servers:
self.spare_servers.pop().close()
self.device_manager.keep_going = False self.device_manager.keep_going = False
self.cover_cache.stop() self.cover_cache.stop()
self.hide() self.hide()