2 * Copyright (C) 2003-2010 The Music Player Daemon Project
3 * http://www.musicpd.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 along
16 * with this program; if not, write to the Free Software Foundation, Inc.,
17 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
20 #include "config.h" /* must be first for large file support */
21 #include "inotify_update.h"
22 #include "inotify_source.h"
23 #include "inotify_queue.h"
29 #include <sys/inotify.h>
37 #define G_LOG_DOMAIN "inotify"
40 IN_MASK
= IN_ATTRIB
|IN_CLOSE_WRITE
|IN_CREATE
|IN_DELETE
|IN_DELETE_SELF
47 struct watch_directory
{
48 struct watch_directory
*parent
;
57 static struct mpd_inotify_source
*inotify_source
;
59 static unsigned inotify_max_depth
;
60 static struct watch_directory inotify_root
;
61 static GTree
*inotify_directories
;
64 compare(gconstpointer a
, gconstpointer b
)
75 tree_add_watch_directory(struct watch_directory
*directory
)
77 g_tree_insert(inotify_directories
,
78 GINT_TO_POINTER(directory
->descriptor
), directory
);
82 tree_remove_watch_directory(struct watch_directory
*directory
)
85 bool found
= g_tree_remove(inotify_directories
,
86 GINT_TO_POINTER(directory
->descriptor
));
90 static struct watch_directory
*
91 tree_find_watch_directory(int wd
)
93 return g_tree_lookup(inotify_directories
, GINT_TO_POINTER(wd
));
97 remove_watch_directory(struct watch_directory
*directory
)
99 assert(directory
!= NULL
);
100 assert(directory
->parent
!= NULL
);
101 assert(directory
->parent
->children
!= NULL
);
103 tree_remove_watch_directory(directory
);
105 while (directory
->children
!= NULL
)
106 remove_watch_directory(directory
->children
->data
);
108 directory
->parent
->children
=
109 g_list_remove(directory
->parent
->children
, directory
);
111 mpd_inotify_source_rm(inotify_source
, directory
->descriptor
);
112 g_free(directory
->name
);
113 g_slice_free(struct watch_directory
, directory
);
117 watch_directory_get_uri_fs(const struct watch_directory
*directory
)
119 char *parent_uri
, *uri
;
121 if (directory
->parent
== NULL
)
124 parent_uri
= watch_directory_get_uri_fs(directory
->parent
);
125 if (parent_uri
== NULL
)
126 return g_strdup(directory
->name
);
128 uri
= g_strconcat(parent_uri
, "/", directory
->name
, NULL
);
134 /* we don't look at "." / ".." nor files with newlines in their name */
135 static bool skip_path(const char *path
)
137 return (path
[0] == '.' && path
[1] == 0) ||
138 (path
[0] == '.' && path
[1] == '.' && path
[2] == 0) ||
139 strchr(path
, '\n') != NULL
;
143 recursive_watch_subdirectories(struct watch_directory
*directory
,
144 const char *path_fs
, unsigned depth
)
146 GError
*error
= NULL
;
150 assert(directory
!= NULL
);
151 assert(depth
<= inotify_max_depth
);
152 assert(path_fs
!= NULL
);
156 if (depth
> inotify_max_depth
)
159 dir
= opendir(path_fs
);
161 g_warning("Failed to open directory %s: %s",
162 path_fs
, g_strerror(errno
));
166 while ((ent
= readdir(dir
))) {
170 struct watch_directory
*child
;
172 if (skip_path(ent
->d_name
))
175 child_path_fs
= g_strconcat(path_fs
, "/", ent
->d_name
, NULL
);
176 /* XXX what about symlinks? */
177 ret
= lstat(child_path_fs
, &st
);
179 g_warning("Failed to stat %s: %s",
180 child_path_fs
, g_strerror(errno
));
181 g_free(child_path_fs
);
185 if (!S_ISDIR(st
.st_mode
)) {
186 g_free(child_path_fs
);
190 ret
= mpd_inotify_source_add(inotify_source
, child_path_fs
,
193 g_warning("Failed to register %s: %s",
194 child_path_fs
, error
->message
);
197 g_free(child_path_fs
);
201 child
= tree_find_watch_directory(ret
);
203 /* already being watched */
204 g_free(child_path_fs
);
208 child
= g_slice_new(struct watch_directory
);
209 child
->parent
= directory
;
210 child
->name
= g_strdup(ent
->d_name
);
211 child
->descriptor
= ret
;
212 child
->children
= NULL
;
214 directory
->children
= g_list_prepend(directory
->children
,
217 tree_add_watch_directory(child
);
219 recursive_watch_subdirectories(child
, child_path_fs
, depth
);
220 g_free(child_path_fs
);
228 watch_directory_depth(const struct watch_directory
*d
)
233 while ((d
= d
->parent
) != NULL
)
240 mpd_inotify_callback(int wd
, unsigned mask
,
241 G_GNUC_UNUSED
const char *name
, G_GNUC_UNUSED
void *ctx
)
243 struct watch_directory
*directory
;
246 /*g_debug("wd=%d mask=0x%x name='%s'", wd, mask, name);*/
248 directory
= tree_find_watch_directory(wd
);
249 if (directory
== NULL
)
252 uri_fs
= watch_directory_get_uri_fs(directory
);
254 if ((mask
& (IN_DELETE_SELF
|IN_MOVE_SELF
)) != 0) {
256 remove_watch_directory(directory
);
260 if ((mask
& (IN_ATTRIB
|IN_CREATE
|IN_MOVE
)) != 0 &&
261 (mask
& IN_ISDIR
) != 0) {
262 /* a sub directory was changed: register those in
264 char *root
= map_directory_fs(db_get_root());
267 if (uri_fs
!= NULL
) {
268 path_fs
= g_strconcat(root
, "/", uri_fs
, NULL
);
273 recursive_watch_subdirectories(directory
, path_fs
,
274 watch_directory_depth(directory
));
278 if ((mask
& (IN_CLOSE_WRITE
|IN_MOVE
|IN_DELETE
)) != 0 ||
279 /* at the maximum depth, we watch out for newly created
281 (watch_directory_depth(directory
) == inotify_max_depth
&&
282 (mask
& (IN_CREATE
|IN_ISDIR
)) == (IN_CREATE
|IN_ISDIR
))) {
283 /* a file was changed, or a directory was
284 moved/deleted: queue a database update */
285 char *uri_utf8
= uri_fs
!= NULL
286 ? fs_charset_to_utf8(uri_fs
)
289 if (uri_utf8
!= NULL
)
290 /* this function will take care of freeing
292 mpd_inotify_enqueue(uri_utf8
);
299 mpd_inotify_init(unsigned max_depth
)
301 struct directory
*root
;
303 GError
*error
= NULL
;
305 g_debug("initializing inotify");
307 root
= db_get_root();
309 g_debug("no music directory configured");
313 path
= map_directory_fs(root
);
315 g_warning("mapper has failed");
319 inotify_source
= mpd_inotify_source_new(mpd_inotify_callback
, NULL
,
321 if (inotify_source
== NULL
) {
322 g_warning("%s", error
->message
);
328 inotify_max_depth
= max_depth
;
330 inotify_root
.name
= path
;
331 inotify_root
.descriptor
= mpd_inotify_source_add(inotify_source
, path
,
333 if (inotify_root
.descriptor
< 0) {
334 g_warning("%s", error
->message
);
336 mpd_inotify_source_free(inotify_source
);
337 inotify_source
= NULL
;
342 inotify_directories
= g_tree_new(compare
);
343 tree_add_watch_directory(&inotify_root
);
345 recursive_watch_subdirectories(&inotify_root
, path
, 0);
347 mpd_inotify_queue_init();
349 g_debug("watching music directory");
353 free_watch_directory(G_GNUC_UNUSED gpointer key
, gpointer value
,
354 G_GNUC_UNUSED gpointer data
)
356 struct watch_directory
*directory
= value
;
358 g_free(directory
->name
);
359 g_list_free(directory
->children
);
361 if (directory
!= &inotify_root
)
362 g_slice_free(struct watch_directory
, directory
);
368 mpd_inotify_finish(void)
370 if (inotify_source
== NULL
)
373 mpd_inotify_queue_finish();
374 mpd_inotify_source_free(inotify_source
);
376 g_tree_foreach(inotify_directories
, free_watch_directory
, NULL
);
377 g_tree_destroy(inotify_directories
);