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
* The Rhythmbox authors hereby grant permission for non-GPL compatible
11
* GStreamer plugins to be used and distributed together with GStreamer
12
* and Rhythmbox. This permission is above and beyond the permissions granted
13
* by the GPL license by which Rhythmbox is covered. If you modify this code
14
* you may extend this exception to your version of the code, but you are not
15
* obligated to do so. If you do not wish to do so, delete this exception
16
* statement from your version.
18
* This program is distributed in the hope that it will be useful,
19
* but WITHOUT ANY WARRANTY; without even the implied warranty of
20
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
21
* GNU General Public License for more details.
23
* You should have received a copy of the GNU General Public License
24
* along with this program; if not, write to the Free Software
25
* Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
34
#include <glib-object.h>
35
#include <glib/gi18n.h>
41
#include "rhythmdb-private.h"
42
#include "rhythmdb-query-result-list.h"
43
#include "rb-file-helpers.h"
45
#if !GLIB_CHECK_VERSION(2,24,0)
46
#define G_FILE_MONITOR_SEND_MOVED 0
49
#define RHYTHMDB_FILE_MODIFY_PROCESS_TIME 2
51
static void rhythmdb_directory_change_cb (GFileMonitor *monitor,
54
GFileMonitorEvent event_type,
56
static void rhythmdb_mount_added_cb (GVolumeMonitor *monitor,
59
static void rhythmdb_mount_removed_cb (GVolumeMonitor *monitor,
64
rhythmdb_init_monitoring (RhythmDB *db)
66
db->priv->monitor_mutex = g_mutex_new ();
68
db->priv->monitored_directories = g_hash_table_new_full (g_file_hash, (GEqualFunc) g_file_equal,
69
(GDestroyNotify) g_object_unref,
70
(GDestroyNotify)g_file_monitor_cancel);
72
db->priv->changed_files = g_hash_table_new_full (rb_refstring_hash, rb_refstring_equal,
73
(GDestroyNotify) rb_refstring_unref,
76
db->priv->volume_monitor = g_volume_monitor_get ();
77
g_signal_connect (G_OBJECT (db->priv->volume_monitor),
79
G_CALLBACK (rhythmdb_mount_added_cb),
82
g_signal_connect (G_OBJECT (db->priv->volume_monitor),
84
G_CALLBACK (rhythmdb_mount_removed_cb),
86
g_signal_connect (G_OBJECT (db->priv->volume_monitor),
88
G_CALLBACK (rhythmdb_mount_removed_cb),
93
rhythmdb_dispose_monitoring (RhythmDB *db)
95
if (db->priv->changed_files_id != 0) {
96
g_source_remove (db->priv->changed_files_id);
97
db->priv->changed_files_id = 0;
100
if (db->priv->volume_monitor != NULL) {
101
g_object_unref (db->priv->volume_monitor);
102
db->priv->volume_monitor = NULL;
107
rhythmdb_finalize_monitoring (RhythmDB *db)
109
rhythmdb_stop_monitoring (db);
111
g_hash_table_destroy (db->priv->monitored_directories);
112
g_hash_table_destroy (db->priv->changed_files);
114
g_mutex_free (db->priv->monitor_mutex);
118
rhythmdb_stop_monitoring (RhythmDB *db)
120
g_hash_table_foreach_remove (db->priv->monitored_directories,
121
(GHRFunc) rb_true_function,
126
actually_add_monitor (RhythmDB *db, GFile *directory, GError **error)
128
GFileMonitor *monitor;
130
if (directory == NULL) {
134
g_mutex_lock (db->priv->monitor_mutex);
136
if (g_hash_table_lookup (db->priv->monitored_directories, directory)) {
137
g_mutex_unlock (db->priv->monitor_mutex);
141
monitor = g_file_monitor_directory (directory, G_FILE_MONITOR_SEND_MOVED, db->priv->exiting, error);
142
if (monitor != NULL) {
143
g_signal_connect_object (G_OBJECT (monitor),
145
G_CALLBACK (rhythmdb_directory_change_cb),
147
g_hash_table_insert (db->priv->monitored_directories,
148
g_object_ref (directory),
152
g_mutex_unlock (db->priv->monitor_mutex);
156
monitor_entry_file (RhythmDBEntry *entry, RhythmDB *db)
158
if (entry->type == RHYTHMDB_ENTRY_TYPE_SONG) {
162
loc = rhythmdb_entry_get_string (entry, RHYTHMDB_PROP_LOCATION);
164
/* don't add add monitor if it's in the library path */
165
for (i = 0; db->priv->library_locations[i] != NULL; i++) {
166
if (g_str_has_prefix (loc, db->priv->library_locations[i]))
169
rhythmdb_monitor_uri_path (db, loc, NULL);
174
monitor_subdirectory (GFile *file, gboolean dir, RhythmDB *db)
178
uri = g_file_get_uri (file);
180
actually_add_monitor (db, file, NULL);
182
/* add the file to the database if it's not already there */
183
RhythmDBEntry *entry;
185
entry = rhythmdb_entry_lookup_by_location (db, uri);
187
rhythmdb_add_uri (db, uri);
195
monitor_library_directory (const char *uri, RhythmDB *db)
197
if ((strcmp (uri, "file:///") == 0) ||
198
(strcmp (uri, "file://") == 0)) {
199
/* display an error to the user? */
203
rb_debug ("beginning monitor of the library directory %s", uri);
204
rhythmdb_monitor_uri_path (db, uri, NULL);
205
rb_uri_handle_recursively_async (uri,
207
(RBUriRecurseFunc) monitor_subdirectory,
209
(GDestroyNotify)g_object_unref);
213
rhythmdb_check_changed_file (RBRefString *uri, gpointer data, RhythmDB *db)
216
glong time_sec = GPOINTER_TO_INT (data);
218
g_get_current_time (&time);
219
if (time.tv_sec >= time_sec + RHYTHMDB_FILE_MODIFY_PROCESS_TIME) {
220
rb_debug ("adding newly located file %s", rb_refstring_get (uri));
221
rhythmdb_add_uri (db, rb_refstring_get (uri));
225
rb_debug ("waiting to add newly located file %s", rb_refstring_get (uri));
231
rhythmdb_process_changed_files (RhythmDB *db)
234
* no need for a mutex around the changed files map as it's only accessed
235
* from the main thread. GFileMonitor's 'changed' signal is emitted from an
236
* idle handler, and we only process the map in a timeout callback.
238
if (g_hash_table_size (db->priv->changed_files) == 0) {
239
db->priv->changed_files_id = 0;
243
g_hash_table_foreach_remove (db->priv->changed_files,
244
(GHRFunc)rhythmdb_check_changed_file, db);
249
_monitor_entry_thread (RhythmDB *db)
251
rhythmdb_entry_foreach (db, (GFunc) monitor_entry_file, db);
252
g_object_unref (G_OBJECT (db));
257
rhythmdb_start_monitoring (RhythmDB *db)
259
g_thread_create ((GThreadFunc)_monitor_entry_thread, g_object_ref (db), FALSE, NULL);
261
/* monitor all library locations */
262
if (db->priv->library_locations) {
264
for (i = 0; db->priv->library_locations[i] != NULL; i++) {
265
monitor_library_directory (db->priv->library_locations[i], db);
271
add_changed_file (RhythmDB *db, const char *uri)
275
g_get_current_time (&time);
276
g_hash_table_replace (db->priv->changed_files,
277
rb_refstring_new (uri),
278
GINT_TO_POINTER (time.tv_sec));
279
if (db->priv->changed_files_id == 0) {
280
db->priv->changed_files_id =
281
g_timeout_add_seconds (RHYTHMDB_FILE_MODIFY_PROCESS_TIME,
282
(GSourceFunc) rhythmdb_process_changed_files,
288
rhythmdb_directory_change_cb (GFileMonitor *monitor,
291
GFileMonitorEvent event_type,
295
char *other_canon_uri = NULL;
296
RhythmDBEntry *entry;
298
canon_uri = g_file_get_uri (file);
299
if (other_file != NULL) {
300
other_canon_uri = g_file_get_uri (other_file);
303
rb_debug ("directory event %d for %s", event_type, canon_uri);
305
switch (event_type) {
306
case G_FILE_MONITOR_EVENT_CREATED:
308
gboolean in_library = FALSE;
311
if (!g_settings_get_boolean (db->priv->settings, "monitor-library"))
314
if (rb_uri_is_hidden (canon_uri))
317
/* ignore new files outside of the library locations */
318
for (i = 0; db->priv->library_locations[i] != NULL; i++) {
319
if (g_str_has_prefix (canon_uri, db->priv->library_locations[i])) {
329
/* process directories immediately */
330
if (rb_uri_is_directory (canon_uri)) {
331
actually_add_monitor (db, file, NULL);
332
rhythmdb_add_uri (db, canon_uri);
334
add_changed_file (db, canon_uri);
337
case G_FILE_MONITOR_EVENT_CHANGED:
338
case G_FILE_MONITOR_EVENT_ATTRIBUTE_CHANGED:
339
if (rhythmdb_entry_lookup_by_location (db, canon_uri)) {
340
add_changed_file (db, canon_uri);
343
case G_FILE_MONITOR_EVENT_CHANGES_DONE_HINT:
346
case G_FILE_MONITOR_EVENT_DELETED:
347
entry = rhythmdb_entry_lookup_by_location (db, canon_uri);
349
g_hash_table_remove (db->priv->changed_files, entry->location);
350
rhythmdb_entry_set_visibility (db, entry, FALSE);
351
rhythmdb_commit (db);
354
#if GLIB_CHECK_VERSION(2,24,0)
355
case G_FILE_MONITOR_EVENT_MOVED:
356
if (other_canon_uri == NULL) {
360
entry = rhythmdb_entry_lookup_by_location (db, other_canon_uri);
362
rb_debug ("file move target %s already exists in database", other_canon_uri);
363
entry = rhythmdb_entry_lookup_by_location (db, canon_uri);
365
g_hash_table_remove (db->priv->changed_files, entry->location);
366
rhythmdb_entry_set_visibility (db, entry, FALSE);
367
rhythmdb_commit (db);
370
entry = rhythmdb_entry_lookup_by_location (db, canon_uri);
373
g_value_init (&v, G_TYPE_STRING);
374
g_value_set_string (&v, other_canon_uri);
375
rhythmdb_entry_set_internal (db, entry, TRUE, RHYTHMDB_PROP_LOCATION, &v);
381
case G_FILE_MONITOR_EVENT_PRE_UNMOUNT:
382
case G_FILE_MONITOR_EVENT_UNMOUNTED:
388
g_free (other_canon_uri);
392
rhythmdb_monitor_uri_path (RhythmDB *db, const char *uri, GError **error)
396
if (rb_uri_is_directory (uri)) {
398
if (g_str_has_suffix(uri, G_DIR_SEPARATOR_S)) {
399
dir = g_strdup (uri);
401
dir = g_strconcat (uri, G_DIR_SEPARATOR_S, NULL);
404
directory = g_file_new_for_uri (dir);
409
file = g_file_new_for_uri (uri);
410
directory = g_file_get_parent (file);
411
g_object_unref (file);
414
actually_add_monitor (db, directory, error);
415
g_object_unref (directory);
419
rhythmdb_mount_added_cb (GVolumeMonitor *monitor,
424
RhythmDBQueryResultList *list;
428
root = g_mount_get_root (mount);
429
mountpoint = g_file_get_uri (root);
430
rb_debug ("volume %s mounted", mountpoint);
431
g_object_unref (root);
433
list = rhythmdb_query_result_list_new ();
434
rhythmdb_do_full_query (db,
435
RHYTHMDB_QUERY_RESULTS (list),
436
RHYTHMDB_QUERY_PROP_EQUALS,
438
RHYTHMDB_ENTRY_TYPE_SONG,
439
RHYTHMDB_QUERY_PROP_EQUALS,
440
RHYTHMDB_PROP_MOUNTPOINT,
443
l = rhythmdb_query_result_list_get_results (list);
444
rb_debug ("%d mounted entries to process", g_list_length (l));
445
for (; l != NULL; l = l->next) {
446
RhythmDBEntry *entry = l->data;
447
const char *location = rhythmdb_entry_get_string (entry, RHYTHMDB_PROP_LOCATION);
449
rhythmdb_entry_update_availability (entry, RHYTHMDB_ENTRY_AVAIL_MOUNTED);
450
if (rb_uri_is_local (location)) {
451
rhythmdb_add_uri_with_types (db,
453
RHYTHMDB_ENTRY_TYPE_SONG,
454
RHYTHMDB_ENTRY_TYPE_IGNORE,
455
RHYTHMDB_ENTRY_TYPE_IMPORT_ERROR);
458
g_object_unref (list);
460
rhythmdb_commit (db);
464
process_unmounted_entries (RhythmDB *db, RhythmDBEntryType *entry_type, const char *mountpoint)
466
RhythmDBQueryResultList *list;
469
list = rhythmdb_query_result_list_new ();
470
rhythmdb_do_full_query (db,
471
RHYTHMDB_QUERY_RESULTS (list),
472
RHYTHMDB_QUERY_PROP_EQUALS,
475
RHYTHMDB_QUERY_PROP_EQUALS,
476
RHYTHMDB_PROP_MOUNTPOINT,
479
l = rhythmdb_query_result_list_get_results (list);
480
rb_debug ("%d unmounted entries to process", g_list_length (l));
481
for (; l != NULL; l = l->next) {
482
RhythmDBEntry *entry = l->data;
483
rhythmdb_entry_update_availability (entry, RHYTHMDB_ENTRY_AVAIL_UNMOUNTED);
485
g_object_unref (list);
486
rhythmdb_commit (db);
490
rhythmdb_mount_removed_cb (GVolumeMonitor *monitor,
497
root = g_mount_get_root (mount);
498
mountpoint = g_file_get_uri (root);
499
rb_debug ("volume %s unmounted", mountpoint);
500
g_object_unref (root);
502
process_unmounted_entries (db, RHYTHMDB_ENTRY_TYPE_SONG, mountpoint);
503
process_unmounted_entries (db, RHYTHMDB_ENTRY_TYPE_IMPORT_ERROR, mountpoint);
508
rhythmdb_get_active_mounts (RhythmDB *db)
511
GList *mountpoints = NULL;
514
mounts = g_volume_monitor_get_mounts (db->priv->volume_monitor);
515
for (i = mounts; i != NULL; i = i->next) {
518
GMount *mount = i->data;
520
root = g_mount_get_root (mount);
521
mountpoint = g_file_get_uri (root);
522
mountpoints = g_list_prepend (mountpoints, mountpoint);
523
g_object_unref (root);
526
rb_list_destroy_free (mounts, (GDestroyNotify) g_object_unref);