]> code.delx.au - pulseaudio/blobdiff - src/modules/module-device-manager.c
device-manager: Don't try to use unlinked devices
[pulseaudio] / src / modules / module-device-manager.c
index 8d61ff4c8464bdec7624968dafc4ad51f7259d79..d86c158d1c6c5eeed7df6c619e9df628aca8bc79 100644 (file)
 #include <sys/types.h>
 #include <stdio.h>
 #include <stdlib.h>
-#include <ctype.h>
 
+#include <pulse/gccmacro.h>
 #include <pulse/xmalloc.h>
-#include <pulse/volume.h>
 #include <pulse/timeval.h>
-#include <pulse/util.h>
 #include <pulse/rtclock.h>
 
 #include <pulsecore/core-error.h>
 #include <pulsecore/pstream.h>
 #include <pulsecore/pstream-util.h>
 #include <pulsecore/database.h>
+#include <pulsecore/tagstruct.h>
 
 #include "module-device-manager-symdef.h"
 
 PA_MODULE_AUTHOR("Colin Guthrie");
 PA_MODULE_DESCRIPTION("Keep track of devices (and their descriptions) both past and present and prioritise by role");
 PA_MODULE_VERSION(PACKAGE_VERSION);
-PA_MODULE_LOAD_ONCE(TRUE);
+PA_MODULE_LOAD_ONCE(true);
 PA_MODULE_USAGE(
     "do_routing=<Automatically route streams based on a priority list (unique per-role)?> "
     "on_hotplug=<When new device becomes available, recheck streams?> "
@@ -84,6 +83,7 @@ enum {
     ROLE_ANIMATION,
     ROLE_PRODUCTION,
     ROLE_A11Y,
+    ROLE_MAX
 };
 
 typedef uint32_t role_indexes_t[NUM_ROLES];
@@ -120,9 +120,9 @@ struct userdata {
     pa_native_protocol *protocol;
     pa_idxset *subscribed;
 
-    pa_bool_t on_hotplug;
-    pa_bool_t on_rescue;
-    pa_bool_t do_routing;
+    bool on_hotplug;
+    bool on_rescue;
+    bool do_routing;
 
     role_indexes_t preferred_sinks;
     role_indexes_t preferred_sources;
@@ -132,11 +132,11 @@ struct userdata {
 
 struct entry {
     uint8_t version;
-    char description[PA_NAME_MAX];
-    pa_bool_t user_set_description;
-    char icon[PA_NAME_MAX];
+    char *description;
+    bool user_set_description;
+    char *icon;
     role_indexes_t priority;
-} PA_GCC_PACKED;
+};
 
 enum {
     SUBCOMMAND_TEST,
@@ -149,11 +149,143 @@ enum {
     SUBCOMMAND_EVENT
 };
 
+/* Forward declarations */
+#ifdef DUMP_DATABASE
+static void dump_database(struct userdata *);
+#endif
+static void notify_subscribers(struct userdata *);
+
+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.");
+
+#ifdef DUMP_DATABASE
+    dump_database(u);
+#endif
+}
+
+static void trigger_save(struct userdata *u) {
+
+    pa_assert(u);
+
+    notify_subscribers(u);
+
+    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 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->description);
+    pa_xfree(e->icon);
+    pa_xfree(e);
+}
 
-static struct entry* read_entry(struct userdata *u, const char *name) {
+static bool entry_write(struct userdata *u, const char *name, const struct entry *e) {
+    pa_tagstruct *t;
     pa_datum key, data;
+    bool r;
+
+    pa_assert(u);
+    pa_assert(name);
+    pa_assert(e);
+
+    t = pa_tagstruct_new(NULL, 0);
+    pa_tagstruct_putu8(t, e->version);
+    pa_tagstruct_puts(t, e->description);
+    pa_tagstruct_put_boolean(t, e->user_set_description);
+    pa_tagstruct_puts(t, e->icon);
+    for (uint8_t i=0; i<ROLE_MAX; ++i)
+        pa_tagstruct_putu32(t, e->priority[i]);
+
+    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, true) == 0);
+
+    pa_tagstruct_free(t);
+
+    return r;
+}
+
+#ifdef ENABLE_LEGACY_DATABASE_ENTRY_FORMAT
+
+#define LEGACY_ENTRY_VERSION 1
+static struct entry* legacy_entry_read(struct userdata *u, pa_datum *data) {
+    struct legacy_entry {
+        uint8_t version;
+        char description[PA_NAME_MAX];
+        bool user_set_description;
+        char icon[PA_NAME_MAX];
+        role_indexes_t priority;
+    } PA_GCC_PACKED;
+    struct legacy_entry *le;
     struct entry *e;
 
+    pa_assert(u);
+    pa_assert(data);
+
+    if (data->size != sizeof(struct legacy_entry)) {
+        pa_log_debug("Size does not match.");
+        return NULL;
+    }
+
+    le = (struct legacy_entry*)data->data;
+
+    if (le->version != LEGACY_ENTRY_VERSION) {
+        pa_log_debug("Version mismatch.");
+        return NULL;
+    }
+
+    if (!memchr(le->description, 0, sizeof(le->description))) {
+        pa_log_warn("Description has missing NUL byte.");
+        return NULL;
+    }
+
+    if (!le->description[0]) {
+        pa_log_warn("Description is empty.");
+        return NULL;
+    }
+
+    if (!memchr(le->icon, 0, sizeof(le->icon))) {
+        pa_log_warn("Icon has missing NUL byte.");
+        return NULL;
+    }
+
+    e = entry_new();
+    e->description = pa_xstrdup(le->description);
+    e->icon = pa_xstrdup(le->icon);
+    return e;
+}
+#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 *description, *icon;
+
     pa_assert(u);
     pa_assert(name);
 
@@ -165,38 +297,70 @@ static struct entry* read_entry(struct userdata *u, const char *name) {
     if (!pa_database_get(u->database, &key, &data))
         goto fail;
 
-    if (data.size != sizeof(struct entry)) {
-        pa_log_debug("Database contains entry for device %s of wrong size %lu != %lu. Probably due to upgrade, ignoring.", name, (unsigned long) data.size, (unsigned long) sizeof(struct entry));
-        goto fail;
-    }
+    t = pa_tagstruct_new(data.data, data.size);
+    e = entry_new();
 
-    e = (struct entry*) data.data;
+    if (pa_tagstruct_getu8(t, &e->version) < 0 ||
+        e->version > ENTRY_VERSION ||
+        pa_tagstruct_gets(t, &description) < 0 ||
+        pa_tagstruct_get_boolean(t, &e->user_set_description) < 0 ||
+        pa_tagstruct_gets(t, &icon) < 0) {
 
-    if (e->version != ENTRY_VERSION) {
-        pa_log_debug("Version of database entry for device %s doesn't match our version. Probably due to upgrade, ignoring.", name);
         goto fail;
     }
 
-    if (!memchr(e->description, 0, sizeof(e->description))) {
-        pa_log_warn("Database contains entry for device %s with missing NUL byte in description", name);
+    if (e->user_set_description && !description) {
+        pa_log("Entry has user_set_description set, but the description is NULL.");
         goto fail;
     }
 
-    if (!memchr(e->icon, 0, sizeof(e->icon))) {
-        pa_log_warn("Database contains entry for device %s with missing NUL byte in icon", name);
+    if (e->user_set_description && !*description) {
+        pa_log("Entry has user_set_description set, but the description is empty.");
         goto fail;
     }
 
+    e->description = pa_xstrdup(description);
+    e->icon = pa_xstrdup(icon);
+
+    for (uint8_t i=0; i<ROLE_MAX; ++i) {
+        if (pa_tagstruct_getu32(t, &e->priority[i]) < 0)
+            goto fail;
+    }
+
+    if (!pa_tagstruct_eof(t))
+        goto fail;
+
+    pa_tagstruct_free(t);
+    pa_datum_free(&data);
+
     return e;
 
 fail:
+    pa_log_debug("Database contains invalid data for key: %s (probably pre-v1.0 data)", name);
+
+    if (e)
+        entry_free(e);
+    if (t)
+        pa_tagstruct_free(t);
+
+#ifdef ENABLE_LEGACY_DATABASE_ENTRY_FORMAT
+    pa_log_debug("Attempting to load legacy (pre-v1.0) data for key: %s", name);
+    if ((e = legacy_entry_read(u, &data))) {
+        pa_log_debug("Success. Saving new format for key: %s", name);
+        if (entry_write(u, name, e))
+            trigger_save(u);
+        pa_datum_free(&data);
+        return e;
+    } else
+        pa_log_debug("Unable to load legacy (pre-v1.0) data for key: %s. Ignoring.", name);
+#endif
 
     pa_datum_free(&data);
     return NULL;
 }
 
 #ifdef DUMP_DATABASE
-static void dump_database_helper(struct userdata *u, uint32_t role_index, const char* human, pa_bool_t sink_mode) {
+static void dump_database_helper(struct userdata *u, uint32_t role_index, const char* human, bool sink_mode) {
     pa_assert(u);
     pa_assert(human);
 
@@ -217,7 +381,7 @@ static void dump_database_helper(struct userdata *u, uint32_t role_index, const
 
 static void dump_database(struct userdata *u) {
     pa_datum key;
-    pa_bool_t done;
+    bool done;
 
     pa_assert(u);
 
@@ -233,14 +397,14 @@ static void dump_database(struct userdata *u) {
 
         name = pa_xstrndup(key.data, key.size);
 
-        if ((e = read_entry(u, name))) {
+        if ((e = entry_read(u, name))) {
             pa_log_debug(" Got entry: %s", name);
             pa_log_debug("  Description: %s", e->description);
             pa_log_debug("  Priorities: None:   %3u, Video: %3u, Music:  %3u, Game: %3u, Event: %3u",
                          e->priority[ROLE_NONE], e->priority[ROLE_VIDEO], e->priority[ROLE_MUSIC], e->priority[ROLE_GAME], e->priority[ROLE_EVENT]);
             pa_log_debug("              Phone:  %3u, Anim:  %3u, Prodtn: %3u, A11y: %3u",
                          e->priority[ROLE_PHONE], e->priority[ROLE_ANIMATION], e->priority[ROLE_PRODUCTION], e->priority[ROLE_A11Y]);
-            pa_xfree(e);
+            entry_free(e);
         }
 
         pa_xfree(name);
@@ -259,7 +423,7 @@ static void dump_database(struct userdata *u) {
             strncpy(name, role_names[role], len);
             for (int i = len+1; i < 12; ++i) name[i] = ' ';
             name[len] = ':'; name[0] -= 32; name[12] = '\0';
-            dump_database_helper(u, role, name, TRUE);
+            dump_database_helper(u, role, name, true);
         }
 
         pa_log_debug("  Sources:");
@@ -269,7 +433,7 @@ static void dump_database(struct userdata *u) {
             strncpy(name, role_names[role], len);
             for (int i = len+1; i < 12; ++i) name[i] = ' ';
             name[len] = ':'; name[0] -= 32; name[12] = '\0';
-            dump_database_helper(u, role, name, FALSE);
+            dump_database_helper(u, role, name, false);
         }
     }
 
@@ -277,25 +441,6 @@ static void dump_database(struct userdata *u) {
 }
 #endif
 
-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.");
-
-#ifdef DUMP_DATABASE
-    dump_database(u);
-#endif
-}
-
 static void notify_subscribers(struct userdata *u) {
 
     pa_native_connection *c;
@@ -303,7 +448,7 @@ static void notify_subscribers(struct userdata *u) {
 
     pa_assert(u);
 
-    for (c = pa_idxset_first(u->subscribed, &idx); c; c = pa_idxset_next(u->subscribed, &idx)) {
+    PA_IDXSET_FOREACH(c, u->subscribed, idx) {
         pa_tagstruct *t;
 
         t = pa_tagstruct_new(NULL, 0);
@@ -317,33 +462,21 @@ static void notify_subscribers(struct userdata *u) {
     }
 }
 
-static void trigger_save(struct userdata *u) {
-
-    pa_assert(u);
-
-    notify_subscribers(u);
-
-    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) {
+static bool entries_equal(const struct entry *a, const struct entry *b) {
 
     pa_assert(a);
     pa_assert(b);
 
-    if (strncmp(a->description, b->description, sizeof(a->description))
+    if (!pa_streq(a->description, b->description)
         || a->user_set_description != b->user_set_description
-        || strncmp(a->icon, b->icon, sizeof(a->icon)))
-        return FALSE;
+        || !pa_streq(a->icon, b->icon))
+        return false;
 
     for (int i=0; i < NUM_ROLES; ++i)
         if (a->priority[i] != b->priority[i])
-            return FALSE;
+            return false;
 
-    return TRUE;
+    return true;
 }
 
 static char *get_name(const char *key, const char *prefix) {
@@ -364,13 +497,15 @@ static inline struct entry *load_or_initialize_entry(struct userdata *u, struct
     pa_assert(name);
     pa_assert(prefix);
 
-    if ((old = read_entry(u, name)))
+    if ((old = entry_read(u, name))) {
         *entry = *old;
-    else {
+        entry->description = pa_xstrdup(old->description);
+        entry->icon = pa_xstrdup(old->icon);
+    } else {
         /* This is a new device, so make sure we write it's priority list correctly */
         role_indexes_t max_priority;
         pa_datum key;
-        pa_bool_t done;
+        bool done;
 
         pa_zero(max_priority);
         done = !pa_database_first(u->database, &key, NULL);
@@ -387,12 +522,12 @@ static inline struct entry *load_or_initialize_entry(struct userdata *u, struct
 
                 name2 = pa_xstrndup(key.data, key.size);
 
-                if ((e = read_entry(u, name2))) {
+                if ((e = entry_read(u, name2))) {
                     for (uint32_t i = 0; i < NUM_ROLES; ++i) {
                         max_priority[i] = PA_MAX(max_priority[i], e->priority[i]);
                     }
 
-                    pa_xfree(e);
+                    entry_free(e);
                 }
 
                 pa_xfree(name2);
@@ -405,7 +540,7 @@ static inline struct entry *load_or_initialize_entry(struct userdata *u, struct
         for (uint32_t i = 0; i < NUM_ROLES; ++i) {
             entry->priority[i] = max_priority[i] + 1;
         }
-        entry->user_set_description = FALSE;
+        entry->user_set_description = false;
     }
 
     return old;
@@ -415,7 +550,7 @@ static uint32_t get_role_index(const char* role) {
     pa_assert(role);
 
     for (uint32_t i = ROLE_NONE; i < NUM_ROLES; ++i)
-        if (strcmp(role, role_names[i]) == 0)
+        if (pa_streq(role, role_names[i]))
             return i;
 
     return PA_INVALID_INDEX;
@@ -424,12 +559,12 @@ static uint32_t get_role_index(const char* role) {
 static void update_highest_priority_device_indexes(struct userdata *u, const char *prefix, void *ignore_device) {
     role_indexes_t *indexes, highest_priority_available;
     pa_datum key;
-    pa_bool_t done, sink_mode;
+    bool done, sink_mode;
 
     pa_assert(u);
     pa_assert(prefix);
 
-    sink_mode = (strcmp(prefix, "sink:") == 0);
+    sink_mode = pa_streq(prefix, "sink:");
 
     if (sink_mode)
         indexes = &u->preferred_sinks;
@@ -454,15 +589,15 @@ static void update_highest_priority_device_indexes(struct userdata *u, const cha
             struct entry *e;
 
             name = pa_xstrndup(key.data, key.size);
-            device_name = get_name(name, prefix);
+            pa_assert_se(device_name = get_name(name, prefix));
 
-            if ((e = read_entry(u, name))) {
+            if ((e = entry_read(u, name))) {
                 for (uint32_t i = 0; i < NUM_ROLES; ++i) {
                     if (!highest_priority_available[i] || e->priority[i] < highest_priority_available[i]) {
                         /* We've found a device with a higher priority than that we've currently got,
                            so see if it is currently available or not and update our list */
                         uint32_t idx;
-                        pa_bool_t found = FALSE;
+                        bool found = false;
 
                         if (sink_mode) {
                             pa_sink *sink;
@@ -470,8 +605,10 @@ static void update_highest_priority_device_indexes(struct userdata *u, const cha
                             PA_IDXSET_FOREACH(sink, u->core->sinks, idx) {
                                 if ((pa_sink*) ignore_device == sink)
                                     continue;
-                                if (strcmp(sink->name, device_name) == 0) {
-                                    found = TRUE;
+                                if (!PA_SINK_IS_LINKED(sink->state))
+                                    continue;
+                                if (pa_streq(sink->name, device_name)) {
+                                    found = true;
                                     idx = sink->index; /* Is this needed? */
                                     break;
                                 }
@@ -482,8 +619,10 @@ static void update_highest_priority_device_indexes(struct userdata *u, const cha
                             PA_IDXSET_FOREACH(source, u->core->sources, idx) {
                                 if ((pa_source*) ignore_device == source)
                                     continue;
-                                if (strcmp(source->name, device_name) == 0) {
-                                    found = TRUE;
+                                if (!PA_SOURCE_IS_LINKED(source->state))
+                                    continue;
+                                if (pa_streq(source->name, device_name)) {
+                                    found = true;
                                     idx = source->index; /* Is this needed? */
                                     break;
                                 }
@@ -497,7 +636,7 @@ static void update_highest_priority_device_indexes(struct userdata *u, const cha
                     }
                 }
 
-                pa_xfree(e);
+                entry_free(e);
             }
 
             pa_xfree(name);
@@ -509,7 +648,6 @@ static void update_highest_priority_device_indexes(struct userdata *u, const cha
     }
 }
 
-
 static void route_sink_input(struct userdata *u, pa_sink_input *si) {
     const char *role;
     uint32_t role_index, device_index;
@@ -547,7 +685,7 @@ static void route_sink_input(struct userdata *u, pa_sink_input *si) {
         return;
 
     if (si->sink != sink)
-        pa_sink_input_move_to(si, sink, FALSE);
+        pa_sink_input_move_to(si, sink, false);
 }
 
 static pa_hook_result_t route_sink_inputs(struct userdata *u, pa_sink *ignore_sink) {
@@ -608,7 +746,7 @@ static void route_source_output(struct userdata *u, pa_source_output *so) {
         return;
 
     if (so->source != source)
-        pa_source_output_move_to(so, source, FALSE);
+        pa_source_output_move_to(so, source, false);
 }
 
 static pa_hook_result_t route_source_outputs(struct userdata *u, pa_source* ignore_source) {
@@ -631,9 +769,8 @@ static pa_hook_result_t route_source_outputs(struct userdata *u, pa_source* igno
 
 static void subscribe_callback(pa_core *c, pa_subscription_event_type_t t, uint32_t idx, void *userdata) {
     struct userdata *u = userdata;
-    struct entry entry, *old = NULL;
+    struct entry *entry, *old = NULL;
     char *name = NULL;
-    pa_datum key, data;
 
     pa_assert(c);
     pa_assert(u);
@@ -649,9 +786,6 @@ static void subscribe_callback(pa_core *c, pa_subscription_event_type_t t, uint3
         t != (PA_SUBSCRIPTION_EVENT_SOURCE_OUTPUT|PA_SUBSCRIPTION_EVENT_CHANGE))
         return;
 
-    pa_zero(entry);
-    entry.version = ENTRY_VERSION;
-
     if ((t & PA_SUBSCRIPTION_EVENT_FACILITY_MASK) == PA_SUBSCRIPTION_EVENT_SINK_INPUT) {
         pa_sink_input *si;
 
@@ -682,23 +816,26 @@ static void subscribe_callback(pa_core *c, pa_subscription_event_type_t t, uint3
         if (!(sink = pa_idxset_get_by_index(c->sinks, idx)))
             return;
 
+        entry = entry_new();
         name = pa_sprintf_malloc("sink:%s", sink->name);
 
-        old = load_or_initialize_entry(u, &entry, name, "sink:");
+        old = load_or_initialize_entry(u, entry, name, "sink:");
 
-        if (!entry.user_set_description)
-            pa_strlcpy(entry.description, pa_strnull(pa_proplist_gets(sink->proplist, PA_PROP_DEVICE_DESCRIPTION)), sizeof(entry.description));
-        else if (strncmp(entry.description, pa_strnull(pa_proplist_gets(sink->proplist, PA_PROP_DEVICE_DESCRIPTION)), sizeof(entry.description)) != 0) {
+        if (!entry->user_set_description) {
+            pa_xfree(entry->description);
+            entry->description = pa_xstrdup(pa_proplist_gets(sink->proplist, PA_PROP_DEVICE_DESCRIPTION));
+        } else if (!pa_streq(entry->description, pa_proplist_gets(sink->proplist, PA_PROP_DEVICE_DESCRIPTION))) {
             /* Warning: If two modules fight over the description, this could cause an infinite loop.
                by changing the description here, we retrigger this subscription callback. The only thing stopping us from
                looping is the fact that the string comparison will fail on the second iteration. If another module tries to manage
                the description, this will fail... */
-            pa_sink_set_description(sink, entry.description);
+            pa_sink_set_description(sink, entry->description);
         }
 
-        pa_strlcpy(entry.icon, pa_strnull(pa_proplist_gets(sink->proplist, PA_PROP_DEVICE_ICON_NAME)), sizeof(entry.icon));
+        pa_xfree(entry->icon);
+        entry->icon = pa_xstrdup(pa_proplist_gets(sink->proplist, PA_PROP_DEVICE_ICON_NAME));
 
-    } else  if ((t & PA_SUBSCRIPTION_EVENT_FACILITY_MASK) == PA_SUBSCRIPTION_EVENT_SOURCE) {
+    } else if ((t & PA_SUBSCRIPTION_EVENT_FACILITY_MASK) == PA_SUBSCRIPTION_EVENT_SOURCE) {
         pa_source *source;
 
         pa_assert((t & PA_SUBSCRIPTION_EVENT_FACILITY_MASK) == PA_SUBSCRIPTION_EVENT_SOURCE);
@@ -709,50 +846,51 @@ static void subscribe_callback(pa_core *c, pa_subscription_event_type_t t, uint3
         if (source->monitor_of)
             return;
 
+        entry = entry_new();
         name = pa_sprintf_malloc("source:%s", source->name);
 
-        old = load_or_initialize_entry(u, &entry, name, "source:");
+        old = load_or_initialize_entry(u, entry, name, "source:");
 
-        if (!entry.user_set_description)
-            pa_strlcpy(entry.description, pa_strnull(pa_proplist_gets(source->proplist, PA_PROP_DEVICE_DESCRIPTION)), sizeof(entry.description));
-        else if (strncmp(entry.description, pa_strnull(pa_proplist_gets(source->proplist, PA_PROP_DEVICE_DESCRIPTION)), sizeof(entry.description)) != 0) {
+        if (!entry->user_set_description) {
+            pa_xfree(entry->description);
+            entry->description = pa_xstrdup(pa_proplist_gets(source->proplist, PA_PROP_DEVICE_DESCRIPTION));
+        } else if (!pa_streq(entry->description, pa_proplist_gets(source->proplist, PA_PROP_DEVICE_DESCRIPTION))) {
             /* Warning: If two modules fight over the description, this could cause an infinite loop.
                by changing the description here, we retrigger this subscription callback. The only thing stopping us from
                looping is the fact that the string comparison will fail on the second iteration. If another module tries to manage
                the description, this will fail... */
-            pa_source_set_description(source, entry.description);
+            pa_source_set_description(source, entry->description);
         }
 
-        pa_strlcpy(entry.icon, pa_strnull(pa_proplist_gets(source->proplist, PA_PROP_DEVICE_ICON_NAME)), sizeof(entry.icon));
+        pa_xfree(entry->icon);
+        entry->icon = pa_xstrdup(pa_proplist_gets(source->proplist, PA_PROP_DEVICE_ICON_NAME));
+    } else {
+        pa_assert_not_reached();
     }
 
     pa_assert(name);
 
     if (old) {
 
-        if (entries_equal(old, &entry)) {
-            pa_xfree(old);
+        if (entries_equal(old, entry)) {
+            entry_free(old);
+            entry_free(entry);
             pa_xfree(name);
 
             return;
         }
 
-        pa_xfree(old);
+        entry_free(old);
     }
 
-    key.data = name;
-    key.size = strlen(name);
-
-    data.data = &entry;
-    data.size = sizeof(entry);
-
     pa_log_info("Storing device %s.", name);
 
-    if (pa_database_set(u->database, &key, &data, TRUE) == 0)
+    if (entry_write(u, name, entry))
         trigger_save(u);
     else
         pa_log_warn("Could not save device");;
 
+    entry_free(entry);
     pa_xfree(name);
 }
 
@@ -766,13 +904,13 @@ static pa_hook_result_t sink_new_hook_callback(pa_core *c, pa_sink_new_data *new
 
     name = pa_sprintf_malloc("sink:%s", new_data->name);
 
-    if ((e = read_entry(u, name))) {
-        if (e->user_set_description && strncmp(e->description, pa_proplist_gets(new_data->proplist, PA_PROP_DEVICE_DESCRIPTION), sizeof(e->description)) != 0) {
+    if ((e = entry_read(u, name))) {
+        if (e->user_set_description && !pa_safe_streq(e->description, pa_proplist_gets(new_data->proplist, PA_PROP_DEVICE_DESCRIPTION))) {
             pa_log_info("Restoring description for sink %s.", new_data->name);
             pa_proplist_sets(new_data->proplist, PA_PROP_DEVICE_DESCRIPTION, e->description);
         }
 
-        pa_xfree(e);
+        entry_free(e);
     }
 
     pa_xfree(name);
@@ -790,14 +928,14 @@ static pa_hook_result_t source_new_hook_callback(pa_core *c, pa_source_new_data
 
     name = pa_sprintf_malloc("source:%s", new_data->name);
 
-    if ((e = read_entry(u, name))) {
-        if (e->user_set_description && strncmp(e->description, pa_proplist_gets(new_data->proplist, PA_PROP_DEVICE_DESCRIPTION), sizeof(e->description)) != 0) {
+    if ((e = entry_read(u, name))) {
+        if (e->user_set_description && !pa_safe_streq(e->description, pa_proplist_gets(new_data->proplist, PA_PROP_DEVICE_DESCRIPTION))) {
             /* NB, We cannot detect if we are a monitor here... this could mess things up a bit... */
             pa_log_info("Restoring description for source %s.", new_data->name);
             pa_proplist_sets(new_data->proplist, PA_PROP_DEVICE_DESCRIPTION, e->description);
         }
 
-        pa_xfree(e);
+        entry_free(e);
     }
 
     pa_xfree(name);
@@ -832,8 +970,8 @@ static pa_hook_result_t sink_input_new_hook_callback(pa_core *c, pa_sink_input_n
                 pa_sink *sink;
 
                 if ((sink = pa_idxset_get_by_index(u->core->sinks, device_index))) {
-                    new_data->sink = sink;
-                    new_data->save_sink = FALSE;
+                    if (!pa_sink_input_new_data_set_sink(new_data, sink, false))
+                        pa_log_debug("Not restoring device for stream because no supported format was found");
                 }
             }
         }
@@ -871,10 +1009,9 @@ static pa_hook_result_t source_output_new_hook_callback(pa_core *c, pa_source_ou
             if (PA_INVALID_INDEX != device_index) {
                 pa_source *source;
 
-                if ((source = pa_idxset_get_by_index(u->core->sources, device_index))) {
-                    new_data->source = source;
-                    new_data->save_source = FALSE;
-                }
+                if ((source = pa_idxset_get_by_index(u->core->sources, device_index)))
+                    if (!pa_source_output_new_data_set_source(new_data, source, false))
+                        pa_log_debug("Not restoring device for stream because no supported format was found");
             }
         }
     }
@@ -882,7 +1019,6 @@ static pa_hook_result_t source_output_new_hook_callback(pa_core *c, pa_source_ou
     return PA_HOOK_OK;
 }
 
-
 static pa_hook_result_t sink_put_hook_callback(pa_core *c, PA_GCC_UNUSED pa_sink *sink, struct userdata *u) {
     pa_assert(c);
     pa_assert(u);
@@ -937,7 +1073,6 @@ static pa_hook_result_t source_unlink_hook_callback(pa_core *c, pa_source *sourc
     return route_source_outputs(u, source);
 }
 
-
 static void apply_entry(struct userdata *u, const char *name, struct entry *e) {
     uint32_t idx;
     char *n;
@@ -980,7 +1115,6 @@ static void apply_entry(struct userdata *u, const char *name, struct entry *e) {
     }
 }
 
-
 #define EXT_VERSION 1
 
 static int extension_cb(pa_native_protocol *p, pa_module *m, pa_native_connection *c, uint32_t tag, pa_tagstruct *t) {
@@ -1013,7 +1147,7 @@ static int extension_cb(pa_native_protocol *p, pa_module *m, pa_native_connectio
 
     case SUBCOMMAND_READ: {
       pa_datum key;
-      pa_bool_t done;
+      bool done;
 
       if (!pa_tagstruct_eof(t))
         goto fail;
@@ -1030,7 +1164,7 @@ static int extension_cb(pa_native_protocol *p, pa_module *m, pa_native_connectio
         name = pa_xstrndup(key.data, key.size);
         pa_datum_free(&key);
 
-        if ((e = read_entry(u, name))) {
+        if ((e = entry_read(u, name))) {
             uint32_t idx;
             char *device_name;
             uint32_t found_index = PA_INVALID_INDEX;
@@ -1038,7 +1172,7 @@ static int extension_cb(pa_native_protocol *p, pa_module *m, pa_native_connectio
             if ((device_name = get_name(name, "sink:"))) {
                 pa_sink* s;
                 PA_IDXSET_FOREACH(s, u->core->sinks, idx) {
-                    if (strcmp(s->name, device_name) == 0) {
+                    if (pa_streq(s->name, device_name)) {
                         found_index = s->index;
                         break;
                     }
@@ -1047,7 +1181,7 @@ static int extension_cb(pa_native_protocol *p, pa_module *m, pa_native_connectio
             } else if ((device_name = get_name(name, "source:"))) {
                 pa_source* s;
                 PA_IDXSET_FOREACH(s, u->core->sources, idx) {
-                    if (strcmp(s->name, device_name) == 0) {
+                    if (pa_streq(s->name, device_name)) {
                         found_index = s->index;
                         break;
                     }
@@ -1066,7 +1200,7 @@ static int extension_cb(pa_native_protocol *p, pa_module *m, pa_native_connectio
                 pa_tagstruct_putu32(reply, e->priority[i]);
             }
 
-            pa_xfree(e);
+            entry_free(e);
         }
 
         pa_xfree(name);
@@ -1089,19 +1223,12 @@ static int extension_cb(pa_native_protocol *p, pa_module *m, pa_native_connectio
         if (!device || !*device || !description || !*description)
           goto fail;
 
-        if ((e = read_entry(u, device))) {
-            pa_datum key, data;
-
-            pa_strlcpy(e->description, description, sizeof(e->description));
-            e->user_set_description = TRUE;
+        if ((e = entry_read(u, device))) {
+            pa_xfree(e->description);
+            e->description = pa_xstrdup(description);
+            e->user_set_description = true;
 
-            key.data = (char *) device;
-            key.size = strlen(device);
-
-            data.data = e;
-            data.size = sizeof(*e);
-
-            if (pa_database_set(u->database, &key, &data, TRUE) == 0) {
+            if (entry_write(u, (char *)device, e)) {
                 apply_entry(u, device, e);
 
                 trigger_save(u);
@@ -1109,7 +1236,7 @@ static int extension_cb(pa_native_protocol *p, pa_module *m, pa_native_connectio
             else
                 pa_log_warn("Could not save device");
 
-            pa_xfree(e);
+            entry_free(e);
         }
         else
             pa_log_warn("Could not rename device %s, no entry in database", device);
@@ -1139,7 +1266,7 @@ static int extension_cb(pa_native_protocol *p, pa_module *m, pa_native_connectio
 
     case SUBCOMMAND_ROLE_DEVICE_PRIORITY_ROUTING: {
 
-        pa_bool_t enable;
+        bool enable;
 
         if (pa_tagstruct_get_boolean(t, &enable) < 0)
             goto fail;
@@ -1158,15 +1285,15 @@ static int extension_cb(pa_native_protocol *p, pa_module *m, pa_native_connectio
         const char *role;
         struct entry *e;
         uint32_t role_index, n_devices;
-        pa_datum key, data;
-        pa_bool_t done, sink_mode = TRUE;
+        pa_datum key;
+        bool done, sink_mode = true;
         struct device_t { uint32_t prio; char *device; };
         struct device_t *device;
         struct device_t **devices;
         uint32_t i, idx, offset;
         pa_hashmap *h;
         /*void *state;*/
-        pa_bool_t first;
+        bool first;
 
         if (pa_tagstruct_gets(t, &role) < 0 ||
             pa_tagstruct_getu32(t, &n_devices) < 0 ||
@@ -1174,11 +1301,11 @@ static int extension_cb(pa_native_protocol *p, pa_module *m, pa_native_connectio
             goto fail;
 
         if (PA_INVALID_INDEX == (role_index = get_role_index(role)))
-           goto fail;
+            goto fail;
 
         /* Cycle through the devices given and make sure they exist */
         h = pa_hashmap_new(pa_idxset_string_hash_func, pa_idxset_string_compare_func);
-        first = TRUE;
+        first = true;
         idx = 0;
         for (i = 0; i < n_devices; ++i) {
             const char *s;
@@ -1188,41 +1315,39 @@ static int extension_cb(pa_native_protocol *p, pa_module *m, pa_native_connectio
                     pa_xfree(device);
                 }
 
-                pa_hashmap_free(h, NULL, NULL);
+                pa_hashmap_free(h);
                 pa_log_error("Protocol error on reorder");
                 goto fail;
             }
 
             /* Ensure this is a valid entry */
-            if (!(e = read_entry(u, s))) {
+            if (!(e = entry_read(u, s))) {
                 while ((device = pa_hashmap_steal_first(h))) {
                     pa_xfree(device->device);
                     pa_xfree(device);
                 }
 
-                pa_hashmap_free(h, NULL, NULL);
+                pa_hashmap_free(h);
                 pa_log_error("Client specified an unknown device in it's reorder list.");
                 goto fail;
             }
-            pa_xfree(e);
+            entry_free(e);
 
             if (first) {
-                first = FALSE;
+                first = false;
                 sink_mode = (0 == strncmp("sink:", s, 5));
-            } else if ((sink_mode && 0 != strncmp("sink:", s, 5))
-                       || (!sink_mode && 0 != strncmp("source:", s, 7)))
-            {
+            } else if ((sink_mode && 0 != strncmp("sink:", s, 5)) || (!sink_mode && 0 != strncmp("source:", s, 7))) {
                 while ((device = pa_hashmap_steal_first(h))) {
                     pa_xfree(device->device);
                     pa_xfree(device);
                 }
 
-                pa_hashmap_free(h, NULL, NULL);
+                pa_hashmap_free(h);
                 pa_log_error("Attempted to reorder mixed devices (sinks and sources)");
                 goto fail;
             }
 
-            /* Add the device to our hashmap. If it's alredy in it, free it now and carry on */
+            /* Add the device to our hashmap. If it's already in it, free it now and carry on */
             device = pa_xnew(struct device_t, 1);
             device->device = pa_xstrdup(s);
             if (pa_hashmap_put(h, device->device, device) == 0) {
@@ -1240,7 +1365,7 @@ static int extension_cb(pa_native_protocol *p, pa_module *m, pa_native_connectio
         }*/
 
         /* Now cycle through our list and add all the devices.
-           This has the effect of addign in any in our DB,
+           This has the effect of adding in any in our DB,
            not specified in the device list (and thus will be
            tacked on at the end) */
         offset = idx;
@@ -1256,10 +1381,10 @@ static int extension_cb(pa_native_protocol *p, pa_module *m, pa_native_connectio
             if ((sink_mode && 0 == strncmp("sink:", device->device, 5))
                 || (!sink_mode && 0 == strncmp("source:", device->device, 7))) {
 
-                /* Add the device to our hashmap. If it's alredy in it, free it now and carry on */
+                /* Add the device to our hashmap. If it's already in it, free it now and carry on */
                 if (pa_hashmap_put(h, device->device, device) == 0
-                    && (e = read_entry(u, device->device))) {
-                    /* We add offset on to the existing priorirty so that when we order, the
+                    && (e = entry_read(u, device->device))) {
+                    /* We add offset on to the existing priority so that when we order, the
                        existing entries are always lower priority than the new ones. */
                     device->prio = (offset + e->priority[role_index]);
                     pa_xfree(e);
@@ -1290,7 +1415,7 @@ static int extension_cb(pa_native_protocol *p, pa_module *m, pa_native_connectio
         while ((device = pa_hashmap_steal_first(h))) {
             devices[idx++] = device;
         }
-        pa_hashmap_free(h, NULL, NULL);
+        pa_hashmap_free(h);
 
         /* Simple bubble sort */
         for (i = 0; i < n_devices; ++i) {
@@ -1311,22 +1436,16 @@ static int extension_cb(pa_native_protocol *p, pa_module *m, pa_native_connectio
 
         /* Go through in order and write the new entry and cleanup our own list */
         idx = 1;
-        first = TRUE;
+        first = true;
         for (i = 0; i < n_devices; ++i) {
-            if ((e = read_entry(u, devices[i]->device))) {
+            if ((e = entry_read(u, devices[i]->device))) {
                 if (e->priority[role_index] == idx)
                     idx++;
                 else {
                     e->priority[role_index] = idx;
 
-                    key.data = (char *) devices[i]->device;
-                    key.size = strlen(devices[i]->device);
-
-                    data.data = e;
-                    data.size = sizeof(*e);
-
-                    if (pa_database_set(u->database, &key, &data, TRUE) == 0) {
-                        first = FALSE;
+                    if (entry_write(u, (char *) devices[i]->device, e)) {
+                        first = false;
                         idx++;
                     }
                 }
@@ -1337,6 +1456,8 @@ static int extension_cb(pa_native_protocol *p, pa_module *m, pa_native_connectio
             pa_xfree(devices[i]);
         }
 
+        pa_xfree(devices);
+
         if (!first) {
             trigger_save(u);
 
@@ -1351,7 +1472,7 @@ static int extension_cb(pa_native_protocol *p, pa_module *m, pa_native_connectio
 
     case SUBCOMMAND_SUBSCRIBE: {
 
-      pa_bool_t enabled;
+      bool enabled;
 
       if (pa_tagstruct_get_boolean(t, &enabled) < 0 ||
         !pa_tagstruct_eof(t))
@@ -1389,6 +1510,11 @@ static pa_hook_result_t connection_unlink_hook_cb(pa_native_protocol *p, pa_nati
     return PA_HOOK_OK;
 }
 
+struct prioritised_indexes {
+    uint32_t index;
+    int32_t priority;
+};
+
 int pa__init(pa_module*m) {
     pa_modargs *ma = NULL;
     struct userdata *u;
@@ -1396,7 +1522,8 @@ int pa__init(pa_module*m) {
     pa_sink *sink;
     pa_source *source;
     uint32_t idx;
-    pa_bool_t do_routing = FALSE, on_hotplug = TRUE, on_rescue = TRUE;
+    bool do_routing = false, on_hotplug = true, on_rescue = true;
+    uint32_t total_devices;
 
     pa_assert(m);
 
@@ -1448,24 +1575,71 @@ int pa__init(pa_module*m) {
         u->source_unlink_hook_slot = pa_hook_connect(&m->core->hooks[PA_CORE_HOOK_SOURCE_UNLINK], PA_HOOK_LATE+5, (pa_hook_cb_t) source_unlink_hook_callback, u);
     }
 
-    if (!(fname = pa_state_path("device-manager", TRUE)))
+    if (!(fname = pa_state_path("device-manager", true)))
         goto fail;
 
-    if (!(u->database = pa_database_open(fname, TRUE))) {
+    if (!(u->database = pa_database_open(fname, true))) {
         pa_log("Failed to open volume database '%s': %s", fname, pa_cstrerror(errno));
         pa_xfree(fname);
         goto fail;
     }
 
-    pa_log_info("Sucessfully opened database file '%s'.", fname);
+    pa_log_info("Successfully opened database file '%s'.", fname);
     pa_xfree(fname);
 
-    /* We cycle over all the available sinks so that they are added to our database if they are not in it yet */
-    PA_IDXSET_FOREACH(sink, m->core->sinks, idx)
-        subscribe_callback(m->core, PA_SUBSCRIPTION_EVENT_SINK|PA_SUBSCRIPTION_EVENT_NEW, sink->index, u);
+    /* Attempt to inject the devices into the list in priority order */
+    total_devices = PA_MAX(pa_idxset_size(m->core->sinks), pa_idxset_size(m->core->sources));
+    if (total_devices > 0 && total_devices < 128) {
+        uint32_t i;
+        struct prioritised_indexes p_i[128];
+
+        /* We cycle over all the available sinks so that they are added to our database if they are not in it yet */
+        i = 0;
+        PA_IDXSET_FOREACH(sink, m->core->sinks, idx) {
+            pa_log_debug("Found sink index %u", sink->index);
+            p_i[i  ].index = sink->index;
+            p_i[i++].priority = sink->priority;
+        }
+        /* Bubble sort it (only really useful for first time creation) */
+        if (i > 1)
+          for (uint32_t j = 0; j < i; ++j)
+              for (uint32_t k = 0; k < i; ++k)
+                  if (p_i[j].priority > p_i[k].priority) {
+                      struct prioritised_indexes tmp_pi = p_i[k];
+                      p_i[k] = p_i[j];
+                      p_i[j] = tmp_pi;
+                  }
+        /* Register it */
+        for (uint32_t j = 0; j < i; ++j)
+            subscribe_callback(m->core, PA_SUBSCRIPTION_EVENT_SINK|PA_SUBSCRIPTION_EVENT_NEW, p_i[j].index, u);
+
+        /* We cycle over all the available sources so that they are added to our database if they are not in it yet */
+        i = 0;
+        PA_IDXSET_FOREACH(source, m->core->sources, idx) {
+            p_i[i  ].index = source->index;
+            p_i[i++].priority = source->priority;
+        }
+        /* Bubble sort it (only really useful for first time creation) */
+        if (i > 1)
+          for (uint32_t j = 0; j < i; ++j)
+              for (uint32_t k = 0; k < i; ++k)
+                  if (p_i[j].priority > p_i[k].priority) {
+                      struct prioritised_indexes tmp_pi = p_i[k];
+                      p_i[k] = p_i[j];
+                      p_i[j] = tmp_pi;
+                  }
+        /* Register it */
+        for (uint32_t j = 0; j < i; ++j)
+            subscribe_callback(m->core, PA_SUBSCRIPTION_EVENT_SOURCE|PA_SUBSCRIPTION_EVENT_NEW, p_i[j].index, u);
+    }
+    else if (total_devices > 0) {
+        /* This user has a *lot* of devices... */
+        PA_IDXSET_FOREACH(sink, m->core->sinks, idx)
+            subscribe_callback(m->core, PA_SUBSCRIPTION_EVENT_SINK|PA_SUBSCRIPTION_EVENT_NEW, sink->index, u);
 
-    PA_IDXSET_FOREACH(source, m->core->sources, idx)
-        subscribe_callback(m->core, PA_SUBSCRIPTION_EVENT_SOURCE|PA_SUBSCRIPTION_EVENT_NEW, source->index, u);
+        PA_IDXSET_FOREACH(source, m->core->sources, idx)
+            subscribe_callback(m->core, PA_SUBSCRIPTION_EVENT_SOURCE|PA_SUBSCRIPTION_EVENT_NEW, source->index, u);
+    }
 
     /* Perform the routing (if it's enabled) which will update our priority list cache too */
     for (uint32_t i = 0; i < NUM_ROLES; ++i) {
@@ -1488,7 +1662,7 @@ fail:
     if (ma)
         pa_modargs_free(ma);
 
-    return  -1;
+    return -1;
 }
 
 void pa__done(pa_module*m) {
@@ -1522,6 +1696,9 @@ void pa__done(pa_module*m) {
     if (u->source_unlink_hook_slot)
         pa_hook_slot_free(u->source_unlink_hook_slot);
 
+    if (u->connection_unlink_hook_slot)
+        pa_hook_slot_free(u->connection_unlink_hook_slot);
+
     if (u->save_time_event)
         u->core->mainloop->time_free(u->save_time_event);
 
@@ -1534,7 +1711,7 @@ void pa__done(pa_module*m) {
     }
 
     if (u->subscribed)
-        pa_idxset_free(u->subscribed, NULL, NULL);
+        pa_idxset_free(u->subscribed, NULL);
 
     pa_xfree(u);
 }