1 /* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */
2 /* dbus-timeout.c DBusTimeout implementation
3  *
4  * Copyright (C) 2003  CodeFactory AB
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., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
21  *
22  */
23 
24 #include <config.h>
25 #include "dbus-internals.h"
26 #include "dbus-timeout.h"
27 #include "dbus-list.h"
28 
29 /**
30  * @defgroup DBusTimeoutInternals DBusTimeout implementation details
31  * @ingroup  DBusInternals
32  * @brief implementation details for DBusTimeout
33  *
34  * @{
35  */
36 
37 /**
38  * Internals of DBusTimeout
39  */
40 struct DBusTimeout
41 {
42   int refcount;                                /**< Reference count */
43   int interval;                                /**< Timeout interval in milliseconds. */
44 
45   DBusTimeoutHandler handler;                  /**< Timeout handler. */
46   void *handler_data;                          /**< Timeout handler data. */
47   DBusFreeFunction free_handler_data_function; /**< Free the timeout handler data. */
48 
49   void *data;		   	               /**< Application data. */
50   DBusFreeFunction free_data_function;         /**< Free the application data. */
51   unsigned int enabled : 1;                    /**< True if timeout is active. */
52   unsigned int needs_restart : 1;              /**< Flag that timeout should be restarted after re-enabling. */
53 };
54 
55 /**
56  * Creates a new DBusTimeout, enabled by default.
57  * @param interval the timeout interval in milliseconds.
58  * @param handler function to call when the timeout occurs.
59  * @param data data to pass to the handler
60  * @param free_data_function function to be called to free the data.
61  * @returns the new DBusTimeout object,
62  */
63 DBusTimeout*
_dbus_timeout_new(int interval,DBusTimeoutHandler handler,void * data,DBusFreeFunction free_data_function)64 _dbus_timeout_new (int                 interval,
65 		   DBusTimeoutHandler  handler,
66 		   void               *data,
67 		   DBusFreeFunction    free_data_function)
68 {
69   DBusTimeout *timeout;
70 
71   timeout = dbus_new0 (DBusTimeout, 1);
72   if (timeout == NULL)
73     return NULL;
74 
75   timeout->refcount = 1;
76   timeout->interval = interval;
77 
78   timeout->handler = handler;
79   timeout->handler_data = data;
80   timeout->free_handler_data_function = free_data_function;
81 
82   timeout->enabled = TRUE;
83   timeout->needs_restart = FALSE;
84 
85   return timeout;
86 }
87 
88 /**
89  * Increments the reference count of a DBusTimeout object.
90  *
91  * @param timeout the timeout object.
92  * @returns the timeout object.
93  */
94 DBusTimeout *
_dbus_timeout_ref(DBusTimeout * timeout)95 _dbus_timeout_ref (DBusTimeout *timeout)
96 {
97   timeout->refcount += 1;
98 
99   return timeout;
100 }
101 
102 /**
103  * Decrements the reference count of a DBusTimeout object
104  * and finalizes the object if the count reaches zero.
105  *
106  * @param timeout the timeout object.
107  */
108 void
_dbus_timeout_unref(DBusTimeout * timeout)109 _dbus_timeout_unref (DBusTimeout *timeout)
110 {
111   _dbus_assert (timeout != NULL);
112   _dbus_assert (timeout->refcount > 0);
113 
114   timeout->refcount -= 1;
115   if (timeout->refcount == 0)
116     {
117       dbus_timeout_set_data (timeout, NULL, NULL); /* call free_data_function */
118 
119       if (timeout->free_handler_data_function)
120 	(* timeout->free_handler_data_function) (timeout->handler_data);
121 
122       dbus_free (timeout);
123     }
124 }
125 
126 /**
127  * Change the timeout interval to be interval milliseconds from now
128  * (forgetting when the timeout was initially started), and enable it.
129  *
130  * This function is only valid when used in conjunction with DBusLoop:
131  * it can be used in the message bus daemon implementation or in unit tests,
132  * but it cannot be used in conjunction with an application main loop.
133  *
134  * @param timeout the timeout
135  * @param interval the new interval
136  */
137 void
_dbus_timeout_restart(DBusTimeout * timeout,int interval)138 _dbus_timeout_restart (DBusTimeout *timeout,
139                        int          interval)
140 {
141   _dbus_assert (interval >= 0);
142 
143   timeout->interval = interval;
144   timeout->enabled = TRUE;
145   timeout->needs_restart = TRUE;
146 }
147 
148 /**
149  * Disable the timeout. Note that you should use
150  * _dbus_connection_toggle_timeout_unlocked() etc. instead, if
151  * the timeout is passed out to an application main loop.
152  * i.e. you can't use this function in the D-Bus library, it's
153  * only used in the message bus daemon implementation.
154  *
155  * @param timeout the timeout
156  * @param enabled #TRUE if timeout should be enabled.
157  */
158 void
_dbus_timeout_disable(DBusTimeout * timeout)159 _dbus_timeout_disable (DBusTimeout  *timeout)
160 {
161   timeout->enabled = FALSE;
162 }
163 
164 /**
165  * @typedef DBusTimeoutList
166  *
167  * Opaque data type representing a list of timeouts
168  * and a set of DBusAddTimeoutFunction/DBusRemoveTimeoutFunction.
169  * Automatically handles removing/re-adding timeouts
170  * when the DBusAddTimeoutFunction is updated or changed.
171  * Holds a reference count to each timeout.
172  *
173  */
174 
175 /**
176  * DBusTimeoutList implementation details. All fields
177  * are private.
178  *
179  */
180 struct DBusTimeoutList
181 {
182   DBusList *timeouts; /**< Timeout objects. */
183 
184   DBusAddTimeoutFunction add_timeout_function;       /**< Callback for adding a timeout. */
185   DBusRemoveTimeoutFunction remove_timeout_function; /**< Callback for removing a timeout. */
186   DBusTimeoutToggledFunction timeout_toggled_function; /**< Callback when timeout is enabled/disabled or changes interval */
187   void *timeout_data;                                /**< Data for timeout callbacks */
188   DBusFreeFunction timeout_free_data_function;       /**< Free function for timeout callback data */
189 };
190 
191 /**
192  * Creates a new timeout list. Returns #NULL if insufficient
193  * memory exists.
194  *
195  * @returns the new timeout list, or #NULL on failure.
196  */
197 DBusTimeoutList*
_dbus_timeout_list_new(void)198 _dbus_timeout_list_new (void)
199 {
200   DBusTimeoutList *timeout_list;
201 
202   timeout_list = dbus_new0 (DBusTimeoutList, 1);
203   if (timeout_list == NULL)
204     return NULL;
205 
206   return timeout_list;
207 }
208 
209 /**
210  * Frees a DBusTimeoutList.
211  *
212  * @param timeout_list the timeout list.
213  */
214 void
_dbus_timeout_list_free(DBusTimeoutList * timeout_list)215 _dbus_timeout_list_free (DBusTimeoutList *timeout_list)
216 {
217   /* free timeout_data and remove timeouts as a side effect */
218   _dbus_timeout_list_set_functions (timeout_list,
219 				    NULL, NULL, NULL, NULL, NULL);
220 
221   _dbus_list_foreach (&timeout_list->timeouts,
222 		      (DBusForeachFunction) _dbus_timeout_unref,
223 		      NULL);
224   _dbus_list_clear (&timeout_list->timeouts);
225 
226   dbus_free (timeout_list);
227 }
228 
229 /**
230  * Sets the timeout functions. This function is the "backend"
231  * for dbus_connection_set_timeout_functions().
232  *
233  * @param timeout_list the timeout list
234  * @param add_function the add timeout function.
235  * @param remove_function the remove timeout function.
236  * @param toggled_function toggle notify function, or #NULL
237  * @param data the data for those functions.
238  * @param free_data_function the function to free the data.
239  * @returns #FALSE if no memory
240  *
241  */
242 dbus_bool_t
_dbus_timeout_list_set_functions(DBusTimeoutList * timeout_list,DBusAddTimeoutFunction add_function,DBusRemoveTimeoutFunction remove_function,DBusTimeoutToggledFunction toggled_function,void * data,DBusFreeFunction free_data_function)243 _dbus_timeout_list_set_functions (DBusTimeoutList           *timeout_list,
244 				  DBusAddTimeoutFunction     add_function,
245 				  DBusRemoveTimeoutFunction  remove_function,
246                                   DBusTimeoutToggledFunction toggled_function,
247 				  void                      *data,
248 				  DBusFreeFunction           free_data_function)
249 {
250   /* Add timeouts with the new function, failing on OOM */
251   if (add_function != NULL)
252     {
253       DBusList *link;
254 
255       link = _dbus_list_get_first_link (&timeout_list->timeouts);
256       while (link != NULL)
257         {
258           DBusList *next = _dbus_list_get_next_link (&timeout_list->timeouts,
259                                                      link);
260 
261           if (!(* add_function) (link->data, data))
262             {
263               /* remove it all again and return FALSE */
264               DBusList *link2;
265 
266               link2 = _dbus_list_get_first_link (&timeout_list->timeouts);
267               while (link2 != link)
268                 {
269                   DBusList *next2 = _dbus_list_get_next_link (&timeout_list->timeouts,
270                                                               link2);
271 
272                   (* remove_function) (link2->data, data);
273 
274                   link2 = next2;
275                 }
276 
277               return FALSE;
278             }
279 
280           link = next;
281         }
282     }
283 
284   /* Remove all current timeouts from previous timeout handlers */
285 
286   if (timeout_list->remove_timeout_function != NULL)
287     {
288       _dbus_list_foreach (&timeout_list->timeouts,
289 			  (DBusForeachFunction) timeout_list->remove_timeout_function,
290 			  timeout_list->timeout_data);
291     }
292 
293   if (timeout_list->timeout_free_data_function != NULL)
294     (* timeout_list->timeout_free_data_function) (timeout_list->timeout_data);
295 
296   timeout_list->add_timeout_function = add_function;
297   timeout_list->remove_timeout_function = remove_function;
298   timeout_list->timeout_toggled_function = toggled_function;
299   timeout_list->timeout_data = data;
300   timeout_list->timeout_free_data_function = free_data_function;
301 
302   return TRUE;
303 }
304 
305 /**
306  * Adds a new timeout to the timeout list, invoking the
307  * application DBusAddTimeoutFunction if appropriate.
308  *
309  * @param timeout_list the timeout list.
310  * @param timeout the timeout to add.
311  * @returns #TRUE on success, #FALSE If no memory.
312  */
313 dbus_bool_t
_dbus_timeout_list_add_timeout(DBusTimeoutList * timeout_list,DBusTimeout * timeout)314 _dbus_timeout_list_add_timeout (DBusTimeoutList *timeout_list,
315 				DBusTimeout     *timeout)
316 {
317   if (!_dbus_list_append (&timeout_list->timeouts, timeout))
318     return FALSE;
319 
320   _dbus_timeout_ref (timeout);
321 
322   if (timeout_list->add_timeout_function != NULL)
323     {
324       if (!(* timeout_list->add_timeout_function) (timeout,
325                                                    timeout_list->timeout_data))
326         {
327           _dbus_list_remove_last (&timeout_list->timeouts, timeout);
328           _dbus_timeout_unref (timeout);
329           return FALSE;
330         }
331     }
332 
333   return TRUE;
334 }
335 
336 /**
337  * Removes a timeout from the timeout list, invoking the
338  * application's DBusRemoveTimeoutFunction if appropriate.
339  *
340  * @param timeout_list the timeout list.
341  * @param timeout the timeout to remove.
342  */
343 void
_dbus_timeout_list_remove_timeout(DBusTimeoutList * timeout_list,DBusTimeout * timeout)344 _dbus_timeout_list_remove_timeout (DBusTimeoutList *timeout_list,
345 				   DBusTimeout     *timeout)
346 {
347   if (!_dbus_list_remove (&timeout_list->timeouts, timeout))
348     _dbus_assert_not_reached ("Nonexistent timeout was removed");
349 
350   if (timeout_list->remove_timeout_function != NULL)
351     (* timeout_list->remove_timeout_function) (timeout,
352 					       timeout_list->timeout_data);
353 
354   _dbus_timeout_unref (timeout);
355 }
356 
357 /**
358  * Sets a timeout to the given enabled state, invoking the
359  * application's DBusTimeoutToggledFunction if appropriate.
360  *
361  * @param timeout_list the timeout list.
362  * @param timeout the timeout to toggle.
363  * @param enabled #TRUE to enable
364  */
365 void
_dbus_timeout_list_toggle_timeout(DBusTimeoutList * timeout_list,DBusTimeout * timeout,dbus_bool_t enabled)366 _dbus_timeout_list_toggle_timeout (DBusTimeoutList           *timeout_list,
367                                    DBusTimeout               *timeout,
368                                    dbus_bool_t                enabled)
369 {
370   enabled = !!enabled;
371 
372   if (enabled == timeout->enabled)
373     return;
374 
375   timeout->enabled = enabled;
376 
377   if (timeout_list->timeout_toggled_function != NULL)
378     (* timeout_list->timeout_toggled_function) (timeout,
379                                                 timeout_list->timeout_data);
380 }
381 
382 /**
383  * Returns whether a timeout needs restart time counting in the event loop.
384  *
385  * @param timeout the DBusTimeout object
386  * @returns #TRUE if restart is needed
387  */
388 dbus_bool_t
_dbus_timeout_needs_restart(DBusTimeout * timeout)389 _dbus_timeout_needs_restart (DBusTimeout *timeout)
390 {
391   return timeout->needs_restart;
392 }
393 
394 /**
395  * Mark timeout as restarted (setting timestamps is responsibility of the event
396  * loop).
397  *
398  * @param timeout the DBusTimeout object
399  */
400 void
_dbus_timeout_restarted(DBusTimeout * timeout)401 _dbus_timeout_restarted (DBusTimeout *timeout)
402 {
403   timeout->needs_restart = FALSE;
404 }
405 
406 /** @} */
407 
408 /**
409  * @defgroup DBusTimeout DBusTimeout
410  * @ingroup  DBus
411  * @brief Object representing a timeout
412  *
413  * Types and functions related to DBusTimeout. A timeout
414  * represents a timeout that the main loop needs to monitor,
415  * as in Qt's QTimer or GLib's g_timeout_add().
416  *
417  * Use dbus_connection_set_timeout_functions() or dbus_server_set_timeout_functions()
418  * to be notified when libdbus needs to add or remove timeouts.
419  *
420  * @{
421  */
422 
423 
424 /**
425  * @typedef DBusTimeout
426  *
427  * Opaque object representing a timeout.
428  */
429 
430 /**
431  * Gets the timeout interval. The dbus_timeout_handle()
432  * should be called each time this interval elapses,
433  * starting after it elapses once.
434  *
435  * The interval may change during the life of the
436  * timeout; if so, the timeout will be disabled and
437  * re-enabled (calling the "timeout toggled function")
438  * to notify you of the change.
439  *
440  * @param timeout the DBusTimeout object.
441  * @returns the interval in milliseconds.
442  */
443 int
dbus_timeout_get_interval(DBusTimeout * timeout)444 dbus_timeout_get_interval (DBusTimeout *timeout)
445 {
446   return timeout->interval;
447 }
448 
449 /**
450  * Gets data previously set with dbus_timeout_set_data()
451  * or #NULL if none.
452  *
453  * @param timeout the DBusTimeout object.
454  * @returns previously-set data.
455  */
456 void*
dbus_timeout_get_data(DBusTimeout * timeout)457 dbus_timeout_get_data (DBusTimeout *timeout)
458 {
459   return timeout->data;
460 }
461 
462 /**
463  * Sets data which can be retrieved with dbus_timeout_get_data().
464  * Intended for use by the DBusAddTimeoutFunction and
465  * DBusRemoveTimeoutFunction to store their own data.  For example with
466  * Qt you might store the QTimer for this timeout and with GLib
467  * you might store a g_timeout_add result id.
468  *
469  * @param timeout the DBusTimeout object.
470  * @param data the data.
471  * @param free_data_function function to be called to free the data.
472  */
473 void
dbus_timeout_set_data(DBusTimeout * timeout,void * data,DBusFreeFunction free_data_function)474 dbus_timeout_set_data (DBusTimeout      *timeout,
475 		       void             *data,
476 		       DBusFreeFunction  free_data_function)
477 {
478   if (timeout->free_data_function != NULL)
479     (* timeout->free_data_function) (timeout->data);
480 
481   timeout->data = data;
482   timeout->free_data_function = free_data_function;
483 }
484 
485 /**
486  * Calls the timeout handler for this timeout.
487  * This function should be called when the timeout
488  * occurs.
489  *
490  * If this function returns #FALSE, then there wasn't
491  * enough memory to handle the timeout. Typically just
492  * letting the timeout fire again next time it naturally
493  * times out is an adequate response to that problem,
494  * but you could try to do more if you wanted.
495  *
496  * @param timeout the DBusTimeout object.
497  * @returns #FALSE if there wasn't enough memory
498  */
499 dbus_bool_t
dbus_timeout_handle(DBusTimeout * timeout)500 dbus_timeout_handle (DBusTimeout *timeout)
501 {
502   return (* timeout->handler) (timeout->handler_data);
503 }
504 
505 
506 /**
507  * Returns whether a timeout is enabled or not. If not
508  * enabled, it should not be polled by the main loop.
509  *
510  * @param timeout the DBusTimeout object
511  * @returns #TRUE if the timeout is enabled
512  */
513 dbus_bool_t
dbus_timeout_get_enabled(DBusTimeout * timeout)514 dbus_timeout_get_enabled (DBusTimeout *timeout)
515 {
516   return timeout->enabled;
517 }
518 
519 /** @} end public API docs */
520