2005-02-15 Havoc Pennington <hp@redhat.com>
[platform/upstream/dbus.git] / dbus / dbus-pending-call.c
1 /* -*- mode: C; c-file-style: "gnu" -*- */
2 /* dbus-pending-call.c Object representing a call in progress.
3  *
4  * Copyright (C) 2002, 2003 Red Hat Inc.
5  *
6  * Licensed under the Academic Free License version 2.1
7  *
8  * This program is free software; you can redistribute it and/or modify
9  * it under the terms of the GNU General Public License as published by
10  * the Free Software Foundation; either version 2 of the License, or
11  * (at your option) any later version.
12  *
13  * This program is distributed in the hope that it will be useful,
14  * but WITHOUT ANY WARRANTY; without even the implied warranty of
15  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
16  * GNU General Public License for more details.
17  *
18  * You should have received a copy of the GNU General Public License
19  * along with this program; if not, write to the Free Software
20  * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
21  *
22  */
23
24 #include "dbus-internals.h"
25 #include "dbus-connection-internal.h"
26 #include "dbus-pending-call.h"
27 #include "dbus-list.h"
28 #include "dbus-threads.h"
29 #include "dbus-test.h"
30
31 /**
32  * @defgroup DBusPendingCallInternals DBusPendingCall implementation details
33  * @ingroup DBusInternals
34  * @brief DBusPendingCall private implementation details.
35  *
36  * The guts of DBusPendingCall and its methods.
37  *
38  * @{
39  */
40
41 static dbus_int32_t notify_user_data_slot = -1;
42
43 /**
44  * Creates a new pending reply object.
45  *
46  * @param connection connection where reply will arrive
47  * @param timeout_milliseconds length of timeout, -1 for default
48  * @param timeout_handler timeout handler, takes pending call as data
49  * @returns a new #DBusPendingCall or #NULL if no memory.
50  */
51 DBusPendingCall*
52 _dbus_pending_call_new (DBusConnection    *connection,
53                         int                timeout_milliseconds,
54                         DBusTimeoutHandler timeout_handler)
55 {
56   DBusPendingCall *pending;
57   DBusTimeout *timeout;
58
59   _dbus_assert (timeout_milliseconds >= 0 || timeout_milliseconds == -1);
60   
61   if (timeout_milliseconds == -1)
62     timeout_milliseconds = _DBUS_DEFAULT_TIMEOUT_VALUE;
63
64   /* it would probably seem logical to pass in _DBUS_INT_MAX for
65    * infinite timeout, but then math in
66    * _dbus_connection_block_for_reply would get all overflow-prone, so
67    * smack that down.
68    */
69   if (timeout_milliseconds > _DBUS_ONE_HOUR_IN_MILLISECONDS * 6)
70     timeout_milliseconds = _DBUS_ONE_HOUR_IN_MILLISECONDS * 6;
71   
72   if (!dbus_pending_call_allocate_data_slot (&notify_user_data_slot))
73     return NULL;
74   
75   pending = dbus_new0 (DBusPendingCall, 1);
76   
77   if (pending == NULL)
78     {
79       dbus_pending_call_free_data_slot (&notify_user_data_slot);
80       return NULL;
81     }
82
83   timeout = _dbus_timeout_new (timeout_milliseconds,
84                                timeout_handler,
85                                pending, NULL);  
86
87   if (timeout == NULL)
88     {
89       dbus_pending_call_free_data_slot (&notify_user_data_slot);
90       dbus_free (pending);
91       return NULL;
92     }
93   
94   pending->refcount.value = 1;
95   pending->connection = connection;
96   pending->timeout = timeout;
97
98   _dbus_data_slot_list_init (&pending->slot_list);
99   
100   return pending;
101 }
102
103 /**
104  * Calls notifier function for the pending call
105  * and sets the call to completed.
106  *
107  * @param pending the pending call
108  * 
109  */
110 void
111 _dbus_pending_call_notify (DBusPendingCall *pending)
112 {
113   _dbus_assert (!pending->completed);
114   
115   pending->completed = TRUE;
116
117   if (pending->function)
118     {
119       void *user_data;
120       user_data = dbus_pending_call_get_data (pending,
121                                               notify_user_data_slot);
122       
123       (* pending->function) (pending, user_data);
124     }
125 }
126
127 /** @} */
128
129 /**
130  * @defgroup DBusPendingCall DBusPendingCall
131  * @ingroup  DBus
132  * @brief Pending reply to a method call message
133  *
134  * A DBusPendingCall is an object representing an
135  * expected reply. A #DBusPendingCall can be created
136  * when you send a message that should have a reply.
137  *
138  * @{
139  */
140
141 /**
142  * @typedef DBusPendingCall
143  *
144  * Opaque data type representing a message pending.
145  */
146
147 /**
148  * Increments the reference count on a pending call.
149  *
150  * @param pending the pending call object
151  * @returns the pending call object
152  */
153 DBusPendingCall *
154 dbus_pending_call_ref (DBusPendingCall *pending)
155 {
156   _dbus_return_val_if_fail (pending != NULL, NULL);
157
158   _dbus_atomic_inc (&pending->refcount);
159
160   return pending;
161 }
162
163 /**
164  * Decrements the reference count on a pending call,
165  * freeing it if the count reaches 0.
166  *
167  * @param pending the pending call object
168  */
169 void
170 dbus_pending_call_unref (DBusPendingCall *pending)
171 {
172   dbus_bool_t last_unref;
173
174   _dbus_return_if_fail (pending != NULL);
175
176   last_unref = (_dbus_atomic_dec (&pending->refcount) == 1);
177
178   if (last_unref)
179     {
180       /* If we get here, we should be already detached
181        * from the connection, or never attached.
182        */
183       _dbus_assert (pending->connection == NULL);
184       _dbus_assert (!pending->timeout_added);  
185
186       /* this assumes we aren't holding connection lock... */
187       _dbus_data_slot_list_free (&pending->slot_list);
188       
189       if (pending->timeout != NULL)
190         _dbus_timeout_unref (pending->timeout);
191       
192       if (pending->timeout_link)
193         {
194           dbus_message_unref ((DBusMessage *)pending->timeout_link->data);
195           _dbus_list_free_link (pending->timeout_link);
196           pending->timeout_link = NULL;
197         }
198
199       if (pending->reply)
200         {
201           dbus_message_unref (pending->reply);
202           pending->reply = NULL;
203         }
204       
205       dbus_free (pending);
206
207       dbus_pending_call_free_data_slot (&notify_user_data_slot);
208     }
209 }
210
211 /**
212  * Sets a notification function to be called when the reply is
213  * received or the pending call times out.
214  *
215  * @param pending the pending call
216  * @param function notifier function
217  * @param user_data data to pass to notifier function
218  * @param free_user_data function to free the user data
219  * @returns #FALSE if not enough memory
220  */
221 dbus_bool_t
222 dbus_pending_call_set_notify (DBusPendingCall              *pending,
223                               DBusPendingCallNotifyFunction function,
224                               void                         *user_data,
225                               DBusFreeFunction              free_user_data)
226 {
227   _dbus_return_val_if_fail (pending != NULL, FALSE);
228
229   /* could invoke application code! */
230   if (!dbus_pending_call_set_data (pending, notify_user_data_slot,
231                                    user_data, free_user_data))
232     return FALSE;
233   
234   pending->function = function;
235
236   return TRUE;
237 }
238
239 /**
240  * Cancels the pending call, such that any reply or error received
241  * will just be ignored.  Drops the dbus library's internal reference
242  * to the #DBusPendingCall so will free the call if nobody else is
243  * holding a reference. However you usually get a reference
244  * from dbus_connection_send() so probably your app owns a ref also.
245  * 
246  * @param pending the pending call
247  */
248 void
249 dbus_pending_call_cancel (DBusPendingCall *pending)
250 {
251   if (pending->connection)
252     _dbus_connection_remove_pending_call (pending->connection,
253                                           pending);
254 }
255
256 /**
257  * Checks whether the pending call has received a reply
258  * yet, or not.
259  *
260  * @todo not thread safe? I guess it has to lock though it sucks
261  *
262  * @param pending the pending call
263  * @returns #TRUE if a reply has been received */
264 dbus_bool_t
265 dbus_pending_call_get_completed (DBusPendingCall *pending)
266 {
267   return pending->completed;
268 }
269
270 /**
271  * Gets the reply, or returns #NULL if none has been received
272  * yet. Ownership of the reply message passes to the caller. This
273  * function can only be called once per pending call, since the reply
274  * message is tranferred to the caller.
275  * 
276  * @param pending the pending call
277  * @returns the reply message or #NULL.
278  */
279 DBusMessage*
280 dbus_pending_call_steal_reply (DBusPendingCall *pending)
281 {
282   DBusMessage *message;
283   
284   _dbus_return_val_if_fail (pending->completed, NULL);
285   _dbus_return_val_if_fail (pending->reply != NULL, NULL);
286   
287   message = pending->reply;
288   pending->reply = NULL;
289
290   return message;
291 }
292
293 /**
294  * Block until the pending call is completed.  The blocking is as with
295  * dbus_connection_send_with_reply_and_block(); it does not enter the
296  * main loop or process other messages, it simply waits for the reply
297  * in question.
298  *
299  * If the pending call is already completed, this function returns
300  * immediately.
301  *
302  * @todo when you start blocking, the timeout is reset, but it should
303  * really only use time remaining since the pending call was created.
304  *
305  * @param pending the pending call
306  */
307 void
308 dbus_pending_call_block (DBusPendingCall *pending)
309 {
310   _dbus_connection_block_pending_call (pending);
311 }
312
313 static DBusDataSlotAllocator slot_allocator;
314 _DBUS_DEFINE_GLOBAL_LOCK (pending_call_slots);
315
316 /**
317  * Allocates an integer ID to be used for storing application-specific
318  * data on any DBusPendingCall. The allocated ID may then be used
319  * with dbus_pending_call_set_data() and dbus_pending_call_get_data().
320  * The passed-in slot must be initialized to -1, and is filled in
321  * with the slot ID. If the passed-in slot is not -1, it's assumed
322  * to be already allocated, and its refcount is incremented.
323  * 
324  * The allocated slot is global, i.e. all DBusPendingCall objects will
325  * have a slot with the given integer ID reserved.
326  *
327  * @param slot_p address of a global variable storing the slot
328  * @returns #FALSE on failure (no memory)
329  */
330 dbus_bool_t
331 dbus_pending_call_allocate_data_slot (dbus_int32_t *slot_p)
332 {
333   return _dbus_data_slot_allocator_alloc (&slot_allocator,
334                                           _DBUS_LOCK_NAME (pending_call_slots),
335                                           slot_p);
336 }
337
338 /**
339  * Deallocates a global ID for #DBusPendingCall data slots.
340  * dbus_pending_call_get_data() and dbus_pending_call_set_data() may
341  * no longer be used with this slot.  Existing data stored on existing
342  * DBusPendingCall objects will be freed when the #DBusPendingCall is
343  * finalized, but may not be retrieved (and may only be replaced if
344  * someone else reallocates the slot).  When the refcount on the
345  * passed-in slot reaches 0, it is set to -1.
346  *
347  * @param slot_p address storing the slot to deallocate
348  */
349 void
350 dbus_pending_call_free_data_slot (dbus_int32_t *slot_p)
351 {
352   _dbus_return_if_fail (*slot_p >= 0);
353   
354   _dbus_data_slot_allocator_free (&slot_allocator, slot_p);
355 }
356
357 /**
358  * Stores a pointer on a #DBusPendingCall, along
359  * with an optional function to be used for freeing
360  * the data when the data is set again, or when
361  * the pending call is finalized. The slot number
362  * must have been allocated with dbus_pending_call_allocate_data_slot().
363  *
364  * @param pending the pending_call
365  * @param slot the slot number
366  * @param data the data to store
367  * @param free_data_func finalizer function for the data
368  * @returns #TRUE if there was enough memory to store the data
369  */
370 dbus_bool_t
371 dbus_pending_call_set_data (DBusPendingCall  *pending,
372                             dbus_int32_t      slot,
373                             void             *data,
374                             DBusFreeFunction  free_data_func)
375 {
376   DBusFreeFunction old_free_func;
377   void *old_data;
378   dbus_bool_t retval;
379
380   _dbus_return_val_if_fail (pending != NULL, FALSE);
381   _dbus_return_val_if_fail (slot >= 0, FALSE);
382
383   retval = _dbus_data_slot_list_set (&slot_allocator,
384                                      &pending->slot_list,
385                                      slot, data, free_data_func,
386                                      &old_free_func, &old_data);
387
388   if (retval)
389     {
390       if (old_free_func)
391         (* old_free_func) (old_data);
392     }
393
394   return retval;
395 }
396
397 /**
398  * Retrieves data previously set with dbus_pending_call_set_data().
399  * The slot must still be allocated (must not have been freed).
400  *
401  * @param pending the pending_call
402  * @param slot the slot to get data from
403  * @returns the data, or #NULL if not found
404  */
405 void*
406 dbus_pending_call_get_data (DBusPendingCall   *pending,
407                             dbus_int32_t       slot)
408 {
409   void *res;
410
411   _dbus_return_val_if_fail (pending != NULL, NULL);
412
413   res = _dbus_data_slot_list_get (&slot_allocator,
414                                   &pending->slot_list,
415                                   slot);
416
417   return res;
418 }
419
420 /** @} */
421
422 #ifdef DBUS_BUILD_TESTS
423
424 /**
425  * @ingroup DBusPendingCallInternals
426  * Unit test for DBusPendingCall.
427  *
428  * @returns #TRUE on success.
429  */
430 dbus_bool_t
431 _dbus_pending_call_test (const char *test_data_dir)
432 {  
433
434   return TRUE;
435 }
436 #endif /* DBUS_BUILD_TESTS */