From 625fbee2001be8b861b45cc3bb4c593afc4dbbd3 Mon Sep 17 00:00:00 2001 From: Richard Taylor Date: Sat, 17 Feb 2007 19:59:21 +0000 Subject: [PATCH] More work on progress monitor. svn: r8142 --- ChangeLog | 12 ++++++ src/DisplayModels/_PeopleModel.py | 20 ++++++--- src/DisplayState.py | 5 ++- src/GrampsDb/_CursorIterator.py | 70 +++++++++++++++---------------- src/GrampsDb/_GrampsBSDDB.py | 8 ++++ src/GrampsDb/_GrampsDbBase.py | 36 +++++++++------- src/GrampsDb/_GrampsInMemDB.py | 3 ++ src/GrampsDb/_LongOpStatus.py | 6 +++ src/GrampsDb/_ProgressMonitor.py | 32 ++++++++++++-- src/ProgressDialog.py | 12 +++--- src/ViewManager.py | 6 ++- 11 files changed, 144 insertions(+), 66 deletions(-) diff --git a/ChangeLog b/ChangeLog index bb3c1180b..b8bdbb7c2 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,3 +1,15 @@ +2007-02-17 Richard Taylor + * src/ViewManager.py: add progress monitor + * src/GrampsDb/_GrampsDbBase.py: add get_length to cursors + * src/GrampsDb/_LongOpStatus.py: add __del__ method + * src/GrampsDb/_ProgressMonitor.py: add class params + * src/GrampsDb/_GrampsInMemDB.py: add get_length to cursors + * src/GrampsDb/_CursorIterator.py: use get_length methods + * src/GrampsDb/_GrampsBSDDB.py: add get_length to cursors + * src/DisplayState.py: add progress monitor + * src/DisplayModels/_PeopleModel.py: use LongOpStatus + * src/ProgressDialog.py: fix show method + 2007-02-17 Brian Matherly * src/ReportBase/_Report.py: remove unused progress bar functions diff --git a/src/DisplayModels/_PeopleModel.py b/src/DisplayModels/_PeopleModel.py index 69fad0d4c..f180ef55f 100644 --- a/src/DisplayModels/_PeopleModel.py +++ b/src/DisplayModels/_PeopleModel.py @@ -65,6 +65,7 @@ import DateHandler import ToolTips import GrampsLocale import Config +from GrampsDb import LongOpStatus from Filters import SearchFilter, ExactSearchFilter from Lru import LRU @@ -321,10 +322,11 @@ class PeopleModel(gtk.GenericTreeModel): self.mapper.clear_sort_names() - cursor = self.db.get_person_cursor() - node = cursor.first() + #cursor = self.db.get_person_cursor() + #node = cursor.first() - while node: + #while node: + for node in self.db.get_person_cursor_iter(): handle, d = node if not (handle in skip or (dfilter and not dfilter.match(handle))): name_data = d[PeopleModel._NAME_COL] @@ -333,8 +335,8 @@ class PeopleModel(gtk.GenericTreeModel): sorted_name = nsn(name_data) self.mapper.assign_sort_name(handle, sorted_name, group_name) - node = cursor.next() - cursor.close() + #node = cursor.next() + #cursor.close() def _build_filter_sub(self,dfilter, skip): @@ -348,7 +350,13 @@ class PeopleModel(gtk.GenericTreeModel): self.mapper.clear_sort_names() + status = LongOpStatus(msg="Loading People", + total_steps=len(handle_list), + interval=len(handle_list)/10) + self.db.emit('long-op-start', (status,)) + for handle in handle_list: + status.heartbeat() d = self.db.get_raw_person_data(handle) if not (handle in skip or (dfilter and not dfilter.match(handle))): name_data = d[PeopleModel._NAME_COL] @@ -358,6 +366,8 @@ class PeopleModel(gtk.GenericTreeModel): self.mapper.assign_sort_name(handle, sorted_name, group_name) + status.end() + def calculate_data(self, dfilter=None, skip=[]): """ Calculates the new path to node values for the model. diff --git a/src/DisplayState.py b/src/DisplayState.py index 82c9372e9..576667f04 100644 --- a/src/DisplayState.py +++ b/src/DisplayState.py @@ -260,10 +260,12 @@ class DisplayState(GrampsDb.GrampsDBCallback): 'plugins-reloaded' : (list,list), } - def __init__(self, window, status, progress, warnbtn, uimanager): + def __init__(self, window, status, progress, warnbtn, uimanager, + progress_monitor): self.busy = False self.uimanager = uimanager + self.progress_monitor = progress_monitor self.window = window GrampsDb.GrampsDBCallback.__init__(self) self.status = status @@ -290,6 +292,7 @@ class DisplayState(GrampsDb.GrampsDBCallback): def db_changed(self, db): from PluginUtils import _PluginMgr self.relationship = _PluginMgr.relationship_class(db) + db.connect('long-op-start', self.progress_monitor.add_op) def display_relationship(self,dbstate): default_person = dbstate.db.get_default_person() diff --git a/src/GrampsDb/_CursorIterator.py b/src/GrampsDb/_CursorIterator.py index 74459827e..cea1827d8 100644 --- a/src/GrampsDb/_CursorIterator.py +++ b/src/GrampsDb/_CursorIterator.py @@ -2,41 +2,41 @@ from _LongOpStatus import LongOpStatus class CursorIterator(object): - def __init__(self,db,cursor): - self._db = db - self._cursor = cursor - #self._status = LongOpStatus(total_steps=cursor.get_length(),interval=10) - self._status = LongOpStatus() + def __init__(self, db, cursor, msg=""): + self._db = db + self._cursor = cursor + self._status = LongOpStatus(total_steps=cursor.get_length(), interval=10) + #self._status = LongOpStatus(msg=msg) def __iter__(self): - try: - # Emit start signal - self._db.emit('long-op-start',(self._status,)) - - first = self._cursor.first() - if first: - yield first - - next = self._cursor.next() - while next: - yield next - - # check for cancel - #if self._status.should_cancel(): - # raise GrampsDbUserCancel - - # emit heartbeat - self._status.heartbeat() - next = self._cursor.next() - - # emit stop signal - self._status.end() - self._cursor.close() - raise StopIteration - except: - # Not allowed to use 'finally' because we - # yeild inside the try clause. - self._cursor.close() - self._status_end() - raise + try: + # Emit start signal + self._db.emit('long-op-start', (self._status,)) + + first = self._cursor.first() + if first: + yield first + + next = self._cursor.next() + while next: + yield next + + # check for cancel + #if self._status.should_cancel(): + # raise GrampsDbUserCancel + + # emit heartbeat + self._status.heartbeat() + next = self._cursor.next() + + # emit stop signal + self._status.end() + self._cursor.close() + raise StopIteration + except: + # Not allowed to use 'finally' because we + # yeild inside the try clause. + self._cursor.close() + self._status.end() + raise diff --git a/src/GrampsDb/_GrampsBSDDB.py b/src/GrampsDb/_GrampsBSDDB.py index a11b495e2..44d7e7130 100644 --- a/src/GrampsDb/_GrampsBSDDB.py +++ b/src/GrampsDb/_GrampsBSDDB.py @@ -75,6 +75,7 @@ class GrampsBSDDBCursor(GrampsCursor): def __init__(self,source,txn=None): self.cursor = source.db.cursor(txn) + self.source = source def first(self): d = self.cursor.first() @@ -93,11 +94,15 @@ class GrampsBSDDBCursor(GrampsCursor): def delete(self): self.cursor.delete() + + def get_length(self): + return self.source.stat()['ndata'] class GrampsBSDDBAssocCursor(GrampsCursor): def __init__(self,source,txn=None): self.cursor = source.cursor(txn) + self.source = source def first(self): d = self.cursor.first() @@ -116,6 +121,9 @@ class GrampsBSDDBAssocCursor(GrampsCursor): def delete(self): self.cursor.delete() + + def get_length(self): + return self.source.stat()['ndata'] class GrampsBSDDBDupCursor(GrampsBSDDBAssocCursor): """Cursor that includes handling for duplicate keys""" diff --git a/src/GrampsDb/_GrampsDbBase.py b/src/GrampsDb/_GrampsDbBase.py index 786bcbe1f..7d116d2c8 100644 --- a/src/GrampsDb/_GrampsDbBase.py +++ b/src/GrampsDb/_GrampsDbBase.py @@ -132,6 +132,14 @@ class GrampsCursor: finished using the cursor, freeing up the cursor's resources. """ pass + + def get_length(self): + """ + Returns the number of records in the table referenced by the + cursor + """ + raise NotImplementedError, \ + "get_length must be implemented by all subclasses of GrampsCursor" class GrampsDbBookmarks: def __init__(self, default = []): @@ -346,44 +354,44 @@ class GrampsDbBase(GrampsDBCallback): def get_person_cursor(self): assert False, "Needs to be overridden in the derived class" - def get_person_cursor_iter(self): - return CursorIterator(self,self.get_person_cursor()) + def get_person_cursor_iter(self, msg=_("Processing Person records")): + return CursorIterator(self, self.get_person_cursor(), msg) def get_family_cursor(self): assert False, "Needs to be overridden in the derived class" - def get_family_cursor_iter(self): - return CursorIterator(self,self.get_family_cursor()) + def get_family_cursor_iter(self, msg=_("Processing Family records")): + return CursorIterator(self, self.get_family_cursor(), msg) def get_event_cursor(self): assert False, "Needs to be overridden in the derived class" - def get_event_cursor_iter(self): - return CursorIterator(self,self.get_event_cursor()) + def get_event_cursor_iter(self, msg=_("Processing Event records")): + return CursorIterator(self, self.get_event_cursor(), msg) def get_place_cursor(self): assert False, "Needs to be overridden in the derived class" - def get_place_cursor_iter(self): - return CursorIterator(self,self.get_place_cursor()) + def get_place_cursor_iter(self, msg=_("Processing Place records")): + return CursorIterator(self, self.get_place_cursor(), msg) def get_source_cursor(self): assert False, "Needs to be overridden in the derived class" - def get_source_cursor_iter(self): - return CursorIterator(self,self.get_source_cursor()) + def get_source_cursor_iter(self, msg=_("Processing Source records")): + return CursorIterator(self, self.get_source_cursor(), msg) def get_media_cursor(self): assert False, "Needs to be overridden in the derived class" - def get_media_cursor_iter(self): - return CursorIterator(self,self.get_media_cursor()) + def get_media_cursor_iter(self, msg=_("Processing Media records")): + return CursorIterator(self, self.get_media_cursor(), msg) def get_repository_cursor(self): assert False, "Needs to be overridden in the derived class" - def get_repository_cursor_iter(self): - return CursorIterator(self,self.get_repository_cursor()) + def get_repository_cursor_iter(self, msg=_("Processing Repository records")): + return CursorIterator(self, self.get_repository_cursor(), msg) def open_undodb(self): if not self.readonly: diff --git a/src/GrampsDb/_GrampsInMemDB.py b/src/GrampsDb/_GrampsInMemDB.py index 941909f0c..c18dc85f8 100644 --- a/src/GrampsDb/_GrampsInMemDB.py +++ b/src/GrampsDb/_GrampsInMemDB.py @@ -63,6 +63,9 @@ class GrampsInMemCursor(GrampsCursor): def close(self): pass + + def get_length(self): + return len(self.src_map) #------------------------------------------------------------------------- # diff --git a/src/GrampsDb/_LongOpStatus.py b/src/GrampsDb/_LongOpStatus.py index 745ee13f1..44596ac62 100644 --- a/src/GrampsDb/_LongOpStatus.py +++ b/src/GrampsDb/_LongOpStatus.py @@ -91,7 +91,12 @@ class LongOpStatus(GrampsDBCallback): self._countdown = interval self._secs_left = 0 self._start = time.time() + self._running = True + def __del__(self): + if self._running: + self.emit('op-end') + def heartbeat(self): """This should be called for each step in the operation. It will emit a 'op-heartbeat' every 'interval' steps. It recalcuates the @@ -128,6 +133,7 @@ class LongOpStatus(GrampsDBCallback): """End the operation. Causes the 'op-end' signal to be emitted. """ self.emit('op-end') + self._running = False def should_cancel(self): """Returns true of the user has asked for the operation to be cancelled. diff --git a/src/GrampsDb/_ProgressMonitor.py b/src/GrampsDb/_ProgressMonitor.py index ce72ab4dd..8e5bd3f86 100644 --- a/src/GrampsDb/_ProgressMonitor.py +++ b/src/GrampsDb/_ProgressMonitor.py @@ -2,6 +2,10 @@ This module provides a progess dialog for displaying the status of long running operations. """ +import logging +log = logging.getLogger(".GrampsDb") + +from gettext import gettext as _ class _StatusObjectFacade(object): """This provides a simple structure for recording the information @@ -36,15 +40,27 @@ class ProgressMonitor(object): __default_popup_time = 5 # seconds - def __init__(self, dialog_class, popup_time = None): + def __init__(self, dialog_class, dialog_class_params=(), + title=_("Progress Information"), + popup_time = None): """ @param dialog_class: A class used to display the progress dialog. @type dialog_class: L{_GtkProgressDialog} or the same interface. + @param dialog_class_params: A tuple that will be used as the initial + arguments to the dialog_class, this might be used for passing in + a parent window handle. + @type dialog_class_params: tuple + + @param title: The title of the progress dialog + @type title: string + @param popup_time: number of seconds to wait before popup. @type popup_time: int """ self._dialog_class = dialog_class + self._dialog_class_params = dialog_class_params + self._title = title self._popup_time = popup_time if self._popup_time == None: @@ -55,8 +71,10 @@ class ProgressMonitor(object): def _get_dlg(self): if self._dlg == None: - self._dlg = self._dialog_class("Long running operation.") - self._dlg.show() + self._dlg = self._dialog_class(self._dialog_class_params, + self._title) + + self._dlg.show() return self._dlg @@ -66,6 +84,8 @@ class ProgressMonitor(object): @param op_status: the status object. @type op_status: L{GrampsDb.LongOpStatus} """ + + log.debug("adding op to Progress Monitor") facade = _StatusObjectFacade(op_status) self._status_stack.append(facade) idx = len(self._status_stack)-1 @@ -84,6 +104,8 @@ class ProgressMonitor(object): # check the estimated time to complete to see if we need # to pop up a progress dialog. + log.debug("heartbeat in ProgressMonitor") + facade = self._status_stack[idx] if facade.status_obj.estimated_secs_to_complete() > self._popup_time: @@ -100,6 +122,8 @@ class ProgressMonitor(object): def _end(self, idx): # hide any progress dialog # remove the status object from the stack + + log.debug("received end in ProgressMonitor") facade = self._status_stack[idx] if facade.active: dlg = self._get_dlg() @@ -119,7 +143,7 @@ if __name__ == '__main__': from GrampsDb import LongOpStatus def test(a,b): - d = ProgressDialog(_GtkProgressDialog) + d = ProgressDialog(_GtkProgressDialog, "Test Progress") s = LongOpStatus("Doing very long operation", 100, 10) diff --git a/src/ProgressDialog.py b/src/ProgressDialog.py index 9d797df60..c03a4b7e1 100644 --- a/src/ProgressDialog.py +++ b/src/ProgressDialog.py @@ -43,8 +43,8 @@ class _GtkProgressBar(gtk.VBox): self._pbar_max = (long_op_status.get_total_steps()/ long_op_status.get_interval()) self._pbar_index = 0.0 - self._pbar.set_fraction((float(long_op_status.get_total_steps())/ - (float(long_op_status.get_interval())))/ + self._pbar.set_fraction(((100/float(long_op_status.get_total_steps())* + float(long_op_status.get_interval())))/ 100.0) if msg != '': @@ -70,15 +70,15 @@ class _GtkProgressBar(gtk.VBox): self._pbar.set_fraction(val/100.0) self._pbar.old_val = val -class _GtkProgressDialog(gtk.Dialog): +class GtkProgressDialog(gtk.Dialog): """A gtk window to display the status of a long running process.""" - def __init__(self, title): + def __init__(self, window_params, title): """@param title: The title to display on the top of the window. @type title: string """ - gtk.Dialog.__init__(self) + gtk.Dialog.__init__(self, *window_params) self.connect('delete_event', self._warn) self.set_has_separator(False) self.set_title(title) @@ -162,7 +162,7 @@ if __name__ == '__main__': from GrampsDb import LongOpStatus, ProgressMonitor def test(a,b): - d = ProgressMonitor(_GtkProgressDialog) + d = ProgressMonitor(GtkProgressDialog) s = LongOpStatus("Doing very long operation", 100, 10) diff --git a/src/ViewManager.py b/src/ViewManager.py index affa73b4f..8bab1dd8e 100644 --- a/src/ViewManager.py +++ b/src/ViewManager.py @@ -78,6 +78,8 @@ import GrampsWidgets import UndoHistory from DbLoader import DbLoader import GrampsDisplay +from GrampsDb import ProgressMonitor +import ProgressDialog def show_url(dialog,link,user_data): GrampsDisplay.url(link) @@ -245,6 +247,8 @@ class ViewManager: vbox.pack_start(self.menubar, False) vbox.pack_start(self.toolbar, False) vbox.add(hbox) + self.progress_monitor = ProgressMonitor(ProgressDialog.GtkProgressDialog, + ("",self.window)) self.progress = gtk.ProgressBar() self.progress.set_size_request(100, -1) self.progress.hide() @@ -262,7 +266,7 @@ class ViewManager: self.uistate = DisplayState.DisplayState( self.window, self.statusbar, self.progress, self.warnbtn, - self.uimanager) + self.uimanager, self.progress_monitor) self.state.connect('database-changed', self.uistate.db_changed) toolbar = self.uimanager.get_widget('/ToolBar')