1 /* -*- Mode: C; indent-tabs-mode: t; c-basic-offset: 8; tab-width: 8 -*- */
2 /* gnome-vfs-monitor.c - File Monitoring for the GNOME Virtual File System.
4 Copyright (C) 2001 Ian McKellar
6 The Gnome Library is free software; you can redistribute it and/or
7 modify it under the terms of the GNU Library General Public License as
8 published by the Free Software Foundation; either version 2 of the
9 License, or (at your option) any later version.
11 The Gnome Library is distributed in the hope that it will be useful,
12 but WITHOUT ANY WARRANTY; without even the implied warranty of
13 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
14 Library General Public License for more details.
16 You should have received a copy of the GNU Library General Public
17 License along with the Gnome Library; see the file COPYING.LIB. If not,
18 write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330,
19 Boston, MA 02111-1307, USA.
21 Author: Ian McKellar <yakk@yakk.net>
26 #include <libgnomevfs/gnome-vfs-monitor.h>
27 #include <libgnomevfs/gnome-vfs-monitor-private.h>
28 #include <libgnomevfs/gnome-vfs-method.h>
32 CALLBACK_STATE_NOT_SENT,
33 CALLBACK_STATE_SENDING,
38 struct GnomeVFSMonitorHandle {
39 GnomeVFSURI *uri; /* the URI being monitored */
40 GnomeVFSMethodHandle *method_handle;
41 GnomeVFSMonitorType type;
42 GnomeVFSMonitorCallback callback;
43 gpointer user_data; /* FIXME - how does this get freed */
47 GList *pending_callbacks; /* protected by handle_hash */
48 guint pending_timeout; /* protected by handle_hash */
49 guint timeout_count; /* count up each time pending_timeout is changed
50 to avoid timeout remove race.
51 protected by handle_hash */
54 struct GnomeVFSMonitorCallbackData {
56 GnomeVFSMonitorEventType event_type;
57 CallbackState send_state;
61 /* Number of seconds between consecutive events of the same type to the same file */
62 #define CONSECUTIVE_CALLBACK_DELAY 2
64 typedef struct GnomeVFSMonitorCallbackData GnomeVFSMonitorCallbackData;
66 /* This hash maps the module-supplied handle pointer to our own MonitrHandle */
67 static GHashTable *handle_hash = NULL;
68 G_LOCK_DEFINE_STATIC (handle_hash);
71 init_hash_table (void)
75 if (handle_hash == NULL) {
76 handle_hash = g_hash_table_new (g_direct_hash, g_direct_equal);
79 G_UNLOCK (handle_hash);
83 free_callback_data (GnomeVFSMonitorCallbackData *callback_data)
85 g_free (callback_data->info_uri);
86 g_free (callback_data);
90 _gnome_vfs_monitor_do_add (GnomeVFSMethod *method,
91 GnomeVFSMonitorHandle **handle,
93 GnomeVFSMonitorType monitor_type,
94 GnomeVFSMonitorCallback callback,
97 GnomeVFSResult result;
98 GnomeVFSMonitorHandle *monitor_handle =
99 g_new0(GnomeVFSMonitorHandle, 1);
102 gnome_vfs_uri_ref (uri);
103 monitor_handle->uri = uri;
105 monitor_handle->type = monitor_type;
106 monitor_handle->callback = callback;
107 monitor_handle->user_data = user_data;
109 result = uri->method->monitor_add (uri->method,
110 &monitor_handle->method_handle, uri, monitor_type);
112 if (result != GNOME_VFS_OK) {
113 gnome_vfs_uri_unref (uri);
114 g_free (monitor_handle);
115 monitor_handle = NULL;
117 G_LOCK (handle_hash);
118 g_hash_table_insert (handle_hash,
119 monitor_handle->method_handle,
121 G_UNLOCK (handle_hash);
124 *handle = monitor_handle;
129 /* Called with handle_hash lock held */
131 no_live_callbacks (GnomeVFSMonitorHandle *monitor_handle)
134 GnomeVFSMonitorCallbackData *callback_data;
136 l = monitor_handle->pending_callbacks;
138 callback_data = l->data;
140 if (callback_data->send_state == CALLBACK_STATE_NOT_SENT ||
141 callback_data->send_state == CALLBACK_STATE_SENDING) {
150 /* Called with handle_hash lock held */
152 destroy_monitor_handle (GnomeVFSMonitorHandle *handle)
156 g_assert (no_live_callbacks (handle));
158 g_list_foreach (handle->pending_callbacks, (GFunc) free_callback_data, NULL);
159 g_list_free (handle->pending_callbacks);
160 handle->pending_callbacks = NULL;
162 res = g_hash_table_remove (handle_hash, handle->method_handle);
164 g_warning ("gnome-vfs-monitor.c: A monitor handle was destroyed "
165 "before it was added to the method hash table. This "
166 "is a bug in the application and can cause crashed. "
167 "It is probably a race-condition.");
170 gnome_vfs_uri_unref (handle->uri);
175 _gnome_vfs_monitor_do_cancel (GnomeVFSMonitorHandle *handle)
177 GnomeVFSResult result;
181 if (!VFS_METHOD_HAS_FUNC(handle->uri->method, monitor_cancel)) {
182 return GNOME_VFS_ERROR_NOT_SUPPORTED;
185 result = handle->uri->method->monitor_cancel (handle->uri->method,
186 handle->method_handle);
188 if (result == GNOME_VFS_OK) {
189 /* mark this monitor as cancelled */
190 handle->cancelled = TRUE;
192 /* destroy the handle if there are no outstanding callbacks */
193 G_LOCK (handle_hash);
194 if (no_live_callbacks (handle)) {
195 destroy_monitor_handle (handle);
197 G_UNLOCK (handle_hash);
206 GnomeVFSMonitorHandle *monitor_handle;
210 actually_dispatch_callback (gpointer data)
212 DispatchData *ddata = data;
213 GnomeVFSMonitorHandle *monitor_handle = ddata->monitor_handle;
214 GnomeVFSMonitorCallbackData *callback_data;
221 /* This function runs on the main loop, so it won't reenter,
222 * although other threads may add stuff to the pending queue
223 * while we don't have the lock
226 gettimeofday (&tv, NULL);
229 G_LOCK (handle_hash);
231 /* Don't clear pending_timeout if we started another timeout
234 if (monitor_handle->timeout_count == ddata->timeout_count) {
235 monitor_handle->pending_timeout = 0;
238 if (!monitor_handle->cancelled) {
239 /* Find all callbacks that needs to be dispatched */
241 l = monitor_handle->pending_callbacks;
243 callback_data = l->data;
245 g_assert (callback_data->send_state != CALLBACK_STATE_SENDING);
247 if (callback_data->send_state == CALLBACK_STATE_NOT_SENT &&
248 callback_data->send_at <= now) {
249 callback_data->send_state = CALLBACK_STATE_SENDING;
250 dispatch = g_list_prepend (dispatch, callback_data);
256 dispatch = g_list_reverse (dispatch);
258 G_UNLOCK (handle_hash);
262 callback_data = l->data;
264 uri = gnome_vfs_uri_to_string
265 (monitor_handle->uri,
266 GNOME_VFS_URI_HIDE_NONE);
269 /* actually run app code */
270 monitor_handle->callback (monitor_handle, uri,
271 callback_data->info_uri,
272 callback_data->event_type,
273 monitor_handle->user_data);
276 callback_data->send_state = CALLBACK_STATE_SENT;
281 g_list_free (dispatch);
283 G_LOCK (handle_hash);
285 l = monitor_handle->pending_callbacks;
287 callback_data = l->data;
290 g_assert (callback_data->send_state != CALLBACK_STATE_SENDING);
292 /* If we've sent the event, and its not affecting coming events, free it */
293 if (callback_data->send_state == CALLBACK_STATE_SENT &&
294 callback_data->send_at + CONSECUTIVE_CALLBACK_DELAY <= now) {
295 /* free the callback_data */
296 free_callback_data (callback_data);
298 monitor_handle->pending_callbacks =
299 g_list_delete_link (monitor_handle->pending_callbacks,
308 /* if we were waiting for this callback to be dispatched to free
309 * this monitor, then do it now.
311 if (monitor_handle->cancelled &&
312 no_live_callbacks (monitor_handle)) {
313 destroy_monitor_handle (monitor_handle);
316 G_UNLOCK (handle_hash);
321 /* Called with handle_hash lock held */
323 send_uri_changes_now (GnomeVFSMonitorHandle *monitor_handle,
328 GnomeVFSMonitorCallbackData *callback_data;
330 l = monitor_handle->pending_callbacks;
332 callback_data = l->data;
333 if (strcmp (callback_data->info_uri, uri) == 0) {
334 callback_data->send_at = now;
340 /* Called with handle_hash lock held */
342 get_min_delay (GList *list, gint32 now)
345 GnomeVFSMonitorCallbackData *callback_data;
347 min_send_at = G_MAXINT;
349 while (list != NULL) {
350 callback_data = list->data;
352 if (callback_data->send_state == CALLBACK_STATE_NOT_SENT) {
353 min_send_at = MIN (min_send_at, callback_data->send_at);
359 if (min_send_at < now) {
362 return min_send_at - now;
367 /* for modules to send callbacks to the app */
369 gnome_vfs_monitor_callback (GnomeVFSMethodHandle *method_handle,
370 GnomeVFSURI *info_uri, /* GList of uris */
371 GnomeVFSMonitorEventType event_type)
373 GnomeVFSMonitorCallbackData *callback_data, *other_data, *last_data;
374 GnomeVFSMonitorHandle *monitor_handle;
382 g_return_if_fail (info_uri != NULL);
386 /* We need to loop here, because there is a race after we add the
387 * handle and when we add it to the hash table.
390 G_LOCK (handle_hash);
391 monitor_handle = g_hash_table_lookup (handle_hash, method_handle);
392 if (monitor_handle == NULL) {
393 G_UNLOCK (handle_hash);
395 } while (monitor_handle == NULL);
397 if (monitor_handle->cancelled) {
398 G_UNLOCK (handle_hash);
402 gettimeofday (&tv, NULL);
405 uri = gnome_vfs_uri_to_string (info_uri, GNOME_VFS_URI_HIDE_NONE);
408 l = monitor_handle->pending_callbacks;
410 other_data = l->data;
411 if (strcmp (other_data->info_uri, uri) == 0) {
417 if (last_data == NULL ||
418 (last_data->event_type != event_type ||
419 last_data->send_state == CALLBACK_STATE_SENT)) {
420 callback_data = g_new0 (GnomeVFSMonitorCallbackData, 1);
421 callback_data->info_uri = g_strdup (uri);
422 callback_data->event_type = event_type;
423 callback_data->send_state = CALLBACK_STATE_NOT_SENT;
424 if (last_data == NULL) {
425 callback_data->send_at = now;
427 if (last_data->event_type != event_type) {
428 /* New type, flush old events */
429 send_uri_changes_now (monitor_handle, uri, now);
430 callback_data->send_at = now;
432 callback_data->send_at = last_data->send_at + CONSECUTIVE_CALLBACK_DELAY;
436 monitor_handle->pending_callbacks =
437 g_list_append(monitor_handle->pending_callbacks, callback_data);
439 delay = get_min_delay (monitor_handle->pending_callbacks, now);
441 if (monitor_handle->pending_timeout) {
442 g_source_remove (monitor_handle->pending_timeout);
445 ddata = g_new (DispatchData, 1);
446 ddata->monitor_handle = monitor_handle;
447 ddata->timeout_count = ++monitor_handle->timeout_count;
450 monitor_handle->pending_timeout = g_idle_add_full (G_PRIORITY_DEFAULT_IDLE,
451 actually_dispatch_callback,
452 ddata, (GDestroyNotify)g_free);
454 monitor_handle->pending_timeout = g_timeout_add_full (G_PRIORITY_DEFAULT,
456 actually_dispatch_callback,
457 ddata, (GDestroyNotify)g_free);
463 G_UNLOCK (handle_hash);