commit dee289dca2369013fd5f5b50198e294e224b7b7b
Author: Zach Goldberg <zgold@bluesata.(none)>
Date:   Thu Jan 21 14:59:17 2010 -0500

    Implement 4 types of callback scope: call, async, notify and infinity.

diff --git a/gi/pygi-argument.c b/gi/pygi-argument.c
index 3bc53cd..a1783e1 100644
--- a/gi/pygi-argument.c
+++ b/gi/pygi-argument.c
@@ -624,12 +624,12 @@ _pygi_argument_to_array (GArgument  *arg,
 GArgument
 _pygi_argument_from_object (PyObject   *object,
                             GITypeInfo *type_info,
-                            GITransfer  transfer)
+                            GITransfer  transfer,
+			    GIArgInfo  *arg_info)
 {
     GArgument arg;
     GITypeTag type_tag;
 
-    GSList *call_free_list = NULL; /* list of PyGiCallbackInvokeInfo* */
     GSList *data_for_notify = NULL; /* list of PyGiCallbackInfo* */
 
     type_tag = g_type_info_get_tag(type_info);
@@ -845,7 +845,7 @@ _pygi_argument_from_object (PyObject   *object,
                     goto array_item_error;
                 }
 
-                item = _pygi_argument_from_object(py_item, item_type_info, item_transfer);
+                item = _pygi_argument_from_object(py_item, item_type_info, item_transfer, arg_info);
 
                 Py_DECREF(py_item);
 
@@ -885,7 +885,10 @@ array_item_error:
 			
 			if (!_pygi_make_native_closure((GICallableInfo*)interface_info,
 						       type_info,
+						       arg_info,
 						       object,
+						       &call_free_list,
+						       &destroy_notify_infos,
 						       &(arg.v_pointer)))
 			    {
 				// TODO: Raise an errorg
@@ -1017,7 +1020,7 @@ array_item_error:
                     goto list_item_error;
                 }
 
-                item = _pygi_argument_from_object(py_item, item_type_info, item_transfer);
+                item = _pygi_argument_from_object(py_item, item_type_info, item_transfer, arg_info);
 
                 Py_DECREF(py_item);
 
@@ -1116,12 +1119,12 @@ list_item_error:
                 py_key = PyList_GET_ITEM(keys, i);
                 py_value = PyList_GET_ITEM(values, i);
 
-                key = _pygi_argument_from_object(py_key, key_type_info, item_transfer);
+                key = _pygi_argument_from_object(py_key, key_type_info, item_transfer, arg_info);
                 if (PyErr_Occurred()) {
                     goto hash_table_item_error;
                 }
 
-                value = _pygi_argument_from_object(py_value, value_type_info, item_transfer);
+                value = _pygi_argument_from_object(py_value, value_type_info, item_transfer, arg_info);
                 if (PyErr_Occurred()) {
                     _pygi_argument_release(&key, type_info, GI_TRANSFER_NOTHING, GI_DIRECTION_IN);
                     goto hash_table_item_error;
diff --git a/gi/pygi-argument.h b/gi/pygi-argument.h
index c162f2f..0ed859b 100644
--- a/gi/pygi-argument.h
+++ b/gi/pygi-argument.h
@@ -31,6 +31,10 @@ G_BEGIN_DECLS
 
 /* Private */
 
+GSList* call_free_list;
+GSList *destroy_notify_infos;
+gint cur_arg_index;
+
 gint _pygi_g_type_info_check_object (GITypeInfo *type_info,
                                      PyObject   *object);
 
@@ -46,7 +50,8 @@ GArray* _pygi_argument_to_array (GArgument  *arg,
 
 GArgument _pygi_argument_from_object (PyObject   *object,
                                       GITypeInfo *type_info,
-                                      GITransfer  transfer);
+                                      GITransfer  transfer,
+				      GIArgInfo  *arg_info);
 
 PyObject* _pygi_argument_to_object (GArgument  *arg,
                                     GITypeInfo *type_info,
diff --git a/gi/pygi-closure.c b/gi/pygi-closure.c
index 2ed82fa..d6c3f8c 100644
--- a/gi/pygi-closure.c
+++ b/gi/pygi-closure.c
@@ -23,10 +23,35 @@
 
 #include <string.h>
 #include <time.h>
+#include <sys/mman.h>
 
 #include <datetime.h>
 #include <pygobject.h>
 
+void _pygi_callback_notify_info_free(gpointer data)
+{
+
+    GSList* info;
+    for(info = data; info; info->next)
+	g_slice_free(PyGICallbackNotifyInfo, info->data);
+
+}
+void _pygi_callback_invoke_closure_free(gpointer data)
+{
+    PyGINativeClosure* invoke_closure = (PyGINativeClosure *)data;
+    
+    if (invoke_closure->info)
+	g_base_info_unref((GIBaseInfo*)invoke_closure->info);
+
+    munmap(invoke_closure->closure, sizeof(ffi_closure));
+    g_slice_free(ffi_cif, invoke_closure->cif);
+
+    if (!invoke_closure->is_destroy_notify)
+	Py_DECREF(invoke_closure->function);
+
+    g_slice_free(PyGINativeClosure, invoke_closure);
+}
+
 void _pygi_handle_closure(ffi_cif *cif, void *result, void **args, void *userdata){
   PyGILState_STATE gstate;
   PyGINativeClosure *closure = userdata;
@@ -82,55 +107,162 @@ void _pygi_handle_closure(ffi_cif *cif, void *result, void **args, void *userdat
      return;
   }
 
-  *(GArgument*)result = _pygi_argument_from_object(retval, return_type, return_transfer);
+  *(GArgument*)result = _pygi_argument_from_object(retval, return_type, return_transfer, arg_info);
 
   PyGILState_Release (gstate);
+
+  /* If this was an async type closure we need to free it now that we have used it
+   */
+  if (closure->free_on_use)
+      _pygi_callback_invoke_closure_free(closure);
+
 }
 
+static void
+_pygi_destroy_notify_callback_closure(ffi_cif *cif,
+				      void *result,
+				      void **args,
+				      void *data)
+{
+    PyGINativeClosure *info = (PyGINativeClosure *)data;
+
+    _pygi_callback_invoke_closure_free(info->notify);
+    _pygi_callback_invoke_closure_free(info);    
+}
+
+
+static gboolean
+_pygi_destroy_notify_create(PyGINativeClosure* destroy_notify)
+{
+    static const ffi_type *destroy_notify_args[] = {&ffi_type_pointer};
+    ffi_status status;
 
-PyGINativeClosure*
+    g_assert(destroy_notify);
+
+    destroy_notify->is_destroy_notify = TRUE;
+
+    //    info->need_free_arg_types = FALSE;
+
+    destroy_notify->closure = mmap(NULL, sizeof (ffi_closure),
+                         PROT_EXEC | PROT_READ | PROT_WRITE,
+                         MAP_ANON | MAP_PRIVATE, -1, sysconf (_SC_PAGE_SIZE));
+
+    if (!destroy_notify->closure) {
+        //gjs_throw(info->context, "mmap failed\n");
+        return FALSE;
+    }
+
+    destroy_notify->cif = g_slice_new(ffi_cif);
+
+    status = ffi_prep_cif(destroy_notify->cif, FFI_DEFAULT_ABI,
+                          1, &ffi_type_void,
+                          (ffi_type**)destroy_notify_args);
+    
+    if (status != FFI_OK) {
+        //gjs_throw(info->context, "ffi_prep_cif failed: %d\n", status);
+        munmap(destroy_notify->closure, sizeof (ffi_closure));
+        return FALSE;
+    }
+
+    status = ffi_prep_closure(destroy_notify->closure, destroy_notify->cif,
+                              _pygi_destroy_notify_callback_closure, destroy_notify);
+
+    if (status != FFI_OK) {
+        //gjs_throw(info->context, "ffi_prep_cif failed: %d\n", status);
+        munmap(destroy_notify->closure, sizeof (ffi_closure));
+        return FALSE;
+    }
+
+    if (mprotect(destroy_notify->closure, sizeof (ffi_closure), PROT_READ | PROT_EXEC) == -1) {
+        //gjs_throw(info->context, "ffi_prep_closure failed: %s\n", strerror(errno));
+        munmap(destroy_notify->closure, sizeof (ffi_closure));
+        return FALSE;
+    }
+
+    return TRUE;
+}
+
+
+gboolean
 _pygi_make_native_closure (GICallableInfo* info,
 			   GITypeInfo* type_info, 
+			   GIArgInfo* arg_info,
 			   PyObject *function, 
+			   GSList**  call_free_list,
+			   GSList** destroy_notify_infos,
 			   gpointer* closure)
 {
-  /* Code copied with pythonic modifications from seed's make_native_closure() method */
   ffi_cif *cif;
   ffi_closure *fficlosure;
   PyObject* cache;
   PyObject* dict;
   PyGINativeClosure *invoke_closure;
+  PyGICallbackNotifyInfo *notify_info;
+  GSList *l;
 
-  // TODO: Assert that function is in fact a function
-  Py_XINCREF(function);
-
-
-  if (PyObject_HasAttrString(function, "__pygi_native_closure")){
-    cache =  (PyObject*) PyObject_GetAttrString(function, 
-                                                          "__pygi_native_closure");
-    closure = PyCObject_AsVoidPtr(cache);
-  }else{      
-    cif = g_new0 (ffi_cif, 1);
-
-    invoke_closure = g_slice_new0(PyGINativeClosure);
-    invoke_closure->info = (GICallableInfo *) g_base_info_ref ((GIBaseInfo *) info);  
-    invoke_closure->function = function;
-    invoke_closure->cif = cif;
-    invoke_closure->type_info = type_info;
+  for (l = *destroy_notify_infos; l; l->next){
+      PyGICallbackNotifyInfo* notify_info = l->data;
+      PyGINativeClosure* callback = notify_info->closure;
+      if (notify_info->destroy_arg_index != cur_arg_index)
+	  continue;
+      
+      invoke_closure = g_slice_new0(PyGINativeClosure);
+      invoke_closure->arg_index = cur_arg_index;
+      if(!_pygi_destroy_notify_create(invoke_closure)){
+	  g_slice_free(PyGINativeClosure, invoke_closure);
+	  return FALSE;
+      }      
+      
+      invoke_closure->notify = (struct PyGINativeClosure*)callback;
+      *closure = invoke_closure->closure;
+      return TRUE;
+  }
 
-    fficlosure =
-        g_callable_info_prepare_closure (info, cif, _pygi_handle_closure,
-					 invoke_closure);
-    invoke_closure->closure = fficlosure;
+  Py_XINCREF(function);
 
-    *closure = invoke_closure->closure;
+  // Build the closure itself
+  cif = g_new0 (ffi_cif, 1);    
+  invoke_closure = g_slice_new0(PyGINativeClosure);
+  invoke_closure->info = (GICallableInfo *) g_base_info_ref ((GIBaseInfo *) info);  
+  invoke_closure->function = function;
+  invoke_closure->cif = cif;
+  invoke_closure->type_info = type_info;
+  invoke_closure->free_on_use = FALSE;
+  invoke_closure->arg_index = cur_arg_index;
+  
+  fficlosure =
+      g_callable_info_prepare_closure (info, cif, _pygi_handle_closure,
+				       invoke_closure);
+  invoke_closure->closure = fficlosure;
+  
+  // Store the closure in the arguement
+  *closure = invoke_closure->closure;
 
+  //Setup scope 
+  switch (g_arg_info_get_scope(arg_info)) {
+      case GI_SCOPE_TYPE_CALL:
+	  /* Append this NativeClosure to a list of closure that we will free
+	     after we're done with this function invokation */
+	  *call_free_list = g_slist_prepend(*call_free_list, invoke_closure);
+	  break;
+      case GI_SCOPE_TYPE_NOTIFIED:
+	  /* Record the index of this notify callback's destroy argument so
+	     we know it when we see it in future calls */
+	  notify_info = g_slice_new0(PyGICallbackNotifyInfo);
+	  notify_info->destroy_arg_index = g_arg_info_get_destroy(arg_info);
+	  notify_info->closure = invoke_closure;
+	  *destroy_notify_infos = g_slist_prepend(*destroy_notify_infos, notify_info);
 
-    dict = PyObject_GetAttrString(function, "__dict__");
-    PyObject_SetItem(dict, PyString_FromString("__pygi_native_closure"),
-                           PyCObject_FromVoidPtr(closure, NULL));                       
+	  break;
+      case GI_SCOPE_TYPE_ASYNC:
+	  invoke_closure->free_on_use = TRUE;
+	      
+      default:
+	  //gjs_throw(context, "Unknown callback scope");
+	  //gjs_callback_invoke_info_free(invoke_info);
+	  return FALSE;
 
-  }
+    }
 
-  return closure;
+  return TRUE;
 }
diff --git a/gi/pygi-closure.h b/gi/pygi-closure.h
index 768cc85..4c08727 100644
--- a/gi/pygi-closure.h
+++ b/gi/pygi-closure.h
@@ -31,20 +31,47 @@ G_BEGIN_DECLS
 
 typedef struct _PyGiNativeClosure
 {
+    /* Need to keep a reference to the
+       notify callback such that when this destroy callback is called
+       (assuming this callback is a destroynotify)
+       it knows a reference to the actual callback */
+    struct PyGINativeClosure* notify;
+
     GICallableInfo *info;
     GITypeInfo *type_info;
     PyObject *function;
     
     ffi_closure *closure;
     ffi_cif *cif;
+
+    gint arg_index;
+    gboolean free_on_use;
+    gboolean is_destroy_notify;
+    
 } PyGINativeClosure; // == GjsCallbackInvokeInfo
 
+typedef struct _PyGiCallbackNotifyInfo
+{
+    gint destroy_arg_index;
+    PyGINativeClosure *closure;
+} PyGICallbackNotifyInfo; // == GjsCallbackInfo
+
+
 
 
 void _pygi_handle_closure(ffi_cif *cif, void *result, void 
 			  **args, void *userdata);
 
-PyGINativeClosure*
-_pygi_make_native_closure (GICallableInfo* info, GITypeInfo* type_info, 
-			   PyObject *function, gpointer *closure);
+void _pygi_callback_invoke_closure_free(gpointer user_data);
+
+void _pygi_callback_notify_info_free(gpointer user_data);
+
+gboolean
+_pygi_make_native_closure (GICallableInfo* info,
+			   GITypeInfo* type_info,
+			   GIArgInfo* arg_info,			   
+			   PyObject *function,
+			   GSList** call_free_list, 
+			   GSList** destroy_notify_infos, 
+			   gpointer *closure);
 #endif
diff --git a/gi/pygi-info.c b/gi/pygi-info.c
index 4d8086c..1f8c3a3 100644
--- a/gi/pygi-info.c
+++ b/gi/pygi-info.c
@@ -547,6 +547,15 @@ _wrap_g_function_info_invoke (PyGIBaseInfo *self,
     args_is_auxiliary = g_newa(gboolean, n_args);
     memset(args_is_auxiliary, 0, sizeof(args_is_auxiliary) * n_args);
 
+
+    /* Initialize list of callbacks of scope 'call' that we will
+       free post function invokation */
+    call_free_list = NULL;
+
+    /* Initialize list of destroy indicies; metadata for callbacks
+       of scope 'notified' */
+    destroy_notify_infos = NULL;
+
     if (is_method) {
         /* The first argument is the instance. */
         n_in_args += 1;
@@ -827,7 +836,8 @@ _wrap_g_function_info_invoke (PyGIBaseInfo *self,
                 g_assert(py_args_pos < n_py_args);
                 py_arg = PyTuple_GET_ITEM(py_args, py_args_pos);
 
-                *args[i] = _pygi_argument_from_object(py_arg, arg_type_infos[i], transfer);
+		cur_arg_index = i;
+                *args[i] = _pygi_argument_from_object(py_arg, arg_type_infos[i], transfer, arg_infos[i]);
 
                 if (PyErr_Occurred()) {
                     /* TODO: release previous input arguments. */
@@ -1212,6 +1222,13 @@ _wrap_g_function_info_invoke (PyGIBaseInfo *self,
     }
 
 out:
+    /* Free any scope call callbacks */
+    g_slist_foreach(call_free_list, (GFunc)_pygi_callback_invoke_closure_free, NULL);
+    g_slist_free(call_free_list);
+
+    g_slist_foreach(destroy_notify_infos, (GFunc)_pygi_callback_notify_info_free, NULL);
+    g_slist_free(destroy_notify_infos);
+
     g_base_info_unref((GIBaseInfo *)return_type_info);
 
     for (i = 0; i < n_args; i++) {
@@ -1997,7 +2014,7 @@ _wrap_g_field_info_set_value (PyGIBaseInfo *self,
                     goto out;
                 }
 
-                value = _pygi_argument_from_object(py_value, field_type_info, GI_TRANSFER_NOTHING);
+                value = _pygi_argument_from_object(py_value, field_type_info, GI_TRANSFER_NOTHING, NULL);
                 if (PyErr_Occurred()) {
                     g_base_info_unref(info);
                     goto out;
@@ -2022,7 +2039,7 @@ _wrap_g_field_info_set_value (PyGIBaseInfo *self,
         g_base_info_unref(info);
     }
 
-    value = _pygi_argument_from_object(py_value, field_type_info, GI_TRANSFER_EVERYTHING);
+    value = _pygi_argument_from_object(py_value, field_type_info, GI_TRANSFER_EVERYTHING, NULL);
     if (PyErr_Occurred()) {
         goto out;
     }
diff --git a/tests/libtestgi.c b/tests/libtestgi.c
index 67b9d15..5aa73ca 100644
--- a/tests/libtestgi.c
+++ b/tests/libtestgi.c
@@ -2927,6 +2927,7 @@ test_gi__overrides_object_return (void)
 /**
  * test_gi_simple_callback:
  * Test basic callback with no arguements
+ * @callback: (scope call): 
  * Returns::
  */
 void test_gi_simple_callback(SimpleCallback callback)
@@ -2936,8 +2937,21 @@ void test_gi_simple_callback(SimpleCallback callback)
 }
 
 /**
+ * test_gi_notify_callback:
+ * Test basic callback with no arguements
+ * @callback: (scope notified): 
+ * Returns::
+ */
+void test_gi_notify_callback(SimpleCallback callback, GDestroyNotify notify)
+{
+  notify(NULL);
+  return;
+}
+
+/**
  * test_gi_native_type_args_callback:
  * Test basic callback with a couple native typed arguements (int, str)
+ * @callback: (scope async): 
  * Returns::
  */
 void test_gi_native_type_args_callback(NativeTypedArgsCallback callback)
diff --git a/tests/libtestgi.h b/tests/libtestgi.h
index 6f9f5ae..e45a93f 100644
--- a/tests/libtestgi.h
+++ b/tests/libtestgi.h
@@ -633,6 +633,7 @@ typedef int (*SimpleReturnCallback)();
 
 
 void test_gi_simple_callback(SimpleCallback callback);
+void test_gi_notify_callback(SimpleCallback callback, GDestroyNotify notify);
 void test_gi_native_type_args_callback(NativeTypedArgsCallback callback);
 void test_gi_simple_struct_callback(SimpleStructCallback callback);
 
diff --git a/tests/test_gi.py b/tests/test_gi.py
index 27158e0..14ea019 100644
--- a/tests/test_gi.py
+++ b/tests/test_gi.py
@@ -1398,6 +1398,9 @@ class TestCallbacks(unittest.TestCase):
     def simple_callback(self):
         self.simple_callback.__dict__["called"] = True
 
+    def destroy_notify(self, data):
+        print "DESTROY CALLED %s" % data
+
     def native_args_callback(self, intval, strval):
         self.native_args_callback.__dict__["called"] = True
 	self.assertEquals(intval, 1337)
@@ -1419,6 +1422,10 @@ class TestCallbacks(unittest.TestCase):
 	TestGI.simple_callback(self.simple_callback)
         self.assertTrue(self.simple_callback.called, "Expected the simple callback to be called")
 
+    def test_notify_callback(self):
+	TestGI.notify_callback(self.simple_callback, self.destroy_notify)
+        #self.assertTrue(self.simple_callback.called, "Expected the simple callback to be called")
+
     # FIXME: Turns out this is really hard to check in python.  On the TODO
     def _test_invalid_arg_count(self):
         exception = False
