python client: general wait utility refactoring

Use a lambda in the wait utility rather than a special wrapper class.
Remove unnecessary callback parameters.
Add an error callback keyword argument.

Change-Id: I8e759cf6fee5eaf9bf4ac44e7ff6576ced4c16a6
Signed-off-by: Brad Bishop <bradleyb@fuzziesquirrel.com>
diff --git a/obmc/mapper/utils.py b/obmc/mapper/utils.py
index 8ac9e55..e34590d 100644
--- a/obmc/mapper/utils.py
+++ b/obmc/mapper/utils.py
@@ -29,13 +29,15 @@
             obmc.mapper.MAPPER_NAME,
             obmc.mapper.MAPPER_PATH,
             introspect=False)
-        self.mapper_iface = dbus.Interface(
+        self.iface = dbus.Interface(
             mapper, dbus_interface=obmc.mapper.MAPPER_IFACE)
         self.done = False
         self.callback = kw.pop('callback', None)
-        self.callback_keyword = kw.pop('keyword', None)
-        self.callback_args = a
-        self.callback_kw = kw
+        self.error_callback = kw.pop('error_callback', self.default_error)
+        self.busy_retries = kw.pop('busy_retries', 5)
+        self.busy_retry_delay_milliseconds = kw.pop(
+            'busy_retry_delay_milliseconds', 1000)
+        self.waitlist_keyword = kw.pop('waitlist_keyword', None)
 
         self.bus.add_signal_receiver(
             self.name_owner_changed_handler,
@@ -46,10 +48,15 @@
             dbus_interface=dbus.BUS_DAEMON_IFACE + '.ObjectManager',
             sender_keyword='sender',
             signal_name='InterfacesAdded')
-        gobject.idle_add(self.name_owner_changed_handler)
 
-    def check_done(self):
-        if not all(self.waitlist.values()) or self.done:
+        self.name_owner_changed_handler()
+
+    @staticmethod
+    def default_error(e):
+        raise e
+
+    def force_done(self):
+        if self.done:
             return
 
         self.done = True
@@ -61,36 +68,65 @@
             self.interfaces_added_handler,
             dbus_interface=dbus.BUS_DAEMON_IFACE + '.ObjectManager',
             signal_name='InterfacesAdded')
+
+    def check_done(self):
+        if not all(self.waitlist.values()) or self.done:
+            return
+
+        self.force_done()
+
         if self.callback:
-            if self.callback_keyword:
-                self.callback_kw[self.callback_keyword] = self.waitlist
-            self.callback(*self.callback_args, **self.callback_kw)
+            kwargs = {}
+            if self.waitlist_keyword:
+                kwargs[waitlist_keyword] = self.waitlist
+            self.callback(**kwargs)
+
+    def get_object_async(self, path, retry):
+        method = getattr(self.iface, 'GetObject')
+        method.call_async(
+            path,
+            reply_handler=lambda x: self.get_object_callback(
+                path, x),
+            error_handler=lambda x: self.get_object_error(
+                path, retry, x))
+        return False
+
+    def get_object_error(self, path, retry, e):
+        if self.done:
+            return
+
+        if e.get_dbus_name() == 'org.freedesktop.DBus.Error.FileNotFound':
+            pass
+        elif e.get_dbus_name() == 'org.freedesktop.DBus.Error.ObjectPathInUse':
+            if retry > self.busy_retries:
+                self.force_done()
+                self.error_callback(e)
+            else:
+                gobject.timeout_add(
+                    self.busy_retry_delay_milliseconds,
+                    self.get_object_async,
+                    path,
+                    retry + 1)
+        else:
+            self.force_done()
+            self.error_callback(e)
 
     def get_object_callback(self, path, info):
         self.waitlist[path] = list(info)[0]
         self.check_done()
 
     def name_owner_changed_handler(self, *a, **kw):
-        class Callback(object):
-            def __init__(self, func, *args):
-                self.func = func
-                self.extra_args = args
-
-            def __call__(self, *a):
-                return self.func(*(self.extra_args + a))
-
         if self.done:
             return
 
-        for path in self.waitlist.keys():
-            method = getattr(self.mapper_iface, 'GetObject')
-            method.call_async(
-                path,
-                reply_handler=Callback(self.get_object_callback, path))
+        for path in filter(
+                lambda x: not self.waitlist[x], self.waitlist.keys()):
+            self.get_object_async(path, 0)
 
     def interfaces_added_handler(self, path, *a, **kw):
         if self.done:
             return
+
         if path in self.waitlist.keys():
             self.waitlist[path] = kw['sender']
         self.check_done()