]> 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 baff560356add6fe9b4d9bcba02bae0fe26cb7b5..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,10 +83,23 @@ enum {
     ROLE_ANIMATION,
     ROLE_PRODUCTION,
     ROLE_A11Y,
+    ROLE_MAX
 };
 
 typedef uint32_t role_indexes_t[NUM_ROLES];
 
+static const char* role_names[NUM_ROLES] = {
+    "none",
+    "video",
+    "music",
+    "game",
+    "event",
+    "phone",
+    "animation",
+    "production",
+    "a11y",
+};
+
 struct userdata {
     pa_core *core;
     pa_module *module;
@@ -108,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;
@@ -120,9 +132,11 @@ struct userdata {
 
 struct entry {
     uint8_t version;
-    char description[PA_NAME_MAX];
+    char *description;
+    bool user_set_description;
+    char *icon;
     role_indexes_t priority;
-} PA_GCC_PACKED;
+};
 
 enum {
     SUBCOMMAND_TEST,
@@ -130,17 +144,148 @@ enum {
     SUBCOMMAND_RENAME,
     SUBCOMMAND_DELETE,
     SUBCOMMAND_ROLE_DEVICE_PRIORITY_ROUTING,
-    SUBCOMMAND_PREFER_DEVICE,
-    SUBCOMMAND_DEFER_DEVICE,
+    SUBCOMMAND_REORDER,
     SUBCOMMAND_SUBSCRIBE,
     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);
 
-static struct entry* read_entry(struct userdata *u, const char *name) {
+    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 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);
 
@@ -152,33 +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));
+    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_gets(t, &description) < 0 ||
+        pa_tagstruct_get_boolean(t, &e->user_set_description) < 0 ||
+        pa_tagstruct_gets(t, &icon) < 0) {
+
         goto fail;
     }
 
-    e = (struct entry*) data.data;
-
-    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);
+    if (e->user_set_description && !description) {
+        pa_log("Entry has user_set_description set, but the description is NULL.");
         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 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);
 
@@ -190,7 +372,7 @@ static void dump_database_helper(struct userdata *u, uint32_t role_index, const
             pa_log_debug("   %s No sink specified", human);
     } else {
         pa_source *s;
-        if (PA_INVALID_INDEX != u->preferred_sinks[role_index] && (s = pa_idxset_get_by_index(u->core->sinks, u->preferred_sinks[role_index])))
+        if (PA_INVALID_INDEX != u->preferred_sources[role_index] && (s = pa_idxset_get_by_index(u->core->sources, u->preferred_sources[role_index])))
             pa_log_debug("   %s %s (%s)", human, pa_strnull(pa_proplist_gets(s->proplist, PA_PROP_DEVICE_DESCRIPTION)), s->name);
         else
             pa_log_debug("   %s No source specified", human);
@@ -199,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);
 
@@ -215,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);
@@ -231,58 +413,42 @@ static void dump_database(struct userdata *u) {
         key = next_key;
     }
 
-    pa_log_debug(" Highest priority devices per-role:");
-
-    pa_log_debug("  Sinks:");
-    dump_database_helper(u, ROLE_NONE, "None:  ", TRUE);
-    dump_database_helper(u, ROLE_NONE, "Video: ", TRUE);
-    dump_database_helper(u, ROLE_NONE, "Music: ", TRUE);
-    dump_database_helper(u, ROLE_NONE, "Game:  ", TRUE);
-    dump_database_helper(u, ROLE_NONE, "Event: ", TRUE);
-    dump_database_helper(u, ROLE_NONE, "Phone: ", TRUE);
-    dump_database_helper(u, ROLE_NONE, "Anim:  ", TRUE);
-    dump_database_helper(u, ROLE_NONE, "Prodtn:", TRUE);
-    dump_database_helper(u, ROLE_NONE, "Ally:  ", TRUE);
-
-    pa_log_debug("  Sources:");
-    dump_database_helper(u, ROLE_NONE, "None:  ", FALSE);
-    dump_database_helper(u, ROLE_NONE, "Video: ", FALSE);
-    dump_database_helper(u, ROLE_NONE, "Music: ", FALSE);
-    dump_database_helper(u, ROLE_NONE, "Game:  ", FALSE);
-    dump_database_helper(u, ROLE_NONE, "Event: ", FALSE);
-    dump_database_helper(u, ROLE_NONE, "Phone: ", FALSE);
-    dump_database_helper(u, ROLE_NONE, "Anim:  ", FALSE);
-    dump_database_helper(u, ROLE_NONE, "Prodtn:", FALSE);
-    dump_database_helper(u, ROLE_NONE, "Ally:  ", FALSE);
+    if (u->do_routing) {
+        pa_log_debug(" Highest priority devices per-role:");
+
+        pa_log_debug("  Sinks:");
+        for (uint32_t role = ROLE_NONE; role < NUM_ROLES; ++role) {
+            char name[13];
+            uint32_t len = PA_MIN(12u, strlen(role_names[role]));
+            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);
+        }
+
+        pa_log_debug("  Sources:");
+        for (uint32_t role = ROLE_NONE; role < NUM_ROLES; ++role) {
+            char name[13];
+            uint32_t len = PA_MIN(12u, strlen(role_names[role]));
+            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);
+        }
+    }
 
     pa_log_debug("Completed database dump");
 }
 #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) {
 
-static void trigger_save(struct userdata *u) {
     pa_native_connection *c;
     uint32_t idx;
 
-    for (c = pa_idxset_first(u->subscribed, &idx); c; c = pa_idxset_next(u->subscribed, &idx)) {
+    pa_assert(u);
+
+    PA_IDXSET_FOREACH(c, u->subscribed, idx) {
         pa_tagstruct *t;
 
         t = pa_tagstruct_new(NULL, 0);
@@ -294,19 +460,23 @@ static void trigger_save(struct userdata *u) {
 
         pa_pstream_send_tagstruct(pa_native_connection_get_pstream(c), t);
     }
+}
 
-    if (u->save_time_event)
-        return;
+static bool entries_equal(const struct entry *a, const struct entry *b) {
 
-    u->save_time_event = pa_core_rttime_new(u->core, pa_rtclock_now() + SAVE_INTERVAL, save_time_callback, u);
-}
+    pa_assert(a);
+    pa_assert(b);
+
+    if (!pa_streq(a->description, b->description)
+        || a->user_set_description != b->user_set_description
+        || !pa_streq(a->icon, b->icon))
+        return false;
 
-static pa_bool_t entries_equal(const struct entry *a, const struct entry *b) {
-    /** @todo: Compare the priority lists too */
-    if (strncmp(a->description, b->description, sizeof(a->description)))
-        return FALSE;
+    for (int i=0; i < NUM_ROLES; ++i)
+        if (a->priority[i] != b->priority[i])
+            return false;
 
-    return TRUE;
+    return true;
 }
 
 static char *get_name(const char *key, const char *prefix) {
@@ -327,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);
@@ -350,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);
@@ -368,6 +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;
     }
 
     return old;
@@ -376,36 +549,22 @@ static inline struct entry *load_or_initialize_entry(struct userdata *u, struct
 static uint32_t get_role_index(const char* role) {
     pa_assert(role);
 
-    if (strcmp(role, "") == 0)
-        return ROLE_NONE;
-    if (strcmp(role, "video") == 0)
-        return ROLE_VIDEO;
-    if (strcmp(role, "music") == 0)
-        return ROLE_MUSIC;
-    if (strcmp(role, "game") == 0)
-        return ROLE_GAME;
-    if (strcmp(role, "event") == 0)
-        return ROLE_EVENT;
-    if (strcmp(role, "phone") == 0)
-        return ROLE_PHONE;
-    if (strcmp(role, "animation") == 0)
-        return ROLE_ANIMATION;
-    if (strcmp(role, "production") == 0)
-        return ROLE_PRODUCTION;
-    if (strcmp(role, "a11y") == 0)
-        return ROLE_A11Y;
+    for (uint32_t i = ROLE_NONE; i < NUM_ROLES; ++i)
+        if (pa_streq(role, role_names[i]))
+            return i;
+
     return PA_INVALID_INDEX;
 }
 
 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;
@@ -413,7 +572,7 @@ static void update_highest_priority_device_indexes(struct userdata *u, const cha
         indexes = &u->preferred_sources;
 
     for (uint32_t i = 0; i < NUM_ROLES; ++i) {
-        *indexes[i] = PA_INVALID_INDEX;
+        (*indexes)[i] = PA_INVALID_INDEX;
     }
     pa_zero(highest_priority_available);
 
@@ -426,19 +585,19 @@ static void update_highest_priority_device_indexes(struct userdata *u, const cha
         done = !pa_database_next(u->database, &key, &next_key, NULL);
 
         if (key.size > strlen(prefix) && strncmp(key.data, prefix, strlen(prefix)) == 0) {
-            char *name;
+            char *name, *device_name;
             struct entry *e;
 
             name = pa_xstrndup(key.data, key.size);
+            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]) {
+                    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;
-                        char *device_name = get_name(name, prefix);
+                        bool found = false;
 
                         if (sink_mode) {
                             pa_sink *sink;
@@ -446,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;
                                 }
@@ -458,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;
                                 }
@@ -467,17 +630,17 @@ static void update_highest_priority_device_indexes(struct userdata *u, const cha
                         }
                         if (found) {
                             highest_priority_available[i] = e->priority[i];
-                            *indexes[i] = idx;
+                            (*indexes)[i] = idx;
                         }
 
-                        pa_xfree(device_name);
                     }
                 }
 
-                pa_xfree(e);
+                entry_free(e);
             }
 
             pa_xfree(name);
+            pa_xfree(device_name);
         }
 
         pa_datum_free(&key);
@@ -485,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;
@@ -508,7 +670,7 @@ static void route_sink_input(struct userdata *u, pa_sink_input *si) {
         return;
 
     if (!(role = pa_proplist_gets(si->proplist, PA_PROP_MEDIA_ROLE)))
-        role_index = get_role_index("");
+        role_index = get_role_index("none");
     else
         role_index = get_role_index(role);
 
@@ -523,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, TRUE);
+        pa_sink_input_move_to(si, sink, false);
 }
 
 static pa_hook_result_t route_sink_inputs(struct userdata *u, pa_sink *ignore_sink) {
@@ -569,7 +731,7 @@ static void route_source_output(struct userdata *u, pa_source_output *so) {
         return;
 
     if (!(role = pa_proplist_gets(so->proplist, PA_PROP_MEDIA_ROLE)))
-        role_index = get_role_index("");
+        role_index = get_role_index("none");
     else
         role_index = get_role_index(role);
 
@@ -584,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, TRUE);
+        pa_source_output_move_to(so, source, false);
 }
 
 static pa_hook_result_t route_source_outputs(struct userdata *u, pa_source* ignore_source) {
@@ -607,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);
@@ -625,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;
 
@@ -658,13 +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_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_strlcpy(entry.description, pa_strnull(pa_proplist_gets(sink->proplist, PA_PROP_DEVICE_DESCRIPTION)), sizeof(entry.description));
+        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);
@@ -675,39 +846,52 @@ 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_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_strlcpy(entry.description, pa_strnull(pa_proplist_gets(source->proplist, PA_PROP_DEVICE_DESCRIPTION)), sizeof(entry.description));
+        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);
 
-    pa_database_set(u->database, &key, &data, TRUE);
+    if (entry_write(u, name, entry))
+        trigger_save(u);
+    else
+        pa_log_warn("Could not save device");;
 
+    entry_free(entry);
     pa_xfree(name);
-
-    trigger_save(u);
 }
 
 static pa_hook_result_t sink_new_hook_callback(pa_core *c, pa_sink_new_data *new_data, struct userdata *u) {
@@ -720,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 (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);
@@ -744,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 (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);
@@ -760,6 +944,9 @@ static pa_hook_result_t source_new_hook_callback(pa_core *c, pa_source_new_data
 }
 
 static pa_hook_result_t sink_input_new_hook_callback(pa_core *c, pa_sink_input_new_data *new_data, struct userdata *u) {
+    const char *role;
+    uint32_t role_index;
+
     pa_assert(c);
     pa_assert(new_data);
     pa_assert(u);
@@ -768,13 +955,10 @@ static pa_hook_result_t sink_input_new_hook_callback(pa_core *c, pa_sink_input_n
         return PA_HOOK_OK;
 
     if (new_data->sink)
-        pa_log_debug("Not restoring device for stream, because already set.");
+        pa_log_debug("Not restoring device for stream because already set.");
     else {
-        const char *role;
-        uint32_t role_index;
-
         if (!(role = pa_proplist_gets(new_data->proplist, PA_PROP_MEDIA_ROLE)))
-            role_index = get_role_index("");
+            role_index = get_role_index("none");
         else
             role_index = get_role_index(role);
 
@@ -786,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 = TRUE;
+                    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");
                 }
             }
         }
@@ -797,6 +981,9 @@ static pa_hook_result_t sink_input_new_hook_callback(pa_core *c, pa_sink_input_n
 }
 
 static pa_hook_result_t source_output_new_hook_callback(pa_core *c, pa_source_output_new_data *new_data, struct userdata *u) {
+    const char *role;
+    uint32_t role_index;
+
     pa_assert(c);
     pa_assert(new_data);
     pa_assert(u);
@@ -808,13 +995,10 @@ static pa_hook_result_t source_output_new_hook_callback(pa_core *c, pa_source_ou
         return PA_HOOK_OK;
 
     if (new_data->source)
-        pa_log_debug("Not restoring device for stream, because already set");
+        pa_log_debug("Not restoring device for stream because already set.");
     else {
-        const char *role;
-        uint32_t role_index;
-
         if (!(role = pa_proplist_gets(new_data->proplist, PA_PROP_MEDIA_ROLE)))
-            role_index = get_role_index("");
+            role_index = get_role_index("none");
         else
             role_index = get_role_index(role);
 
@@ -825,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 = TRUE;
-                }
+                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");
             }
         }
     }
@@ -836,13 +1019,14 @@ 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);
     pa_assert(u->core == c);
     pa_assert(u->on_hotplug);
 
+    notify_subscribers(u);
+
     return route_sink_inputs(u, NULL);
 }
 
@@ -852,6 +1036,8 @@ static pa_hook_result_t source_put_hook_callback(pa_core *c, PA_GCC_UNUSED pa_so
     pa_assert(u->core == c);
     pa_assert(u->on_hotplug);
 
+    notify_subscribers(u);
+
     return route_source_outputs(u, NULL);
 }
 
@@ -866,6 +1052,8 @@ static pa_hook_result_t sink_unlink_hook_callback(pa_core *c, pa_sink *sink, str
     if (c->state == PA_CORE_SHUTDOWN)
         return PA_HOOK_OK;
 
+    notify_subscribers(u);
+
     return route_sink_inputs(u, sink);
 }
 
@@ -880,13 +1068,12 @@ static pa_hook_result_t source_unlink_hook_callback(pa_core *c, pa_source *sourc
     if (c->state == PA_CORE_SHUTDOWN)
         return PA_HOOK_OK;
 
+    notify_subscribers(u);
+
     return route_source_outputs(u, source);
 }
 
-
 static void apply_entry(struct userdata *u, const char *name, struct entry *e) {
-    pa_sink *sink;
-    pa_source *source;
     uint32_t idx;
     char *n;
 
@@ -894,36 +1081,40 @@ static void apply_entry(struct userdata *u, const char *name, struct entry *e) {
     pa_assert(name);
     pa_assert(e);
 
+    if (!e->user_set_description)
+        return;
+
     if ((n = get_name(name, "sink:"))) {
-        for (sink = pa_idxset_first(u->core->sinks, &idx); sink; sink = pa_idxset_next(u->core->sinks, &idx)) {
-            if (!pa_streq(sink->name, n)) {
+        pa_sink *s;
+        PA_IDXSET_FOREACH(s, u->core->sinks, idx) {
+            if (!pa_streq(s->name, n)) {
                 continue;
             }
 
-            pa_log_info("Setting description for sink %s.", sink->name);
-            pa_sink_set_description(sink, e->description);
+            pa_log_info("Setting description for sink %s to '%s'", s->name, e->description);
+            pa_sink_set_description(s, e->description);
         }
         pa_xfree(n);
     }
     else if ((n = get_name(name, "source:"))) {
-        for (source = pa_idxset_first(u->core->sources, &idx); source; source = pa_idxset_next(u->core->sources, &idx)) {
-            if (!pa_streq(source->name, n)) {
+        pa_source *s;
+        PA_IDXSET_FOREACH(s, u->core->sources, idx) {
+            if (!pa_streq(s->name, n)) {
                 continue;
             }
 
-            if (source->monitor_of) {
-                pa_log_warn("Cowardly refusing to set the description for monitor source %s.", source->name);
+            if (s->monitor_of) {
+                pa_log_warn("Cowardly refusing to set the description for monitor source %s.", s->name);
                 continue;
             }
 
-            pa_log_info("Setting description for source %s.", source->name);
-            pa_source_set_description(source, e->description);
+            pa_log_info("Setting description for source %s to '%s'", s->name, e->description);
+            pa_source_set_description(s, e->description);
         }
         pa_xfree(n);
     }
 }
 
-
 #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) {
@@ -956,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;
@@ -973,11 +1164,43 @@ 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))) {
-          pa_tagstruct_puts(reply, name);
-          pa_tagstruct_puts(reply, e->description);
+        if ((e = entry_read(u, name))) {
+            uint32_t idx;
+            char *device_name;
+            uint32_t found_index = PA_INVALID_INDEX;
+
+            if ((device_name = get_name(name, "sink:"))) {
+                pa_sink* s;
+                PA_IDXSET_FOREACH(s, u->core->sinks, idx) {
+                    if (pa_streq(s->name, device_name)) {
+                        found_index = s->index;
+                        break;
+                    }
+                }
+                pa_xfree(device_name);
+            } else if ((device_name = get_name(name, "source:"))) {
+                pa_source* s;
+                PA_IDXSET_FOREACH(s, u->core->sources, idx) {
+                    if (pa_streq(s->name, device_name)) {
+                        found_index = s->index;
+                        break;
+                    }
+                }
+                pa_xfree(device_name);
+            }
+
+            pa_tagstruct_puts(reply, name);
+            pa_tagstruct_puts(reply, e->description);
+            pa_tagstruct_puts(reply, e->icon);
+            pa_tagstruct_putu32(reply, found_index);
+            pa_tagstruct_putu32(reply, NUM_ROLES);
+
+            for (uint32_t i = ROLE_NONE; i < NUM_ROLES; ++i) {
+                pa_tagstruct_puts(reply, role_names[i]);
+                pa_tagstruct_putu32(reply, e->priority[i]);
+            }
 
-          pa_xfree(e);
+            entry_free(e);
         }
 
         pa_xfree(name);
@@ -1000,18 +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)) && ENTRY_VERSION == e->version) {
-            pa_datum key, data;
+        if ((e = entry_read(u, device))) {
+            pa_xfree(e->description);
+            e->description = pa_xstrdup(description);
+            e->user_set_description = true;
 
-            pa_strlcpy(e->description, description, sizeof(e->description));
-
-            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);
@@ -1019,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);
@@ -1049,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;
@@ -1063,122 +1280,199 @@ static int extension_cb(pa_native_protocol *p, pa_module *m, pa_native_connectio
         break;
     }
 
-    case SUBCOMMAND_PREFER_DEVICE:
-    case SUBCOMMAND_DEFER_DEVICE: {
+    case SUBCOMMAND_REORDER: {
 
-        const char *role, *device;
+        const char *role;
         struct entry *e;
-        uint32_t role_index;
+        uint32_t role_index, n_devices;
+        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;*/
+        bool first;
 
         if (pa_tagstruct_gets(t, &role) < 0 ||
-            pa_tagstruct_gets(t, &device) < 0)
+            pa_tagstruct_getu32(t, &n_devices) < 0 ||
+            n_devices < 1)
             goto fail;
 
-        if (!role || !device || !*device)
+        if (PA_INVALID_INDEX == (role_index = get_role_index(role)))
             goto fail;
 
-        role_index = get_role_index(role);
-        if (PA_INVALID_INDEX == role_index)
-            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;
+        idx = 0;
+        for (i = 0; i < n_devices; ++i) {
+            const char *s;
+            if (pa_tagstruct_gets(t, &s) < 0) {
+                while ((device = pa_hashmap_steal_first(h))) {
+                    pa_xfree(device->device);
+                    pa_xfree(device);
+                }
 
-        if ((e = read_entry(u, device)) && ENTRY_VERSION == e->version) {
-            pa_datum key, data;
-            pa_bool_t done;
-            char* prefix = NULL;
-            uint32_t priority;
-            pa_bool_t haschanged = FALSE;
+                pa_hashmap_free(h);
+                pa_log_error("Protocol error on reorder");
+                goto fail;
+            }
 
-            if (strncmp(device, "sink:", 5) == 0)
-                prefix = pa_xstrdup("sink:");
-            else if (strncmp(device, "source:", 7) == 0)
-                prefix = pa_xstrdup("source:");
+            /* Ensure this is a valid entry */
+            if (!(e = entry_read(u, s))) {
+                while ((device = pa_hashmap_steal_first(h))) {
+                    pa_xfree(device->device);
+                    pa_xfree(device);
+                }
 
-            if (!prefix)
+                pa_hashmap_free(h);
+                pa_log_error("Client specified an unknown device in it's reorder list.");
                 goto fail;
+            }
+            entry_free(e);
+
+            if (first) {
+                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))) {
+                while ((device = pa_hashmap_steal_first(h))) {
+                    pa_xfree(device->device);
+                    pa_xfree(device);
+                }
 
-            priority = e->priority[role_index];
+                pa_hashmap_free(h);
+                pa_log_error("Attempted to reorder mixed devices (sinks and sources)");
+                goto fail;
+            }
 
-            /* Now we need to load up all the other entries of this type and shuffle the priroities around */
+            /* 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) {
+                device->prio = idx;
+                idx++;
+            } else {
+                pa_xfree(device->device);
+                pa_xfree(device);
+            }
+        }
 
-            done = !pa_database_first(u->database, &key, NULL);
+        /*pa_log_debug("Hashmap contents (received from client)");
+        PA_HASHMAP_FOREACH(device, h, state) {
+            pa_log_debug("  - %s (%d)", device->device, device->prio);
+        }*/
 
-            while (!done && !haschanged) {
-                pa_datum next_key;
+        /* Now cycle through our list and add all the devices.
+           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;
+        done = !pa_database_first(u->database, &key, NULL);
 
-                done = !pa_database_next(u->database, &key, &next_key, NULL);
+        while (!done && idx < 256) {
+            pa_datum next_key;
 
-                /* Only read devices with the right prefix */
-                if (key.size > strlen(prefix) && strncmp(key.data, prefix, strlen(prefix)) == 0) {
-                    char *name;
-                    struct entry *e2;
+            done = !pa_database_next(u->database, &key, &next_key, NULL);
 
-                    name = pa_xstrndup(key.data, key.size);
+            device = pa_xnew(struct device_t, 1);
+            device->device = pa_xstrndup(key.data, key.size);
+            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 already in it, free it now and carry on */
+                if (pa_hashmap_put(h, device->device, device) == 0
+                    && (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);
+                }
+                else {
+                    pa_xfree(device->device);
+                    pa_xfree(device);
+                }
+            } else {
+                pa_xfree(device->device);
+                pa_xfree(device);
+            }
 
-                    if ((e2 = read_entry(u, name))) {
-                        if (SUBCOMMAND_PREFER_DEVICE == command) {
-                            /* PREFER */
-                            if (e2->priority[role_index] == (priority - 1)) {
-                                e2->priority[role_index]++;
-                                haschanged = TRUE;
-                            }
-                        } else {
-                            /* DEFER */
-                            if (e2->priority[role_index] == (priority + 1)) {
-                                e2->priority[role_index]--;
-                                haschanged = TRUE;
-                            }
-                        }
+            pa_datum_free(&key);
 
-                        if (haschanged) {
-                            data.data = e2;
-                            data.size = sizeof(*e2);
+            key = next_key;
+        }
 
-                            if (pa_database_set(u->database, &key, &data, TRUE))
-                                pa_log_warn("Could not save device");
-                        }
+        /*pa_log_debug("Hashmap contents (combined with database)");
+        PA_HASHMAP_FOREACH(device, h, state) {
+            pa_log_debug("  - %s (%d)", device->device, device->prio);
+        }*/
+
+        /* Now we put all the entries in a simple list for sorting it. */
+        n_devices = pa_hashmap_size(h);
+        devices = pa_xnew(struct device_t *,  n_devices);
+        idx = 0;
+        while ((device = pa_hashmap_steal_first(h))) {
+            devices[idx++] = device;
+        }
+        pa_hashmap_free(h);
+
+        /* Simple bubble sort */
+        for (i = 0; i < n_devices; ++i) {
+            for (uint32_t j = i; j < n_devices; ++j) {
+                if (devices[i]->prio > devices[j]->prio) {
+                    struct device_t *tmp;
+                    tmp = devices[i];
+                    devices[i] = devices[j];
+                    devices[j] = tmp;
+                }
+            }
+        }
 
-                        pa_xfree(e2);
+        /*pa_log_debug("Sorted device list");
+        for (i = 0; i < n_devices; ++i) {
+            pa_log_debug("  - %s (%d)", devices[i]->device, devices[i]->prio);
+        }*/
+
+        /* Go through in order and write the new entry and cleanup our own list */
+        idx = 1;
+        first = true;
+        for (i = 0; i < n_devices; ++i) {
+            if ((e = entry_read(u, devices[i]->device))) {
+                if (e->priority[role_index] == idx)
+                    idx++;
+                else {
+                    e->priority[role_index] = idx;
+
+                    if (entry_write(u, (char *) devices[i]->device, e)) {
+                        first = false;
+                        idx++;
                     }
-
-                    pa_xfree(name);
                 }
 
-                pa_datum_free(&key);
-                key = next_key;
+                pa_xfree(e);
             }
+            pa_xfree(devices[i]->device);
+            pa_xfree(devices[i]);
+        }
 
-            /* Now write out our actual entry */
-            if (haschanged) {
-                if (SUBCOMMAND_PREFER_DEVICE == command)
-                    e->priority[role_index]--;
-                else
-                    e->priority[role_index]++;
-
-                key.data = (char *) device;
-                key.size = strlen(device);
-
-                data.data = e;
-                data.size = sizeof(*e);
-
-                if (pa_database_set(u->database, &key, &data, TRUE))
-                    pa_log_warn("Could not save device");
-
-                trigger_save(u);
-            }
+        pa_xfree(devices);
 
-            pa_xfree(e);
+        if (!first) {
+            trigger_save(u);
 
-            pa_xfree(prefix);
+            if (sink_mode)
+                route_sink_inputs(u, NULL);
+            else
+                route_source_outputs(u, NULL);
         }
-        else
-            pa_log_warn("Could not reorder device %s, no entry in database", device);
 
         break;
     }
 
     case SUBCOMMAND_SUBSCRIBE: {
 
-      pa_bool_t enabled;
+      bool enabled;
 
       if (pa_tagstruct_get_boolean(t, &enabled) < 0 ||
         !pa_tagstruct_eof(t))
@@ -1216,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;
@@ -1223,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);
 
@@ -1259,42 +1559,93 @@ int pa__init(pa_module*m) {
     u->source_new_hook_slot = pa_hook_connect(&m->core->hooks[PA_CORE_HOOK_SOURCE_NEW], PA_HOOK_EARLY, (pa_hook_cb_t) source_new_hook_callback, u);
 
     /* The following slots are used to deal with routing */
-    /* A little bit later than module-stream-restore, module-intended-roles */
-    u->sink_input_new_hook_slot = pa_hook_connect(&m->core->hooks[PA_CORE_HOOK_SINK_INPUT_NEW], PA_HOOK_EARLY+15, (pa_hook_cb_t) sink_input_new_hook_callback, u);
-    u->source_output_new_hook_slot = pa_hook_connect(&m->core->hooks[PA_CORE_HOOK_SOURCE_OUTPUT_NEW], PA_HOOK_EARLY+15, (pa_hook_cb_t) source_output_new_hook_callback, u);
+    /* A little bit later than module-stream-restore, but before module-intended-roles */
+    u->sink_input_new_hook_slot = pa_hook_connect(&m->core->hooks[PA_CORE_HOOK_SINK_INPUT_NEW], PA_HOOK_EARLY+5, (pa_hook_cb_t) sink_input_new_hook_callback, u);
+    u->source_output_new_hook_slot = pa_hook_connect(&m->core->hooks[PA_CORE_HOOK_SOURCE_OUTPUT_NEW], PA_HOOK_EARLY+5, (pa_hook_cb_t) source_output_new_hook_callback, u);
 
     if (on_hotplug) {
-        /* A little bit later than module-stream-restore, module-intended-roles */
-        u->sink_put_hook_slot = pa_hook_connect(&m->core->hooks[PA_CORE_HOOK_SINK_PUT], PA_HOOK_LATE+15, (pa_hook_cb_t) sink_put_hook_callback, u);
-        u->source_put_hook_slot = pa_hook_connect(&m->core->hooks[PA_CORE_HOOK_SOURCE_PUT], PA_HOOK_LATE+15, (pa_hook_cb_t) source_put_hook_callback, u);
+        /* A little bit later than module-stream-restore, but before module-intended-roles */
+        u->sink_put_hook_slot = pa_hook_connect(&m->core->hooks[PA_CORE_HOOK_SINK_PUT], PA_HOOK_LATE+5, (pa_hook_cb_t) sink_put_hook_callback, u);
+        u->source_put_hook_slot = pa_hook_connect(&m->core->hooks[PA_CORE_HOOK_SOURCE_PUT], PA_HOOK_LATE+5, (pa_hook_cb_t) source_put_hook_callback, u);
     }
 
     if (on_rescue) {
-        /* A little bit later than module-stream-restore, module-intended-roles, a little bit earlier than module-rescue-streams, ... */
-        u->sink_unlink_hook_slot = pa_hook_connect(&m->core->hooks[PA_CORE_HOOK_SINK_UNLINK], PA_HOOK_LATE+15, (pa_hook_cb_t) sink_unlink_hook_callback, u);
-        u->source_unlink_hook_slot = pa_hook_connect(&m->core->hooks[PA_CORE_HOOK_SOURCE_UNLINK], PA_HOOK_LATE+15, (pa_hook_cb_t) source_unlink_hook_callback, u);
+        /* A little bit later than module-stream-restore, a little bit earlier than module-intended-roles, module-rescue-streams, ... */
+        u->sink_unlink_hook_slot = pa_hook_connect(&m->core->hooks[PA_CORE_HOOK_SINK_UNLINK], PA_HOOK_LATE+5, (pa_hook_cb_t) sink_unlink_hook_callback, u);
+        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) {
+        u->preferred_sinks[i] = u->preferred_sources[i] = PA_INVALID_INDEX;
+    }
+
     route_sink_inputs(u, NULL);
     route_source_outputs(u, NULL);
 
@@ -1311,7 +1662,7 @@ fail:
     if (ma)
         pa_modargs_free(ma);
 
-    return  -1;
+    return -1;
 }
 
 void pa__done(pa_module*m) {
@@ -1345,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);
 
@@ -1357,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);
 }