pump up version number and update changelog
[profile/ivi/pulseaudio-module-murphy-ivi.git] / augment / module-augment-properties.c
1 /***
2   This file is part of PulseAudio.
3
4   Copyright 2009 Lennart Poettering
5
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.
10
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.
15
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
19   USA.
20 ***/
21
22 #include <pulsecore/pulsecore-config.h>
23
24 #include <sys/stat.h>
25 #include <dirent.h>
26 #include <time.h>
27
28 #if defined(HAVE_REGEX_H)
29 #include <regex.h>
30 #elif defined(HAVE_PCREPOSIX_H)
31 #include <pcreposix.h>
32 #endif
33
34 #include <pulse/xmalloc.h>
35
36 #include <pulsecore/module.h>
37 #include <pulsecore/core-util.h>
38 #include <pulsecore/modargs.h>
39 #include <pulsecore/log.h>
40 #include <pulsecore/client.h>
41 #include <pulsecore/conf-parser.h>
42 #include <pulsecore/hashmap.h>
43
44 #include "module-augment-properties-symdef.h"
45
46 PA_MODULE_AUTHOR("Lennart Poettering");
47 PA_MODULE_DESCRIPTION("Augment the property sets of streams with additional static information");
48 PA_MODULE_VERSION(PACKAGE_VERSION);
49 PA_MODULE_LOAD_ONCE(true);
50
51 #ifndef CONFIG_FILE_DIR
52 #define CONFIG_FILE_DIR "/etc/pulse/augment_property_client_rules"
53 #endif
54
55 #ifndef SINK_INPUT_RULE_DIR
56 #define SINK_INPUT_RULE_DIR "/etc/pulse/augment_property_sink_input_rules"
57 #endif
58
59 #define STAT_INTERVAL 30
60 #define MAX_CACHE_SIZE 50
61
62 enum rule_match {
63     RULE_UNDEFINED = 1,
64     RULE_HIT,
65     RULE_MISS
66 };
67
68 static const char* const valid_modargs[] = {
69     NULL
70 };
71
72 struct rule {
73     time_t timestamp;
74     bool good;
75     time_t desktop_mtime;
76     time_t conf_mtime;
77     char *process_name;
78     char *application_name;
79     char *icon_name;
80     char *role;
81     pa_proplist *proplist;
82 };
83
84 struct sink_input_rule_file {
85     pa_hashmap *rules;
86     char *target_key;
87     char *target_value;
88     char *client_name;
89     char *fn; /* for hashmap memory management */
90 };
91
92 struct sink_input_rule_section {
93     char *stream_key;
94     bool comp;
95     regex_t stream_value;
96     char *section_name; /* for hashmap memory management */
97 };
98
99 struct userdata {
100     pa_hashmap *cache;
101     pa_hook_slot *client_new_slot, *client_proplist_changed_slot, *sink_input_new_slot;
102     pa_hashmap *sink_input_rules;
103     pa_client *directory_watch_client;
104 };
105
106 static void rule_free(struct rule *r) {
107     pa_assert(r);
108
109     pa_xfree(r->process_name);
110     pa_xfree(r->application_name);
111     pa_xfree(r->icon_name);
112     pa_xfree(r->role);
113     if (r->proplist)
114         pa_proplist_free(r->proplist);
115     pa_xfree(r);
116 }
117
118 static void sink_input_rule_free(struct sink_input_rule_section *s, struct userdata *u) {
119     pa_assert(s);
120
121     pa_xfree(s->stream_key);
122     if (s->comp)
123         regfree(&s->stream_value);
124     pa_xfree(s->section_name);
125
126     pa_xfree(s);
127 }
128
129 static void sink_input_rule_file_free(struct sink_input_rule_file *rf, struct userdata *u) {
130     void       *state;
131     struct sink_input_rule_section   *section;
132
133     pa_assert(rf);
134
135     PA_HASHMAP_FOREACH(section, rf->rules, state) {
136         pa_xfree(section->stream_key);
137         if (section->comp)
138             regfree(&section->stream_value);
139         pa_xfree(section->section_name);
140     }
141
142     pa_hashmap_free(rf->rules);
143
144     pa_xfree(rf->client_name);
145     pa_xfree(rf->target_key);
146     pa_xfree(rf->target_value);
147     pa_xfree(rf->fn);
148
149     pa_xfree(rf);
150 }
151
152 static int parse_properties(pa_config_parser_state *state) {
153
154     pa_assert(state);
155
156     struct rule *r = state->userdata;
157     pa_proplist *n;
158
159     if (!(n = pa_proplist_from_string(state->rvalue)))
160         return -1;
161
162     if (r->proplist) {
163         pa_proplist_update(r->proplist, PA_UPDATE_MERGE, n);
164         pa_proplist_free(n);
165     } else
166         r->proplist = n;
167
168     return 0;
169 }
170
171 static int parse_categories(pa_config_parser_state *state) {
172     pa_assert(state);
173
174     struct rule *r = state->userdata;
175     const char *s = NULL;
176     char *c;
177
178     while ((c = pa_split(state->rvalue, ";", &s))) {
179
180         if (pa_streq(c, "Game")) {
181             pa_xfree(r->role);
182             r->role = pa_xstrdup("game");
183         } else if (pa_streq(c, "Telephony")) {
184             pa_xfree(r->role);
185             r->role = pa_xstrdup("phone");
186         }
187
188         pa_xfree(c);
189     }
190
191     return 0;
192 }
193
194 static int check_type(pa_config_parser_state *state) {
195     pa_assert(state);
196
197     return pa_streq(state->rvalue, "Application") ? 0 : -1;
198 }
199
200 static int catch_all(pa_config_parser_state *state) {
201
202     return 0;
203 }
204
205 static void parse_file(struct rule *r, const char *fn, pa_config_item *table, bool first) {
206     char *application_name = NULL;
207     char *icon_name = NULL;
208     char *role = NULL;
209     pa_proplist *p = NULL;
210
211     if (first) {
212         /* clean up before update */
213         pa_xfree(r->application_name);
214         pa_xfree(r->icon_name);
215         pa_xfree(r->role);
216
217         if (r->proplist)
218             pa_proplist_clear(r->proplist);
219     }
220     else {
221         /* keep the old data safe */
222         application_name = r->application_name;
223         icon_name = r->icon_name;
224         role = r->role;
225         p = r->proplist;
226     }
227
228     r->application_name = r->icon_name = r->role = NULL;
229
230     table[0].data = &r->application_name;
231     table[1].data = &r->icon_name;
232
233     if (pa_config_parse(fn, NULL, table, NULL, r) < 0)
234         pa_log_warn("Failed to parse file %s.", fn);
235
236     if (!first) {
237         /* copy the old data in place if there was no new data, merge the proplist */
238
239         if (r->application_name)
240             pa_xfree(application_name);
241         else
242             r->application_name = application_name;
243
244         if (r->icon_name)
245             pa_xfree(icon_name);
246         else
247             r->icon_name = icon_name;
248
249         if (r->role)
250             pa_xfree(role);
251         else
252             r->role = role;
253
254         if (p) {
255             if (r->proplist) {
256                 pa_proplist_update(r->proplist, PA_UPDATE_MERGE, p);
257                 pa_proplist_clear(p);
258             }
259             else {
260                 r->proplist = p;
261             }
262         }
263     }
264 }
265
266 static void update_rule(struct rule *r) {
267     char *fn;
268     struct stat st;
269     static pa_config_item table[] = {
270         { "Name", pa_config_parse_string,              NULL, "Desktop Entry" },
271         { "Icon", pa_config_parse_string,              NULL, "Desktop Entry" },
272         { "Type", check_type,                          NULL, "Desktop Entry" },
273         { "X-PulseAudio-Properties", parse_properties, NULL, "Desktop Entry" },
274         { "Categories", parse_categories,              NULL, "Desktop Entry" },
275         { NULL,  catch_all, NULL, NULL },
276         { NULL, NULL, NULL, NULL },
277     };
278     bool found = false;
279
280     pa_assert(r);
281
282     /* Check first the non-graphical applications configuration file. If a
283        file corresponding to the process isn't found, go and check the desktop
284        files. If a file is found, augment it with the desktop data anyway. */
285
286     fn = pa_sprintf_malloc(CONFIG_FILE_DIR PA_PATH_SEP "%s.conf", r->process_name);
287
288     pa_log_debug("Looking for file %s", fn);
289
290     if (stat(fn, &st) == 0)
291         found = true;
292
293     if (!found)
294         r->good = false;
295
296     if (found && !(r->good && st.st_mtime == r->conf_mtime)) {
297         /* Theoretically the filename could have changed, but if so
298            having the same mtime is very unlikely so not worth tracking it in r */
299         if (r->good)
300             pa_log_debug("Found %s (which has been updated since we last checked).", fn);
301         else
302             pa_log_debug("Found %s.", fn);
303
304         parse_file(r, fn, table, true);
305         r->conf_mtime = st.st_mtime;
306         r->good = true;
307     }
308
309     pa_xfree(fn);
310     found = false;
311
312     fn = pa_sprintf_malloc(DESKTOPFILEDIR PA_PATH_SEP "%s.desktop", r->process_name);
313
314     pa_log_debug("Looking for file %s", fn);
315
316     if (stat(fn, &st) == 0)
317         found = true;
318     else {
319 #ifdef DT_DIR
320         DIR *desktopfiles_dir;
321         struct dirent *dir;
322
323         /* Let's try a more aggressive search, but only one level */
324         if ((desktopfiles_dir = opendir(DESKTOPFILEDIR))) {
325             while ((dir = readdir(desktopfiles_dir))) {
326                 if (dir->d_type != DT_DIR
327                     || strcmp(dir->d_name, ".") == 0
328                     || strcmp(dir->d_name, "..") == 0)
329                     continue;
330
331                 pa_xfree(fn);
332                 fn = pa_sprintf_malloc(DESKTOPFILEDIR PA_PATH_SEP "%s" PA_PATH_SEP "%s.desktop", dir->d_name, r->process_name);
333
334                 if (stat(fn, &st) == 0) {
335                     found = true;
336                     break;
337                 }
338             }
339             closedir(desktopfiles_dir);
340         }
341 #endif
342     }
343     if (!found) {
344         r->good = false;
345         pa_xfree(fn);
346         return;
347     }
348
349     if (r->good) {
350         if (st.st_mtime == r->desktop_mtime) {
351             /* Theoretically the filename could have changed, but if so
352                having the same mtime is very unlikely so not worth tracking it in r */
353             pa_xfree(fn);
354             return;
355         }
356         pa_log_debug("Found %s (which has been updated since we last checked).", fn);
357     } else
358         pa_log_debug("Found %s.", fn);
359
360     parse_file(r, fn, table, false);
361     r->desktop_mtime = st.st_mtime;
362     r->good = true;
363
364
365     pa_xfree(fn);
366 }
367
368 static void apply_rule(struct rule *r, pa_proplist *p) {
369     pa_assert(r);
370     pa_assert(p);
371
372     if (!r->good)
373         return;
374
375     if (r->proplist)
376         pa_proplist_update(p, PA_UPDATE_MERGE, r->proplist);
377
378     if (r->icon_name)
379         if (!pa_proplist_contains(p, PA_PROP_APPLICATION_ICON_NAME))
380             pa_proplist_sets(p, PA_PROP_APPLICATION_ICON_NAME, r->icon_name);
381
382     if (r->application_name) {
383         const char *t;
384
385         t = pa_proplist_gets(p, PA_PROP_APPLICATION_NAME);
386
387         if (!t || pa_streq(t, r->process_name))
388             pa_proplist_sets(p, PA_PROP_APPLICATION_NAME, r->application_name);
389     }
390
391     if (r->role)
392         if (!pa_proplist_contains(p, PA_PROP_MEDIA_ROLE))
393             pa_proplist_sets(p, PA_PROP_MEDIA_ROLE, r->role);
394 }
395
396 static void make_room(pa_hashmap *cache) {
397     pa_assert(cache);
398
399     while (pa_hashmap_size(cache) >= MAX_CACHE_SIZE) {
400         struct rule *r;
401
402         pa_assert_se(r = pa_hashmap_steal_first(cache));
403         rule_free(r);
404     }
405 }
406
407 static pa_hook_result_t process(struct userdata *u, pa_proplist *p) {
408
409     struct rule *r;
410     time_t now;
411     const char *pn;
412
413     pa_assert(u);
414     pa_assert(p);
415
416     if (!(pn = pa_proplist_gets(p, PA_PROP_APPLICATION_PROCESS_BINARY)))
417         return PA_HOOK_OK;
418
419     if (*pn == '.' || strchr(pn, '/'))
420         return PA_HOOK_OK;
421
422     time(&now);
423
424     pa_log_debug("Looking for configuration file for %s", pn);
425
426     if ((r = pa_hashmap_get(u->cache, pn))) {
427         if (now-r->timestamp > STAT_INTERVAL) {
428             r->timestamp = now;
429             update_rule(r);
430         }
431     } else {
432         make_room(u->cache);
433
434         r = pa_xnew0(struct rule, 1);
435         r->process_name = pa_xstrdup(pn);
436         r->timestamp = now;
437         pa_hashmap_put(u->cache, (void *)r->process_name, r);
438         update_rule(r);
439     }
440
441     apply_rule(r, p);
442     return PA_HOOK_OK;
443 }
444
445 static pa_hook_result_t client_new_cb(pa_core *core, pa_client_new_data *data, struct userdata *u) {
446     pa_core_assert_ref(core);
447     pa_assert(data);
448     pa_assert(u);
449
450     return process(u, data->proplist);
451 }
452
453 static pa_hook_result_t client_proplist_changed_cb(pa_core *core, pa_client *client, struct userdata *u) {
454     pa_core_assert_ref(core);
455     pa_assert(client);
456     pa_assert(u);
457
458     return process(u, client->proplist);
459 }
460
461 static char **filter_by_client(pa_hashmap *h, pa_client *c) {
462
463     char **possible = pa_xnew0(char *, pa_hashmap_size(h)+1);
464     const char *process_binary;
465     char **iter;
466     void *state = NULL;
467     const void *key = NULL;
468
469     if (!c || !c->proplist) {
470         pa_xfree(possible);
471         return NULL;
472     }
473
474     process_binary = pa_proplist_gets(c->proplist, PA_PROP_APPLICATION_PROCESS_BINARY);
475
476     iter = possible;
477
478     while (pa_hashmap_iterate(h, &state, &key)) {
479         struct sink_input_rule_file *rf = pa_hashmap_get(h, key);
480
481         pa_assert(rf);
482
483         if (!rf->client_name ||
484             (process_binary && strcmp(process_binary, rf->client_name) == 0)) {
485             *iter = pa_xstrdup((const char *) key);
486             iter++;
487         }
488     }
489
490     return possible;
491 }
492
493 static pa_hook_result_t process_sink_input(
494         struct userdata *u,
495         pa_proplist *p,
496         pa_client *client) {
497
498     void *state = NULL;
499     const void *fn = NULL;
500     const char *prop = NULL;
501     struct sink_input_rule_file *rf;
502     char **possible = NULL, **iter;
503     pa_hashmap *valid;
504
505     possible = filter_by_client(u->sink_input_rules, client);
506
507     if (!possible)
508         return PA_HOOK_OK;
509
510     /* size of possible must be at least one, or otherwise we wouldn't be here */
511
512     if (possible[0] == NULL) {
513         goto end;
514     }
515
516     valid = pa_hashmap_new(pa_idxset_string_hash_func, pa_idxset_string_compare_func);
517
518     iter = possible;
519
520     /* init the map */
521
522     while (*iter) {
523         pa_hashmap_put(valid, (void *) *iter, (void *) RULE_UNDEFINED);
524         iter++;
525     }
526
527     prop = pa_proplist_iterate(p, &state);
528
529     while (prop) {
530
531         const char *value = pa_proplist_gets(p, prop);
532
533         iter = possible;
534
535         while (*iter) {
536
537             enum rule_match status = (enum rule_match) pa_hashmap_get(valid, *iter);
538
539             if (status == RULE_MISS) {
540                 iter++;
541                 continue;
542             }
543
544             rf = pa_hashmap_get(u->sink_input_rules, *iter);
545
546             if (rf) {
547                 void *section_state = NULL;
548                 const void *section = NULL;
549                 while (pa_hashmap_iterate(rf->rules, &section_state, &section)) {
550                     struct sink_input_rule_section *s = pa_hashmap_get(rf->rules, section);
551
552                     if (!s) {
553                         pa_log_error("no section associated with %s", (const char *) section);
554                         continue;
555                     }
556                     if (strcmp(prop, s->stream_key) == 0) {
557
558                         if (value && !regexec(&s->stream_value, value, 0, NULL, 0)) {
559                             /* hit */
560                             pa_log_debug("hit %s", *iter);
561
562                             if (status == RULE_UNDEFINED) {
563                                 pa_hashmap_remove(valid, *iter);
564                                 pa_hashmap_put(valid, (void *) *iter, (void *) RULE_HIT);
565                             }
566                         }
567                         else {
568                             /* miss, no more processing for this rule file*/
569                             pa_hashmap_remove(valid, *iter);
570                             pa_hashmap_put(valid, (void *) *iter, (void *) RULE_MISS);
571                             pa_log_debug("miss %s", *iter);
572                         }
573                     }
574                 }
575             }
576             iter++;
577         }
578
579         prop = pa_proplist_iterate(p, &state);
580     }
581
582     /* go do the changes for the rule files that actually were matching */
583     state = NULL;
584
585     while (pa_hashmap_iterate(valid, &state, &fn)) {
586         enum rule_match status = (enum rule_match) pa_hashmap_get(valid, fn);
587         if (status == RULE_HIT) {
588             pa_log_debug("rule hit: %s", (const char *) fn);
589             rf = pa_hashmap_get(u->sink_input_rules, (const char *) fn);
590             pa_proplist_sets(p, rf->target_key, rf->target_value);
591         }
592         else if (status == RULE_MISS) {
593             pa_log_debug("rule miss: %s", (const char *) fn);
594         }
595         else if (status == RULE_UNDEFINED) {
596             pa_log_debug("rule undefined: %s", (const char *) fn);
597         }
598         else {
599             pa_log_error("memory corruption for %s (%i)", (const char *) fn, status);
600         }
601     }
602
603     pa_hashmap_free(valid);
604
605     iter = possible;
606     while (*iter) {
607         pa_xfree(*iter);
608         iter++;
609     }
610
611 end:
612     pa_xfree(possible);
613
614     return PA_HOOK_OK;
615 }
616
617 static pa_hook_result_t sink_input_new_cb(
618         pa_core *core,
619         pa_sink_input_new_data *new_data,
620         struct userdata *u) {
621
622     pa_core_assert_ref(core);
623     pa_assert(new_data);
624     pa_assert(u);
625
626     if (!new_data->client)
627         return PA_HOOK_OK;
628
629     if (!u->sink_input_rules)
630         return PA_HOOK_OK;
631
632     return process_sink_input(u, new_data->proplist, new_data->client);
633 }
634
635 static int parse_rule_sections(pa_config_parser_state *state) {
636
637     struct sink_input_rule_file **rfp;
638     struct sink_input_rule_file *rf;
639     struct sink_input_rule_section *s;
640
641     pa_assert(state);
642
643     rfp = state->data;
644     rf = *rfp;
645     s = pa_hashmap_get(rf->rules, state->section);
646
647     if (!s) {
648         s = pa_xnew0(struct sink_input_rule_section, 1);
649         s->comp = false;
650
651         /* add key to the struct for later freeing */
652         s->section_name = pa_xstrdup(state->section);
653         pa_hashmap_put(rf->rules, (void *)s->section_name, s);
654     }
655
656     if (strcmp(state->lvalue, "prop_key") == 0) {
657         if (s->stream_key)
658             pa_xfree(s->stream_key);
659         s->stream_key = pa_xstrdup(state->rvalue);
660     }
661     else if (strcmp(state->lvalue, "prop_value") == 0) {
662         int ret;
663         if (s->comp)
664             regfree(&s->stream_value);
665
666         ret = regcomp(&s->stream_value, state->rvalue, REG_EXTENDED|REG_NOSUB);
667         s->comp = true;
668
669         if (ret != 0) {
670             char errbuf[256];
671             regerror(ret, &s->stream_value, errbuf, 256);
672             pa_log_error("Failed compiling regular expression: %s", errbuf);
673         }
674     }
675
676     return 0;
677 }
678
679 static bool validate_sink_input_rule(struct sink_input_rule_file *rf) {
680
681     void *state;
682     struct sink_input_rule_section *s;
683
684     if (!rf->target_key || !rf->target_value) {
685         pa_log_error("No result condition listed for rule file");
686         /* no result condition listed, so no point in using this rule file */
687         return false;
688     }
689
690     PA_HASHMAP_FOREACH(s, rf->rules, state) {
691         if (!s->stream_key || s->comp == false) {
692             pa_log_error("Incomplete rule section [%s] in rule file", s->section_name);
693             return false;
694         }
695     }
696
697     return true;
698 }
699
700 static pa_hashmap *update_sink_input_rules() {
701
702     struct dirent *file;
703     DIR *sinkinputrulefiles_dir;
704     pa_hashmap *rules = pa_hashmap_new(pa_idxset_string_hash_func, pa_idxset_string_compare_func);
705
706     sinkinputrulefiles_dir = opendir(SINK_INPUT_RULE_DIR);
707
708     if (!sinkinputrulefiles_dir)
709         return NULL;
710
711     while ((file = readdir(sinkinputrulefiles_dir))) {
712
713         if (file->d_type == DT_REG) {
714
715             struct sink_input_rule_file *rf = pa_xnew0(struct sink_input_rule_file, 1);
716
717             /* parse the file */
718
719             pa_config_item table[] = {
720                 { "target_key",   pa_config_parse_string, &rf->target_key,   "result"  },
721                 { "target_value", pa_config_parse_string, &rf->target_value, "result"  },
722                 { "client_name",  pa_config_parse_string, &rf->client_name,  "general" },
723                 { NULL,           parse_rule_sections,    &rf,               NULL      },
724                 { NULL, NULL, NULL, NULL },
725             };
726
727             rf->rules = pa_hashmap_new(pa_idxset_string_hash_func, pa_idxset_string_compare_func);
728             rf->fn = pa_sprintf_malloc(SINK_INPUT_RULE_DIR PA_PATH_SEP "%s", file->d_name);
729
730             if (pa_config_parse(rf->fn, NULL, table, NULL, rf) >= 0) {
731
732                 pa_log_info("Successfully parsed sink input conf file %s", file->d_name);
733
734                 if (!validate_sink_input_rule(rf)) {
735                     pa_log_error("validating rule file failed");
736                     sink_input_rule_file_free(rf, NULL);
737                     rf = NULL;
738                 }
739                 else {
740                     pa_log_info("adding filename %s to %p", rf->fn, rf);
741                     pa_hashmap_put(rules, (void *)rf->fn, rf);
742                 }
743             }
744         }
745     }
746
747     closedir(sinkinputrulefiles_dir);
748
749     if (pa_hashmap_isempty(rules)) {
750         pa_hashmap_free(rules);
751         return NULL;
752     }
753
754     return rules;
755 }
756
757 static void send_event(pa_client *c, const char *evt, pa_proplist *d) {
758
759     struct userdata *u = c->userdata;
760
761     const char *action = pa_proplist_gets(d, "action");
762     const char *dir = pa_proplist_gets(d, "directory");
763     const char *file = pa_proplist_gets(d, "file");
764
765     pa_log_debug("received event '%s': action: %s, dir: %s, file: %s", evt, action, dir, file);
766
767     /* update the rules */
768     pa_xfree(u->sink_input_rules);
769     u->sink_input_rules = update_sink_input_rules();
770 }
771
772 static pa_client *create_directory_watch_client(pa_module *m, const char *directory, struct userdata *u) {
773     pa_client_new_data data;
774     pa_client *c;
775
776     /* Create a virtual client for triggering the directory callbacks. The
777        protocol is a property list. */
778
779     pa_client_new_data_init(&data);
780     data.module = m;
781     data.driver = __FILE__;
782     pa_proplist_sets(data.proplist, PA_PROP_APPLICATION_NAME, "Directory watch client");
783     pa_proplist_sets(data.proplist, "type", "directory-watch");
784     pa_proplist_sets(data.proplist, "dir-watch.directory", directory);
785
786     c = pa_client_new(m->core, &data);
787
788     pa_client_new_data_done(&data);
789
790     if (!c) {
791         pa_log_error("Failed to create directory watch client");
792         return NULL;
793     }
794
795     c->send_event = send_event;
796     c->userdata = u;
797
798     return c;
799 }
800
801 int pa__init(pa_module *m) {
802     pa_modargs *ma = NULL;
803     struct userdata *u;
804
805     pa_assert(m);
806
807     if (!(ma = pa_modargs_new(m->argument, valid_modargs))) {
808         pa_log("Failed to parse module arguments");
809         goto fail;
810     }
811
812     m->userdata = u = pa_xnew(struct userdata, 1);
813
814     u->sink_input_rules = update_sink_input_rules();
815
816     u->cache = pa_hashmap_new(pa_idxset_string_hash_func, pa_idxset_string_compare_func);
817
818     u->directory_watch_client = create_directory_watch_client(m, SINK_INPUT_RULE_DIR, u);
819     if (!u->directory_watch_client)
820         goto fail;
821
822     u->client_new_slot = pa_hook_connect(&m->core->hooks[PA_CORE_HOOK_CLIENT_NEW], PA_HOOK_EARLY, (pa_hook_cb_t) client_new_cb, u);
823     u->client_proplist_changed_slot = pa_hook_connect(&m->core->hooks[PA_CORE_HOOK_CLIENT_PROPLIST_CHANGED], PA_HOOK_EARLY, (pa_hook_cb_t) client_proplist_changed_cb, u);
824     u->sink_input_new_slot = pa_hook_connect(&m->core->hooks[PA_CORE_HOOK_SINK_INPUT_NEW], PA_HOOK_EARLY, (pa_hook_cb_t) sink_input_new_cb, u);
825
826
827     pa_modargs_free(ma);
828
829     return 0;
830
831 fail:
832     pa__done(m);
833
834     if (ma)
835         pa_modargs_free(ma);
836
837     return -1;
838 }
839
840 void pa__done(pa_module *m) {
841     struct userdata* u;
842     struct sink_input_rule_file *rf;
843     void *state;
844
845     pa_assert(m);
846
847     if (!(u = m->userdata))
848         return;
849
850     if (u->client_new_slot)
851         pa_hook_slot_free(u->client_new_slot);
852     if (u->client_proplist_changed_slot)
853         pa_hook_slot_free(u->client_proplist_changed_slot);
854     if (u->sink_input_new_slot)
855         pa_hook_slot_free(u->sink_input_new_slot);
856
857     if (u->cache) {
858         struct rule *r;
859
860         while ((r = pa_hashmap_steal_first(u->cache)))
861             rule_free(r);
862
863         pa_hashmap_free(u->cache);
864     }
865
866     if (u->sink_input_rules) {
867
868         PA_HASHMAP_FOREACH(rf, u->sink_input_rules, state) {
869             sink_input_rule_file_free(rf, NULL);
870         }
871
872         pa_hashmap_free(u->sink_input_rules);
873     }
874
875     if (u->directory_watch_client)
876         pa_client_free(u->directory_watch_client);
877
878     pa_xfree(u);
879 }