2006-05-27 James Livingston <doclivingston@gmail.com>
[rhythmbox.git] / rhythmdb / rhythmdb-monitor.c
blob6fab98662f4544937afa5c993f3e024583e87c52
1 /* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*-
3 * Copyright (C) 2003,2004 Colin Walters <walters@gnome.org>
5 * This program is free software; you can redistribute it and/or modify
6 * it under the terms of the GNU General Public License as published by
7 * the Free Software Foundation; either version 2 of the License, or
8 * (at your option) any later version.
10 * This program is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 * GNU General Public License for more details.
15 * You should have received a copy of the GNU General Public License
16 * along with this program; if not, write to the Free Software
17 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
21 #include <config.h>
23 #include <string.h>
25 #include <glib.h>
26 #include <glib-object.h>
27 #include <glib/gi18n.h>
28 #include <gconf/gconf-client.h>
29 #include <libgnomevfs/gnome-vfs-volume-monitor.h>
31 #include "rb-debug.h"
32 #include "rhythmdb.h"
33 #include "rhythmdb-private.h"
34 #include "rb-file-helpers.h"
35 #include "rb-preferences.h"
36 #include "eel-gconf-extensions.h"
39 #define RHYTHMDB_FILE_MODIFY_PROCESS_TIME 2
41 static void rhythmdb_volume_mounted_cb (GnomeVFSVolumeMonitor *monitor,
42 GnomeVFSVolume *volume,
43 gpointer data);
44 static void rhythmdb_volume_unmounted_cb (GnomeVFSVolumeMonitor *monitor,
45 GnomeVFSVolume *volume,
46 gpointer data);
48 void
49 rhythmdb_init_monitoring (RhythmDB *db)
51 db->priv->monitored_directories = g_hash_table_new_full (g_str_hash, g_str_equal,
52 (GDestroyNotify) g_free,
53 NULL);
55 db->priv->changed_files = g_hash_table_new_full (g_str_hash, g_str_equal,
56 (GDestroyNotify) g_free,
57 NULL);
59 g_signal_connect (G_OBJECT (gnome_vfs_get_volume_monitor ()),
60 "volume-mounted",
61 G_CALLBACK (rhythmdb_volume_mounted_cb),
62 db);
64 g_signal_connect (G_OBJECT (gnome_vfs_get_volume_monitor ()),
65 "volume-pre-unmount",
66 G_CALLBACK (rhythmdb_volume_unmounted_cb),
67 db);
68 g_signal_connect (G_OBJECT (gnome_vfs_get_volume_monitor ()),
69 "volume-unmounted",
70 G_CALLBACK (rhythmdb_volume_unmounted_cb),
71 db);
74 void
75 rhythmdb_finalize_monitoring (RhythmDB *db)
77 rhythmdb_stop_monitoring (db);
79 g_hash_table_destroy (db->priv->monitored_directories);
80 g_source_remove (db->priv->changed_files_id);
81 g_hash_table_destroy (db->priv->changed_files);
84 static gboolean
85 rhythmdb_unmonitor_directories (char *dir, GnomeVFSMonitorHandle *handle, RhythmDB *db)
87 gnome_vfs_monitor_cancel (handle);
88 return TRUE;
91 void
92 rhythmdb_stop_monitoring (RhythmDB *db)
94 g_hash_table_foreach_remove (db->priv->monitored_directories,
95 (GHRFunc) rhythmdb_unmonitor_directories,
96 db);
99 static void
100 monitor_entry_file (RhythmDBEntry *entry, RhythmDB *db)
102 GError *error = NULL;
104 if (entry->type == RHYTHMDB_ENTRY_TYPE_SONG) {
105 rhythmdb_monitor_uri_path (db, entry->location, &error);
108 if (error) {
109 /* FIXME: should we complain to the user? */
110 rb_debug ("error while attempting to monitor library track: %s", error->message);
114 static void
115 monitor_subdirectory (const char *uri, RhythmDB *db)
117 GError *error = NULL;
119 if (!rb_uri_is_directory (uri))
120 return;
122 rhythmdb_monitor_uri_path (db, uri, &error);
124 if (error) {
125 /* FIXME: should we complain to the user? */
126 rb_debug ("error while attempting to monitor the library directory: %s", error->message);
130 static void
131 monitor_library_directory (const char *uri, RhythmDB *db)
133 GError *error = NULL;
135 if ((strcmp (uri, "file:///") == 0) ||
136 (strcmp (uri, "file://") == 0)) {
137 /* display an error to the user? */
138 return;
141 rb_debug ("beginning monitor of the library directory %s", uri);
142 rhythmdb_monitor_uri_path (db, uri, &error);
143 rb_uri_handle_recursively (uri, (GFunc) monitor_subdirectory, NULL, db);
145 if (error) {
146 /* FIXME: should we complain to the user? */
147 rb_debug ("error while attempting to monitor the library directory: %s", error->message);
150 rb_debug ("loading new tracks from library directory %s", uri);
151 rhythmdb_add_uri (db, uri);
154 static gboolean
155 rhythmdb_check_changed_file (const char *uri, gpointer data, RhythmDB *db)
157 GTimeVal time;
158 glong time_sec = GPOINTER_TO_INT (data);
160 g_get_current_time (&time);
161 if (time.tv_sec >= time_sec + RHYTHMDB_FILE_MODIFY_PROCESS_TIME) {
162 /* process and remove from table */
163 RhythmDBEvent *event = g_new0 (RhythmDBEvent, 1);
164 event->db = db;
165 event->type = RHYTHMDB_EVENT_FILE_CREATED_OR_MODIFIED;
166 event->uri = g_strdup (uri);
168 g_async_queue_push (db->priv->event_queue, event);
169 rb_debug ("adding newly located file %s", uri);
170 return TRUE;
173 rb_debug ("waiting to add newly located file %s", uri);
175 return FALSE;
178 static gboolean
179 rhythmdb_process_changed_files (RhythmDB *db)
181 g_hash_table_foreach_remove (db->priv->changed_files,
182 (GHRFunc)rhythmdb_check_changed_file, db);
183 return TRUE;
187 void
188 rhythmdb_start_monitoring (RhythmDB *db)
190 db->priv->changed_files_id = g_timeout_add (RHYTHMDB_FILE_MODIFY_PROCESS_TIME * 1000,
191 (GSourceFunc) rhythmdb_process_changed_files, db);
193 if (!db->priv->library_locations) {
194 g_slist_foreach (db->priv->library_locations, (GFunc) monitor_library_directory, db);
197 /* monitor every directory that contains a (TYPE_SONG) track */
198 rhythmdb_entry_foreach (db, (GFunc) monitor_entry_file, db);
201 static void
202 rhythmdb_directory_change_cb (GnomeVFSMonitorHandle *handle,
203 const char *monitor_uri,
204 const char *info_uri,
205 GnomeVFSMonitorEventType vfsevent,
206 RhythmDB *db)
208 rb_debug ("directory event %d for %s: %s", (int) vfsevent,
209 monitor_uri, info_uri);
211 switch (vfsevent) {
212 case GNOME_VFS_MONITOR_EVENT_CREATED:
214 GSList *cur;
215 gboolean in_library = FALSE;
217 if (!eel_gconf_get_boolean (CONF_MONITOR_LIBRARY))
218 return;
220 /* ignore new files outside of the library locations */
221 for (cur = db->priv->library_locations; cur != NULL; cur = g_slist_next (cur)) {
222 if (g_str_has_prefix (info_uri, cur->data)) {
223 in_library = TRUE;
224 break;
228 if (!in_library)
229 return;
232 /* process directories immediately */
233 if (rb_uri_is_directory (info_uri)) {
234 rhythmdb_monitor_uri_path (db, info_uri, NULL);
235 rhythmdb_add_uri (db, info_uri);
236 return;
238 /* fall through*/
239 case GNOME_VFS_MONITOR_EVENT_CHANGED:
240 case GNOME_VFS_MONITOR_EVENT_METADATA_CHANGED:
242 GTimeVal time;
244 g_get_current_time (&time);
245 g_hash_table_replace (db->priv->changed_files,
246 g_strdup (info_uri),
247 GINT_TO_POINTER (time.tv_sec));
249 break;
250 case GNOME_VFS_MONITOR_EVENT_DELETED:
252 RhythmDBEvent *event = g_new0 (RhythmDBEvent, 1);
253 event->db = db;
254 event->type = RHYTHMDB_EVENT_FILE_DELETED;
255 event->uri = g_strdup (info_uri);
256 g_async_queue_push (db->priv->event_queue, event);
258 break;
259 case GNOME_VFS_MONITOR_EVENT_STARTEXECUTING:
260 case GNOME_VFS_MONITOR_EVENT_STOPEXECUTING:
261 break;
265 void
266 rhythmdb_monitor_uri_path (RhythmDB *db, const char *uri, GError **error)
268 char *directory;
269 GnomeVFSResult vfsresult;
270 GnomeVFSMonitorHandle **handle;
272 if (rb_uri_is_directory (uri)) {
273 if (g_str_has_suffix(uri, G_DIR_SEPARATOR_S)) {
274 directory = g_strdup (uri);
275 } else {
276 directory = g_strconcat (uri, G_DIR_SEPARATOR_S, NULL);
278 } else {
279 GnomeVFSURI *vfsuri, *parent;
281 vfsuri = gnome_vfs_uri_new (uri);
282 parent = gnome_vfs_uri_get_parent (vfsuri);
283 directory = gnome_vfs_uri_to_string (parent, GNOME_VFS_URI_HIDE_NONE);
284 gnome_vfs_uri_unref (vfsuri);
285 gnome_vfs_uri_unref (parent);
288 if (directory == NULL || g_hash_table_lookup (db->priv->monitored_directories, directory))
289 return;
291 handle = g_new0 (GnomeVFSMonitorHandle *, 1);
292 vfsresult = gnome_vfs_monitor_add (handle, directory,
293 GNOME_VFS_MONITOR_DIRECTORY,
294 (GnomeVFSMonitorCallback) rhythmdb_directory_change_cb,
295 db);
296 if (vfsresult == GNOME_VFS_OK) {
297 rb_debug ("monitoring: %s", directory);
298 g_hash_table_insert (db->priv->monitored_directories,
299 directory, *handle);
300 } else {
301 g_set_error (error,
302 RHYTHMDB_ERROR,
303 RHYTHMDB_ERROR_ACCESS_FAILED,
304 _("Couldn't monitor %s: %s"),
305 directory,
306 gnome_vfs_result_to_string (vfsresult));
307 rb_debug ("failed to monitor %s", directory);
308 g_free (directory);
309 g_free (handle);
314 typedef struct
316 RhythmDB *db;
317 char *mount_point;
318 gboolean mounted;
319 } MountCtxt;
321 static void
322 entry_volume_mounted_or_unmounted (RhythmDBEntry *entry,
323 MountCtxt *ctxt)
325 const char *mount_point;
326 const char *location;
328 if (entry->type != RHYTHMDB_ENTRY_TYPE_SONG &&
329 entry->type != RHYTHMDB_ENTRY_TYPE_IMPORT_ERROR) {
330 return;
333 mount_point = rhythmdb_entry_get_string (entry, RHYTHMDB_PROP_MOUNTPOINT);
334 if (mount_point == NULL || strcmp (mount_point, ctxt->mount_point) != 0) {
335 return;
337 location = rhythmdb_entry_get_string (entry, RHYTHMDB_PROP_LOCATION);
339 if (entry->type == RHYTHMDB_ENTRY_TYPE_SONG) {
340 if (ctxt->mounted) {
341 rb_debug ("queueing stat for entry %s (mounted)", location);
343 /* make files visible immediately,
344 * then hide any that turn out to be missing.
346 rhythmdb_entry_set_visibility (ctxt->db, entry, TRUE);
347 queue_stat_uri (location,
348 ctxt->db,
349 RHYTHMDB_ENTRY_TYPE_SONG);
350 } else {
351 GTimeVal time;
352 GValue val = {0, };
354 rb_debug ("hiding entry %s (unmounted)", location);
356 g_get_current_time (&time);
357 g_value_init (&val, G_TYPE_ULONG);
358 g_value_set_ulong (&val, time.tv_sec);
359 rhythmdb_entry_set_internal (ctxt->db, entry, FALSE,
360 RHYTHMDB_PROP_LAST_SEEN, &val);
361 g_value_unset (&val);
363 rhythmdb_entry_set_visibility (ctxt->db, entry, FALSE);
365 } else if (entry->type == RHYTHMDB_ENTRY_TYPE_IMPORT_ERROR) {
366 /* delete import errors for files on unmounted volumes */
367 if (ctxt->mounted == FALSE) {
368 rb_debug ("removing import error for %s (unmounted)", location);
369 rhythmdb_entry_delete (ctxt->db, entry);
375 static void
376 rhythmdb_volume_mounted_cb (GnomeVFSVolumeMonitor *monitor,
377 GnomeVFSVolume *volume,
378 gpointer data)
380 MountCtxt ctxt;
382 ctxt.db = RHYTHMDB (data);
383 ctxt.mount_point = gnome_vfs_volume_get_activation_uri (volume);
384 ctxt.mounted = TRUE;
385 rhythmdb_entry_foreach (RHYTHMDB (data),
386 (GFunc)entry_volume_mounted_or_unmounted,
387 &ctxt);
388 rhythmdb_commit (RHYTHMDB (data));
389 g_free (ctxt.mount_point);
393 static void
394 rhythmdb_volume_unmounted_cb (GnomeVFSVolumeMonitor *monitor,
395 GnomeVFSVolume *volume,
396 gpointer data)
398 MountCtxt ctxt;
400 ctxt.db = RHYTHMDB (data);
401 ctxt.mount_point = gnome_vfs_volume_get_activation_uri (volume);
402 ctxt.mounted = FALSE;
403 rb_debug ("volume %s unmounted", ctxt.mount_point);
404 rhythmdb_entry_foreach (RHYTHMDB (data),
405 (GFunc)entry_volume_mounted_or_unmounted,
406 &ctxt);
407 rhythmdb_commit (RHYTHMDB (data));
408 g_free (ctxt.mount_point);