2 This file is part of PulseAudio.
4 Copyright 2012 Ismo Puustinen
6 PulseAudio is free software; you can redistribute it and/or modify
7 it under the terms of the GNU Lesser General Public License as published
8 by the Free Software Foundation; either version 2.1 of the License,
9 or (at your option) any later version.
11 PulseAudio is distributed in the hope that it will be useful, but
12 WITHOUT ANY WARRANTY; without even the implied warranty of
13 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
14 General Public License for more details.
16 You should have received a copy of the GNU Lesser General Public License
17 along with PulseAudio; if not, write to the Free Software
18 Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
22 #include <pulsecore/pulsecore-config.h>
31 #include <sys/inotify.h>
40 #include <sys/types.h>
42 #include <pulse/xmalloc.h>
43 #include <pulse/proplist.h>
45 #include <pulsecore/client.h>
46 #include <pulsecore/macro.h>
47 #include <pulsecore/module.h>
48 #include <pulsecore/core-util.h>
49 #include <pulsecore/core-error.h>
50 #include <pulsecore/log.h>
51 #include <pulsecore/llist.h>
53 #include "module-dir-watch-symdef.h"
55 PA_MODULE_AUTHOR("Ismo Puustinen");
56 PA_MODULE_DESCRIPTION("Directory watch module");
57 PA_MODULE_VERSION(PACKAGE_VERSION);
58 PA_MODULE_LOAD_ONCE(TRUE);
64 pa_hook_slot *client_watch_slot;
65 pa_hook_slot *client_unlink_slot;
67 pa_hashmap *paths_to_clients;
69 pa_io_event *inotify_io;
75 PA_LLIST_FIELDS(struct client_id);
80 PA_LLIST_HEAD(struct client_id, ids);
99 list = pa_proplist_new();
101 pa_proplist_sets(list, "action", action);
102 pa_proplist_sets(list, "directory", dir);
103 pa_proplist_sets(list, "file", fn);
105 pa_client_send_event(c, "dir_watch_event", list);
107 pa_proplist_free(list);
110 static void fire_created(
116 fire(u, c, "create", dir, fn);
119 static void fire_deleted(
125 fire(u, c, "create", dir, fn);
128 static void fire_modified(
134 fire(u, c, "modify", dir, fn);
137 static void fire_attributed(
143 fire(u, c, "attribute", dir, fn);
147 static void client_data_free(struct client_data *cd, struct userdata *u) {
151 pa_xfree(cd->directory);
153 struct client_id *id = cd->ids;
154 PA_LLIST_REMOVE(struct client_id, cd->ids, id);
161 static void dir_watch_inotify_cb(
165 pa_io_event_flags_t events,
168 struct inotify_event *event;
170 uint8_t eventbuf[2 * (sizeof(struct inotify_event) + NAME_MAX + 1)];
171 struct userdata *u = userdata;
173 pa_log_debug("> inotify_cb");
177 r = pa_read(fd, &eventbuf, sizeof(eventbuf), &type);
180 if (r < 0 && errno == EAGAIN)
186 event = (struct inotify_event *) &eventbuf;
191 if ((size_t) r < sizeof(struct inotify_event)) {
192 pa_log("read() too short.");
196 len = sizeof(struct inotify_event) + event->len;
198 if ((size_t) r < len) {
202 if (event->len > 0) {
203 struct client_data *cd = pa_hashmap_get(u->paths_to_clients, (const void *)NULL + event->wd);
204 struct client_id *id;
210 PA_LLIST_FOREACH(id, cd->ids) {
212 pa_client *c = pa_idxset_get_by_index(u->core->clients, id->id);
215 pa_log_error("client not found!");
219 if (event->mask & IN_MODIFY) {
220 pa_log_debug("File %s modified", event->name);
221 fire_modified(u, c, cd->directory, event->name);
223 if (event->mask & IN_CREATE) {
224 pa_log_debug("File %s created", event->name);
225 fire_created(u, c, cd->directory, event->name);
227 if (event->mask & IN_ATTRIB) {
228 pa_log_debug("File %s attribute change", event->name);
229 fire_attributed(u, c, cd->directory, event->name);
231 if (event->mask & IN_DELETE) {
232 pa_log_debug("File %s deleted", event->name);
233 fire_deleted(u, c, cd->directory, event->name);
238 event = (struct inotify_event*) ((uint8_t*) event + len);
247 a->io_free(u->inotify_io);
248 u->inotify_io = NULL;
251 if (u->inotify_fd >= 0) {
252 pa_close(u->inotify_fd);
258 static int add_directory(const char *directory, pa_client *c, struct userdata *u) {
260 struct client_data *cd;
263 wd = inotify_add_watch(u->inotify_fd, directory, IN_CREATE|IN_DELETE|IN_MODIFY|IN_ATTRIB);
265 pa_log_error("Failed to add directory %s to watch list", directory);
269 cd = pa_hashmap_get(u->paths_to_clients, (const void *)NULL + wd);
272 struct client_id *id = pa_xnew0(struct client_id, 1);
275 PA_LLIST_PREPEND(struct client_id, cd->ids, id);
278 struct client_id *id = pa_xnew0(struct client_id, 1);
281 cd = pa_xnew0(struct client_data, 1);
282 cd->directory = pa_xstrdup(directory);
284 PA_LLIST_HEAD_INIT(struct client_id, cd->ids);
286 PA_LLIST_PREPEND(struct client_id, cd->ids, id);
288 pa_hashmap_put(u->paths_to_clients, (const void *)0 + wd, (void *) cd);
297 static pa_hook_result_t client_unlink_cb(
300 struct userdata *u) {
303 struct client_data *cd;
304 struct client_id *id;
305 pa_bool_t found = FALSE;
307 PA_HASHMAP_FOREACH(cd, u->paths_to_clients, state) {
308 PA_LLIST_FOREACH(id, cd->ids) {
309 if (id->id == c->index) {
310 PA_LLIST_REMOVE(struct client_id, cd->ids, id);
321 /* no-one is interested in the directory anymore */
322 inotify_rm_watch(u->inotify_fd, cd->wd);
323 client_data_free(cd, NULL);
332 static pa_hook_result_t client_watch_cb(
335 struct userdata *u) {
336 const char *type, *directory;
338 pa_log("received directory watch event");
340 type = pa_proplist_gets(c->proplist, "type");
342 if (!type || strcmp(type, "directory-watch") != 0) {
347 directory = pa_proplist_gets(c->proplist, "dir-watch.directory");
353 if (add_directory(directory, c, u) < 0) {
358 /* error, kill the client */
363 void pa__done(pa_module *m) {
367 pa_assert(m->userdata);
373 m->core->mainloop->io_free(u->inotify_io);
375 if (u->inotify_fd >= 0)
376 pa_close(u->inotify_fd);
379 if (u->paths_to_clients)
380 pa_hashmap_free(u->paths_to_clients, (pa_free_cb_t) client_data_free);
386 int pa__init(pa_module *m) {
389 pa_log_debug("Init directory watch module");
393 u = pa_xnew0(struct userdata, 1);
396 u->client_watch_slot = pa_hook_connect(&m->core->hooks[PA_CORE_HOOK_CLIENT_PUT], PA_HOOK_EARLY, (pa_hook_cb_t) client_watch_cb, u);
397 u->client_unlink_slot = pa_hook_connect(&m->core->hooks[PA_CORE_HOOK_CLIENT_UNLINK], PA_HOOK_EARLY, (pa_hook_cb_t) client_unlink_cb, u);
402 u->paths_to_clients = pa_hashmap_new(pa_idxset_trivial_hash_func, pa_idxset_trivial_compare_func);
404 u->inotify_fd = inotify_init1(IN_CLOEXEC|IN_NONBLOCK);
405 if (u->inotify_fd < 0) {
409 u->inotify_io = u->core->mainloop->io_new(u->core->mainloop, u->inotify_fd, PA_IO_EVENT_INPUT, dir_watch_inotify_cb, u);
410 if (!u->inotify_io) {