ftp://ftp.redhat.com/pub/redhat/linux/rawhide/SRPMS/SRPMS/gnome-vfs2-2.3.8-1.src.rpm
[gnome-vfs-httpcaptive.git] / libgnomevfs / gnome-vfs-monitor.c
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.
3
4    Copyright (C) 2001 Ian McKellar
5
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.
10
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.
15
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.
20
21    Author: Ian McKellar <yakk@yakk.net>
22 */
23
24 #include <sys/time.h>
25 #include <string.h>
26 #include <libgnomevfs/gnome-vfs-monitor.h>
27 #include <libgnomevfs/gnome-vfs-monitor-private.h>
28 #include <libgnomevfs/gnome-vfs-method.h>
29 #include <glib.h>
30
31 typedef enum {
32         CALLBACK_STATE_NOT_SENT,
33         CALLBACK_STATE_SENDING,
34         CALLBACK_STATE_SENT
35 } CallbackState;
36         
37
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 */
44
45         gboolean cancelled;
46         
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 */
52 };
53
54 struct GnomeVFSMonitorCallbackData {
55         char *info_uri;
56         GnomeVFSMonitorEventType event_type;
57         CallbackState send_state;
58         guint32 send_at;
59 };
60
61 /* Number of seconds between consecutive events of the same type to the same file */
62 #define CONSECUTIVE_CALLBACK_DELAY 2
63
64 typedef struct GnomeVFSMonitorCallbackData GnomeVFSMonitorCallbackData;
65
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);
69
70 static void 
71 init_hash_table (void)
72 {
73         G_LOCK (handle_hash);
74
75         if (handle_hash == NULL) {
76                 handle_hash = g_hash_table_new (g_direct_hash, g_direct_equal);
77         }
78
79         G_UNLOCK (handle_hash);
80 }
81
82 static void
83 free_callback_data (GnomeVFSMonitorCallbackData *callback_data)
84 {
85         g_free (callback_data->info_uri);
86         g_free (callback_data);
87 }
88
89 GnomeVFSResult
90 _gnome_vfs_monitor_do_add (GnomeVFSMethod *method,
91                           GnomeVFSMonitorHandle **handle,
92                           GnomeVFSURI *uri,
93                           GnomeVFSMonitorType monitor_type,
94                           GnomeVFSMonitorCallback callback,
95                           gpointer user_data)
96 {
97         GnomeVFSResult result;
98         GnomeVFSMonitorHandle *monitor_handle = 
99                 g_new0(GnomeVFSMonitorHandle, 1);
100
101         init_hash_table ();
102         gnome_vfs_uri_ref (uri);
103         monitor_handle->uri = uri;
104
105         monitor_handle->type = monitor_type;
106         monitor_handle->callback = callback;
107         monitor_handle->user_data = user_data;
108
109         result = uri->method->monitor_add (uri->method, 
110                         &monitor_handle->method_handle, uri, monitor_type);
111
112         if (result != GNOME_VFS_OK) {
113                 gnome_vfs_uri_unref (uri);
114                 g_free (monitor_handle);
115                 monitor_handle = NULL;
116         } else {
117                 G_LOCK (handle_hash);
118                 g_hash_table_insert (handle_hash, 
119                                      monitor_handle->method_handle,
120                                      monitor_handle);
121                 G_UNLOCK (handle_hash);
122         }
123
124         *handle = monitor_handle;
125
126         return result;
127 }
128
129 /* Called with handle_hash lock held */
130 static gboolean
131 no_live_callbacks (GnomeVFSMonitorHandle *monitor_handle)
132 {
133         GList *l;
134         GnomeVFSMonitorCallbackData *callback_data;
135         
136         l = monitor_handle->pending_callbacks;
137         while (l != NULL) {
138                 callback_data = l->data;
139
140                 if (callback_data->send_state == CALLBACK_STATE_NOT_SENT ||
141                     callback_data->send_state == CALLBACK_STATE_SENDING) {
142                         return FALSE;
143                 }
144                 
145                 l = l->next;
146         }
147         return TRUE;
148 }
149
150 /* Called with handle_hash lock held */
151 static void
152 destroy_monitor_handle (GnomeVFSMonitorHandle *handle)
153 {
154         gboolean res;
155
156         g_assert (no_live_callbacks (handle));
157         
158         g_list_foreach (handle->pending_callbacks, (GFunc) free_callback_data, NULL);
159         g_list_free (handle->pending_callbacks);
160         handle->pending_callbacks = NULL;
161         
162         res = g_hash_table_remove (handle_hash, handle->method_handle);
163         if (!res) {
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.");
168         }
169
170         gnome_vfs_uri_unref (handle->uri);
171         g_free (handle);
172 }
173
174 GnomeVFSResult 
175 _gnome_vfs_monitor_do_cancel (GnomeVFSMonitorHandle *handle)
176 {
177         GnomeVFSResult result;
178
179         init_hash_table ();
180
181         if (!VFS_METHOD_HAS_FUNC(handle->uri->method, monitor_cancel)) {
182                 return GNOME_VFS_ERROR_NOT_SUPPORTED;
183         }
184
185         result = handle->uri->method->monitor_cancel (handle->uri->method,
186                                                       handle->method_handle);
187
188         if (result == GNOME_VFS_OK) {
189                 /* mark this monitor as cancelled */
190                 handle->cancelled = TRUE;
191
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);
196                 }
197                 G_UNLOCK (handle_hash);
198         }
199
200         return result;
201 }
202
203
204 typedef struct {
205         guint timeout_count;
206         GnomeVFSMonitorHandle *monitor_handle;
207 } DispatchData;
208
209 static gint
210 actually_dispatch_callback (gpointer data)
211 {
212         DispatchData *ddata = data;
213         GnomeVFSMonitorHandle *monitor_handle = ddata->monitor_handle;
214         GnomeVFSMonitorCallbackData *callback_data;
215         gchar *uri;
216         GList *l, *next;
217         GList *dispatch;
218         struct timeval tv;
219         guint32 now;
220
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
224          */
225
226         gettimeofday (&tv, NULL);
227         now = tv.tv_sec;
228
229         G_LOCK (handle_hash);
230
231         /* Don't clear pending_timeout if we started another timeout
232          * (and removed this)
233          */
234         if (monitor_handle->timeout_count == ddata->timeout_count) {
235                 monitor_handle->pending_timeout = 0;
236         }
237
238         if (!monitor_handle->cancelled) {
239                 /* Find all callbacks that needs to be dispatched */
240                 dispatch = NULL;
241                 l = monitor_handle->pending_callbacks;
242                 while (l != NULL) {
243                         callback_data = l->data;
244                         
245                         g_assert (callback_data->send_state != CALLBACK_STATE_SENDING);
246
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);
251                         }
252
253                         l = l->next;
254                 }
255
256                 dispatch = g_list_reverse (dispatch);
257                 
258                 G_UNLOCK (handle_hash);
259                 
260                 l = dispatch;
261                 while (l != NULL) {
262                         callback_data = l->data;
263                         
264                         uri = gnome_vfs_uri_to_string 
265                                 (monitor_handle->uri, 
266                                  GNOME_VFS_URI_HIDE_NONE);
267
268
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);
274                         
275                         g_free (uri);
276                         callback_data->send_state = CALLBACK_STATE_SENT;
277
278                         l = l->next;
279                 }
280                         
281                 g_list_free (dispatch);
282                 
283                 G_LOCK (handle_hash);
284
285                 l = monitor_handle->pending_callbacks;
286                 while (l != NULL) {
287                         callback_data = l->data;
288                         next = l->next;
289                         
290                         g_assert (callback_data->send_state != CALLBACK_STATE_SENDING);
291
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);
297                                 
298                                 monitor_handle->pending_callbacks =
299                                         g_list_delete_link (monitor_handle->pending_callbacks,
300                                                             l);
301                         }
302
303                         l = next;
304                 }
305
306         }
307
308         /* if we were waiting for this callback to be dispatched to free
309          * this monitor, then do it now.
310          */
311         if (monitor_handle->cancelled &&
312             no_live_callbacks (monitor_handle)) {
313                 destroy_monitor_handle (monitor_handle);
314         }
315
316         G_UNLOCK (handle_hash);
317
318         return FALSE;
319 }
320
321 /* Called with handle_hash lock held */
322 static void
323 send_uri_changes_now (GnomeVFSMonitorHandle *monitor_handle,
324                       const char *uri,
325                       gint32 now)
326 {
327         GList *l;
328         GnomeVFSMonitorCallbackData *callback_data;
329         
330         l = monitor_handle->pending_callbacks;
331         while (l != NULL) {
332                 callback_data = l->data;
333                 if (strcmp (callback_data->info_uri, uri) == 0) {
334                         callback_data->send_at = now;
335                 }
336                 l = l->next;
337         }
338 }
339
340 /* Called with handle_hash lock held */
341 static guint32
342 get_min_delay  (GList *list, gint32 now)
343 {
344         guint32 min_send_at;
345         GnomeVFSMonitorCallbackData *callback_data;
346
347         min_send_at = G_MAXINT;
348
349         while (list != NULL) {
350                 callback_data = list->data;
351
352                 if (callback_data->send_state == CALLBACK_STATE_NOT_SENT) {
353                         min_send_at = MIN (min_send_at, callback_data->send_at);
354                 }
355
356                 list = list->next;
357         }
358
359         if (min_send_at < now) {
360                 return 0;
361         } else {
362                 return min_send_at - now;
363         }
364 }
365
366
367 /* for modules to send callbacks to the app */
368 void
369 gnome_vfs_monitor_callback (GnomeVFSMethodHandle *method_handle,
370                             GnomeVFSURI *info_uri, /* GList of uris */
371                             GnomeVFSMonitorEventType event_type)
372 {
373         GnomeVFSMonitorCallbackData *callback_data, *other_data, *last_data;
374         GnomeVFSMonitorHandle *monitor_handle;
375         char *uri;
376         struct timeval tv;
377         guint32 now;
378         guint32 delay;
379         GList *l;
380         DispatchData *ddata;
381         
382         g_return_if_fail (info_uri != NULL);
383
384         init_hash_table ();
385
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.
388          */
389         do  {
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);
394                 }
395         } while (monitor_handle == NULL);
396
397         if (monitor_handle->cancelled) {
398                 G_UNLOCK (handle_hash);
399                 return;
400         }
401         
402         gettimeofday (&tv, NULL);
403         now = tv.tv_sec;
404
405         uri = gnome_vfs_uri_to_string (info_uri, GNOME_VFS_URI_HIDE_NONE);
406
407         last_data = NULL;
408         l = monitor_handle->pending_callbacks;
409         while (l != NULL) {
410                 other_data = l->data;
411                 if (strcmp (other_data->info_uri, uri) == 0) {
412                         last_data = l->data;
413                 }
414                 l = l->next;
415         }
416
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;
426                 } else {
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;
431                         } else {
432                                 callback_data->send_at = last_data->send_at + CONSECUTIVE_CALLBACK_DELAY;
433                         }
434                 }
435                 
436                 monitor_handle->pending_callbacks = 
437                         g_list_append(monitor_handle->pending_callbacks, callback_data);
438                 
439                 delay = get_min_delay (monitor_handle->pending_callbacks, now);
440
441                 if (monitor_handle->pending_timeout) {
442                         g_source_remove (monitor_handle->pending_timeout);
443                 }
444                 
445                 ddata = g_new (DispatchData, 1);
446                 ddata->monitor_handle = monitor_handle;
447                 ddata->timeout_count = ++monitor_handle->timeout_count;
448                 
449                 if (delay == 0) {
450                         monitor_handle->pending_timeout = g_idle_add_full (G_PRIORITY_DEFAULT_IDLE,
451                                                                            actually_dispatch_callback,
452                                                                            ddata, (GDestroyNotify)g_free);
453                 } else {
454                         monitor_handle->pending_timeout = g_timeout_add_full (G_PRIORITY_DEFAULT,
455                                                                               delay * 1000,
456                                                                               actually_dispatch_callback,
457                                                                               ddata, (GDestroyNotify)g_free);
458                 }
459         }
460         
461         g_free (uri);
462         
463         G_UNLOCK (handle_hash);
464
465 }