+ mute = e->muted_valid ? e->muted : FALSE;
+
+ pa_dbus_send_basic_variant_reply(conn, msg, DBUS_TYPE_BOOLEAN, &mute);
+
+ entry_free(e);
+}
+
+static void handle_entry_set_mute(DBusConnection *conn, DBusMessage *msg, DBusMessageIter *iter, void *userdata) {
+ struct dbus_entry *de = userdata;
+ dbus_bool_t mute;
+ struct entry *e;
+ pa_bool_t updated;
+
+ pa_assert(conn);
+ pa_assert(msg);
+ pa_assert(iter);
+ pa_assert(de);
+
+ dbus_message_iter_get_basic(iter, &mute);
+
+ pa_assert_se(e = entry_read(de->userdata, de->entry_name));
+
+ updated = !e->muted_valid || e->muted != mute;
+
+ if (updated) {
+ e->muted = mute;
+ e->muted_valid = TRUE;
+
+ pa_assert_se(entry_write(de->userdata, de->entry_name, e, TRUE));
+
+ entry_apply(de->userdata, de->entry_name, e);
+ send_mute_updated_signal(de, e);
+ trigger_save(de->userdata);
+ }
+
+ pa_dbus_send_empty_reply(conn, msg);
+
+ entry_free(e);
+}
+
+static void handle_entry_get_all(DBusConnection *conn, DBusMessage *msg, void *userdata) {
+ struct dbus_entry *de = userdata;
+ struct entry *e;
+ DBusMessage *reply = NULL;
+ DBusMessageIter msg_iter;
+ DBusMessageIter dict_iter;
+ DBusMessageIter dict_entry_iter;
+ const char *device;
+ dbus_bool_t mute;
+
+ pa_assert(conn);
+ pa_assert(msg);
+ pa_assert(de);
+
+ pa_assert_se(e = entry_read(de->userdata, de->entry_name));
+
+ device = e->device_valid ? e->device : "";
+ mute = e->muted_valid ? e->muted : FALSE;
+
+ pa_assert_se((reply = dbus_message_new_method_return(msg)));
+
+ dbus_message_iter_init_append(reply, &msg_iter);
+ pa_assert_se(dbus_message_iter_open_container(&msg_iter, DBUS_TYPE_ARRAY, "{sv}", &dict_iter));
+
+ pa_dbus_append_basic_variant_dict_entry(&dict_iter, entry_property_handlers[ENTRY_PROPERTY_HANDLER_INDEX].property_name, DBUS_TYPE_UINT32, &de->index);
+ pa_dbus_append_basic_variant_dict_entry(&dict_iter, entry_property_handlers[ENTRY_PROPERTY_HANDLER_NAME].property_name, DBUS_TYPE_STRING, &de->entry_name);
+ pa_dbus_append_basic_variant_dict_entry(&dict_iter, entry_property_handlers[ENTRY_PROPERTY_HANDLER_DEVICE].property_name, DBUS_TYPE_STRING, &device);
+
+ pa_assert_se(dbus_message_iter_open_container(&dict_iter, DBUS_TYPE_DICT_ENTRY, NULL, &dict_entry_iter));
+
+ pa_assert_se(dbus_message_iter_append_basic(&dict_entry_iter, DBUS_TYPE_STRING, &entry_property_handlers[ENTRY_PROPERTY_HANDLER_VOLUME].property_name));
+ append_volume_variant(&dict_entry_iter, e);
+
+ pa_assert_se(dbus_message_iter_close_container(&dict_iter, &dict_entry_iter));
+
+ pa_dbus_append_basic_variant_dict_entry(&dict_iter, entry_property_handlers[ENTRY_PROPERTY_HANDLER_MUTE].property_name, DBUS_TYPE_BOOLEAN, &mute);
+
+ pa_assert_se(dbus_message_iter_close_container(&msg_iter, &dict_iter));
+
+ pa_assert_se(dbus_connection_send(conn, reply, NULL));
+
+ dbus_message_unref(reply);
+
+ entry_free(e);
+}
+
+static void handle_entry_remove(DBusConnection *conn, DBusMessage *msg, void *userdata) {
+ struct dbus_entry *de = userdata;
+ pa_datum key;
+
+ pa_assert(conn);
+ pa_assert(msg);
+ pa_assert(de);
+
+ key.data = de->entry_name;
+ key.size = strlen(de->entry_name);
+
+ pa_assert_se(pa_database_unset(de->userdata->database, &key) == 0);
+
+ send_entry_removed_signal(de);
+ trigger_save(de->userdata);
+
+ pa_assert_se(pa_hashmap_remove(de->userdata->dbus_entries, de->entry_name));
+ dbus_entry_free(de);
+
+ pa_dbus_send_empty_reply(conn, msg);
+}
+
+#endif /* HAVE_DBUS */
+
+static void save_time_callback(pa_mainloop_api*a, pa_time_event* e, const struct timeval *t, void *userdata) {
+ struct userdata *u = userdata;
+
+ pa_assert(a);
+ pa_assert(e);
+ pa_assert(u);
+
+ pa_assert(e == u->save_time_event);
+ u->core->mainloop->time_free(u->save_time_event);
+ u->save_time_event = NULL;
+
+ pa_database_sync(u->database);
+ pa_log_info("Synced.");
+}
+
+static struct entry* entry_new(void) {
+ struct entry *r = pa_xnew0(struct entry, 1);
+ r->version = ENTRY_VERSION;
+ return r;
+}
+
+static void entry_free(struct entry* e) {
+ pa_assert(e);
+
+ pa_xfree(e->device);
+ pa_xfree(e->card);
+ pa_xfree(e);
+}
+
+static pa_bool_t entry_write(struct userdata *u, const char *name, const struct entry *e, pa_bool_t replace) {
+ pa_tagstruct *t;
+ pa_datum key, data;
+ pa_bool_t r;
+
+ pa_assert(u);
+ pa_assert(name);
+ pa_assert(e);
+
+ t = pa_tagstruct_new(NULL, 0);
+ pa_tagstruct_putu8(t, e->version);
+ pa_tagstruct_put_boolean(t, e->volume_valid);
+ pa_tagstruct_put_channel_map(t, &e->channel_map);
+ pa_tagstruct_put_cvolume(t, &e->volume);
+ pa_tagstruct_put_boolean(t, e->muted_valid);
+ pa_tagstruct_put_boolean(t, e->muted);
+ pa_tagstruct_put_boolean(t, e->device_valid);
+ pa_tagstruct_puts(t, e->device);
+ pa_tagstruct_put_boolean(t, e->card_valid);
+ pa_tagstruct_puts(t, e->card);
+
+ key.data = (char *) name;
+ key.size = strlen(name);
+
+ data.data = (void*)pa_tagstruct_data(t, &data.size);
+
+ r = (pa_database_set(u->database, &key, &data, replace) == 0);
+
+ pa_tagstruct_free(t);
+
+ return r;
+}
+
+#ifdef ENABLE_LEGACY_DATABASE_ENTRY_FORMAT
+
+#define LEGACY_ENTRY_VERSION 3
+static struct entry *legacy_entry_read(struct userdata *u, const char *name) {
+ struct legacy_entry {
+ uint8_t version;
+ pa_bool_t muted_valid:1, volume_valid:1, device_valid:1, card_valid:1;
+ pa_bool_t muted:1;
+ pa_channel_map channel_map;
+ pa_cvolume volume;
+ char device[PA_NAME_MAX];
+ char card[PA_NAME_MAX];
+ } PA_GCC_PACKED;
+
+ pa_datum key;
+ pa_datum data;
+ struct legacy_entry *le;
+ struct entry *e;
+
+ pa_assert(u);
+ pa_assert(name);
+
+ key.data = (char *) name;
+ key.size = strlen(name);
+
+ pa_zero(data);
+
+ if (!pa_database_get(u->database, &key, &data))
+ goto fail;
+
+ if (data.size != sizeof(struct legacy_entry)) {
+ pa_log_debug("Size does not match.");
+ goto fail;
+ }
+
+ le = (struct legacy_entry *) data.data;
+
+ if (le->version != LEGACY_ENTRY_VERSION) {
+ pa_log_debug("Version mismatch.");
+ goto fail;
+ }
+
+ if (!memchr(le->device, 0, sizeof(le->device))) {
+ pa_log_warn("Device has missing NUL byte.");
+ goto fail;
+ }
+
+ if (!memchr(le->card, 0, sizeof(le->card))) {
+ pa_log_warn("Card has missing NUL byte.");
+ goto fail;
+ }
+
+ if (le->device_valid && !pa_namereg_is_valid_name(le->device)) {
+ pa_log_warn("Invalid device name stored in database for legacy stream");
+ goto fail;
+ }
+
+ if (le->card_valid && !pa_namereg_is_valid_name(le->card)) {
+ pa_log_warn("Invalid card name stored in database for legacy stream");
+ goto fail;
+ }
+
+ if (le->volume_valid && !pa_channel_map_valid(&le->channel_map)) {
+ pa_log_warn("Invalid channel map stored in database for legacy stream");
+ goto fail;
+ }
+
+ if (le->volume_valid && (!pa_cvolume_valid(&le->volume) || !pa_cvolume_compatible_with_channel_map(&le->volume, &le->channel_map))) {
+ pa_log_warn("Invalid volume stored in database for legacy stream");
+ goto fail;
+ }
+
+ e = entry_new();
+ e->muted_valid = le->muted_valid;
+ e->muted = le->muted;
+ e->volume_valid = le->volume_valid;
+ e->channel_map = le->channel_map;
+ e->volume = le->volume;
+ e->device_valid = le->device_valid;
+ e->device = pa_xstrdup(le->device);
+ e->card_valid = le->card_valid;
+ e->card = pa_xstrdup(le->card);
+ return e;
+
+fail:
+ pa_datum_free(&data);
+
+ return NULL;
+}
+#endif
+
+static struct entry *entry_read(struct userdata *u, const char *name) {
+ pa_datum key, data;
+ struct entry *e = NULL;
+ pa_tagstruct *t = NULL;
+ const char *device, *card;
+
+ pa_assert(u);
+ pa_assert(name);
+
+ key.data = (char*) name;
+ key.size = strlen(name);
+
+ pa_zero(data);
+
+ if (!pa_database_get(u->database, &key, &data))
+ goto fail;
+
+ t = pa_tagstruct_new(data.data, data.size);
+ e = entry_new();
+
+ if (pa_tagstruct_getu8(t, &e->version) < 0 ||
+ e->version > ENTRY_VERSION ||
+ pa_tagstruct_get_boolean(t, &e->volume_valid) < 0 ||
+ pa_tagstruct_get_channel_map(t, &e->channel_map) < 0 ||
+ pa_tagstruct_get_cvolume(t, &e->volume) < 0 ||
+ pa_tagstruct_get_boolean(t, &e->muted_valid) < 0 ||
+ pa_tagstruct_get_boolean(t, &e->muted) < 0 ||
+ pa_tagstruct_get_boolean(t, &e->device_valid) < 0 ||
+ pa_tagstruct_gets(t, &device) < 0 ||
+ pa_tagstruct_get_boolean(t, &e->card_valid) < 0 ||
+ pa_tagstruct_gets(t, &card) < 0) {
+
+ goto fail;
+ }
+
+ e->device = pa_xstrdup(device);
+ e->card = pa_xstrdup(card);
+
+ if (!pa_tagstruct_eof(t))
+ goto fail;
+
+ if (e->device_valid && !pa_namereg_is_valid_name(e->device)) {
+ pa_log_warn("Invalid device name stored in database for stream %s", name);
+ goto fail;
+ }
+
+ if (e->card_valid && !pa_namereg_is_valid_name(e->card)) {
+ pa_log_warn("Invalid card name stored in database for stream %s", name);
+ goto fail;
+ }
+
+ if (e->volume_valid && !pa_channel_map_valid(&e->channel_map)) {
+ pa_log_warn("Invalid channel map stored in database for stream %s", name);
+ goto fail;
+ }
+
+ if (e->volume_valid && (!pa_cvolume_valid(&e->volume) || !pa_cvolume_compatible_with_channel_map(&e->volume, &e->channel_map))) {
+ pa_log_warn("Invalid volume stored in database for stream %s", name);
+ goto fail;
+ }
+
+ pa_tagstruct_free(t);
+ pa_datum_free(&data);
+
+ return e;
+
+fail:
+ if (e)
+ entry_free(e);
+ if (t)
+ pa_tagstruct_free(t);
+
+ pa_datum_free(&data);
+ return NULL;
+}
+
+static struct entry* entry_copy(const struct entry *e) {
+ struct entry* r;
+
+ pa_assert(e);
+ r = entry_new();
+ *r = *e;
+ r->device = pa_xstrdup(e->device);
+ r->card = pa_xstrdup(e->card);
+ return r;
+}
+
+static void trigger_save(struct userdata *u) {
+ pa_native_connection *c;
+ uint32_t idx;
+
+ PA_IDXSET_FOREACH(c, u->subscribed, idx) {
+ pa_tagstruct *t;
+
+ t = pa_tagstruct_new(NULL, 0);
+ pa_tagstruct_putu32(t, PA_COMMAND_EXTENSION);
+ pa_tagstruct_putu32(t, 0);
+ pa_tagstruct_putu32(t, u->module->index);
+ pa_tagstruct_puts(t, u->module->name);
+ pa_tagstruct_putu32(t, SUBCOMMAND_EVENT);
+
+ pa_pstream_send_tagstruct(pa_native_connection_get_pstream(c), t);
+ }
+
+ if (u->save_time_event)
+ return;
+
+ u->save_time_event = pa_core_rttime_new(u->core, pa_rtclock_now() + SAVE_INTERVAL, save_time_callback, u);
+}
+
+static pa_bool_t entries_equal(const struct entry *a, const struct entry *b) {
+ pa_cvolume t;
+
+ pa_assert(a);
+ pa_assert(b);
+
+ if (a->device_valid != b->device_valid ||
+ (a->device_valid && !pa_streq(a->device, b->device)))
+ return FALSE;
+
+ if (a->card_valid != b->card_valid ||
+ (a->card_valid && !pa_streq(a->card, b->card)))