diff --git a/admin/moveclients.xsl b/admin/moveclients.xsl index 9328998a..33ec6857 100644 --- a/admin/moveclients.xsl +++ b/admin/moveclients.xsl @@ -26,9 +26,9 @@
- + Move from + + to + with direction +   diff --git a/src/Makefile.am b/src/Makefile.am index f8168ac0..8bff44c1 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -41,6 +41,7 @@ noinst_HEADERS = \ xml2json.h \ listensocket.h \ fastevent.h \ + navigation.h \ event.h \ event_log.h \ event_exec.h \ @@ -92,6 +93,7 @@ icecast_SOURCES = \ xml2json.c \ listensocket.c \ fastevent.c \ + navigation.c \ format.c \ format_ogg.c \ format_mp3.c \ diff --git a/src/admin.c b/src/admin.c index 5923bd96..f77dd9b0 100644 --- a/src/admin.c +++ b/src/admin.c @@ -716,6 +716,7 @@ static void command_move_clients(client_t *client, { const char *dest_source; const char *idtext = NULL; + const char *directiontext = NULL; connection_id_t id; source_t *dest; char buf[255]; @@ -730,6 +731,8 @@ static void command_move_clients(client_t *client, } else { idtext = NULL; } + COMMAND_OPTIONAL(client, "direction", directiontext); + ICECAST_LOG_DEBUG("Done optional check (%d)", parameters_passed); if (!parameters_passed) { xmlDocPtr doc = admin_build_sourcelist(source->mount, client, response); @@ -766,7 +769,7 @@ static void command_move_clients(client_t *client, ICECAST_LOG_INFO("source is \"%s\", destination is \"%s\"", source->mount, dest->mount); - source_move_clients(source, dest, idtext ? &id : NULL); + source_move_clients(source, dest, idtext ? &id : NULL, navigation_str_to_direction(directiontext, NAVIGATION_DIRECTION_REPLACE_ALL)); snprintf(buf, sizeof(buf), "Clients moved from %s to %s", source->mount, dest_source); @@ -827,6 +830,15 @@ static inline xmlNodePtr __add_listener(client_t *client, xmlNewTextChild(node, NULL, XMLSTR("protocol"), XMLSTR(client_protocol_to_string(client->protocol))); + do { + xmlNodePtr history = xmlNewChild(node, NULL, XMLSTR("history"), NULL); + size_t i; + + for (i = 0; i < client->history.fill; i++) { + xmlNewTextChild(history, NULL, XMLSTR("mount"), XMLSTR(mount_identifier_get_mount(client->history.history[i]))); + } + } while (0); + return node; } diff --git a/src/cfgfile.c b/src/cfgfile.c index 39b279c3..68dbf8e7 100644 --- a/src/cfgfile.c +++ b/src/cfgfile.c @@ -217,6 +217,25 @@ static listener_type_t config_str_to_listener_type(const char *str) } } +static fallback_override_t config_str_to_fallback_override_t(const char *str) +{ + if (!str || !*str || strcmp(str, "none") == 0) { + return FALLBACK_OVERRIDE_NONE; + } else if (strcasecmp(str, "all") == 0) { + return FALLBACK_OVERRIDE_ALL; + } else if (strcasecmp(str, "own") == 0) { + return FALLBACK_OVERRIDE_OWN; + } else { + if (util_str_to_bool(str)) { + ICECAST_LOG_WARN("Old style fallback override setting. Please replace %#H with \"all\".", str); + return FALLBACK_OVERRIDE_ALL; + } else { + ICECAST_LOG_WARN("Old style fallback override setting. Please replace %#H with \"none\".", str); + return FALLBACK_OVERRIDE_NONE; + } + } +} + char * config_href_to_id(const char *href) { if (!href || !*href) @@ -1538,7 +1557,7 @@ static void _parse_mount(xmlDocPtr doc, __read_int(doc, node, &mount->mp3_meta_interval, " must not be empty."); } else if (xmlStrcmp(node->name, XMLSTR("fallback-override")) == 0) { tmp = (char *)xmlNodeListGetString(doc, node->xmlChildrenNode, 1); - mount->fallback_override = util_str_to_bool(tmp); + mount->fallback_override = config_str_to_fallback_override_t(tmp); if(tmp) xmlFree(tmp); } else if (xmlStrcmp(node->name, XMLSTR("no-mount")) == 0) { @@ -1695,7 +1714,7 @@ static void _parse_mount(xmlDocPtr doc, current = current->next; } - if (!mount->fallback_mount && (mount->fallback_when_full || mount->fallback_override)) { + if (!mount->fallback_mount && (mount->fallback_when_full || mount->fallback_override != FALLBACK_OVERRIDE_NONE)) { ICECAST_LOG_WARN("Config for mount %s contains fallback options " "but no fallback mount.", mount->mountname); } @@ -2675,7 +2694,7 @@ static void merge_mounts(mount_proxy * dst, mount_proxy * src) dst->max_listeners = src->max_listeners; if (!dst->fallback_mount) dst->fallback_mount = (char*)xmlStrdup((xmlChar*)src->fallback_mount); - if (!dst->fallback_override) + if (dst->fallback_override == FALLBACK_OVERRIDE_NONE) dst->fallback_override = src->fallback_override; if (!dst->no_mount) dst->no_mount = src->no_mount; diff --git a/src/cfgfile.h b/src/cfgfile.h index 3f621a6d..d980ccbf 100644 --- a/src/cfgfile.h +++ b/src/cfgfile.h @@ -67,6 +67,12 @@ typedef enum _mount_type { MOUNT_TYPE_DEFAULT } mount_type; +typedef enum { + FALLBACK_OVERRIDE_NONE = 0, + FALLBACK_OVERRIDE_ALL, + FALLBACK_OVERRIDE_OWN +} fallback_override_t; + typedef struct _mount_proxy { /* The mountpoint this proxy is used for */ char *mountname; @@ -89,7 +95,7 @@ typedef struct _mount_proxy { /* When this source arrives, do we steal back * clients from the fallback? */ - int fallback_override; + fallback_override_t fallback_override; /* Do we permit direct requests of this mountpoint? * (or only indirect, through fallbacks) */ diff --git a/src/client.c b/src/client.c index daa3fefc..97649b8e 100644 --- a/src/client.c +++ b/src/client.c @@ -164,6 +164,7 @@ int client_create(client_t **c_ptr, connection_t *con, http_parser_t *parser) client->refbuf->len = 0; /* force reader code to ignore buffer contents */ client->pos = 0; client->write_to_client = format_generic_write_to_client; + navigation_history_init(&(client->history)); *c_ptr = client; avl_tree_wlock(global_client_list); @@ -344,6 +345,7 @@ void client_destroy(client_t *client) free(client->password); free(client->role); acl_release(client->acl); + navigation_history_clear(&(client->history)); free(client); } diff --git a/src/client.h b/src/client.h index c440a52c..0d01ecfc 100644 --- a/src/client.h +++ b/src/client.h @@ -23,6 +23,7 @@ #include "common/httpp/encoding.h" #include "icecasttypes.h" +#include "navigation.h" #include "errors.h" #include "refbuf.h" #include "module.h" @@ -114,6 +115,9 @@ struct _client_tag { module_t *handler_module; char *handler_function; + /* History of navigated mount points */ + navigation_history_t history; + /* is client getting intro data */ long intro_offset; diff --git a/src/connection.c b/src/connection.c index 373e5cd1..ff0cb2ea 100644 --- a/src/connection.c +++ b/src/connection.c @@ -61,6 +61,7 @@ #include "refobject.h" #include "listensocket.h" #include "fastevent.h" +#include "navigation.h" #define CATMODULE "connection" @@ -953,6 +954,7 @@ static void __add_listener_to_source(source_t *source, client_t *client) } ICECAST_LOG_INFO("stream full, trying %s", next->mount); source = next; + navigation_history_navigate_to(&(client->history), source->identifier, NAVIGATION_DIRECTION_DOWN); loop--; continue; } @@ -1048,7 +1050,7 @@ static void _handle_get_request(client_t *client) { avl_tree_rlock(global.source_tree); /* let's see if this is a source or just a random fserve file */ - source = source_find_mount(client->uri); + source = source_find_mount_with_history(client->uri, &(client->history)); if (source) { /* true mount */ do { diff --git a/src/icecasttypes.h b/src/icecasttypes.h index f0c643c1..8a4c113b 100644 --- a/src/icecasttypes.h +++ b/src/icecasttypes.h @@ -126,6 +126,10 @@ typedef struct listensocket_tag listensocket_t; typedef struct digest_tag digest_t; +/* ---[ navigation.[ch] ]--- */ + +typedef struct mount_identifier_tag mount_identifier_t; + /* ---[ refobject.[ch] ]--- */ typedef struct refobject_base_tag refobject_base_t; @@ -142,6 +146,7 @@ typedef union __attribute__ ((__transparent_union__)) { listensocket_container_t *listensocket_container; listensocket_t *listensocket; digest_t *digest; + mount_identifier_t *mount_identifier; } refobject_t; #else typedef void * refobject_t; diff --git a/src/main.c b/src/main.c index 10a54a31..aecde897 100644 --- a/src/main.c +++ b/src/main.c @@ -80,6 +80,7 @@ #include "listensocket.h" #include "fastevent.h" #include "prng.h" +#include "navigation.h" #include @@ -146,6 +147,7 @@ static void initialize_subsystems(void) log_initialize(); thread_initialize(); prng_initialize(); + navigation_initialize(); global_initialize(); #ifndef FASTEVENT_ENABLED fastevent_initialize(); @@ -186,6 +188,7 @@ static void shutdown_subsystems(void) refobject_unref(fastevent_reg); fastevent_shutdown(); #endif + navigation_shutdown(); prng_shutdown(); global_shutdown(); thread_shutdown(); diff --git a/src/navigation.c b/src/navigation.c new file mode 100644 index 00000000..43eb2255 --- /dev/null +++ b/src/navigation.c @@ -0,0 +1,198 @@ +/* Icecast + * + * This program is distributed under the GNU General Public License, version 2. + * A copy of this license is included with this source. + * + * Copyright 2020, Philipp "ph3-der-loewe" Schafft + */ + +#ifdef HAVE_CONFIG_H +#include +#endif + +#include "common/avl/avl.h" + +#include "navigation.h" + +#include "logging.h" +#define CATMODULE "navigation" + +struct mount_identifier_tag { + /* base object */ + refobject_base_t __base; +}; + +REFOBJECT_DEFINE_TYPE(mount_identifier_t); + +const char * navigation_direction_to_str(navigation_direction_t dir) +{ + switch (dir) { + case NAVIGATION_DIRECTION_UP: return "up"; break; + case NAVIGATION_DIRECTION_DOWN: return "down"; break; + case NAVIGATION_DIRECTION_REPLACE_CURRENT: return "replace-current"; break; + case NAVIGATION_DIRECTION_REPLACE_ALL: return "replace-all"; break; + } + + return NULL; +} + +navigation_direction_t navigation_str_to_direction(const char *str, navigation_direction_t def) +{ + if (!str || !*str) + return def; + + if (strcasecmp(str, "up") == 0) { + return NAVIGATION_DIRECTION_UP; + } else if (strcasecmp(str, "down") == 0) { + return NAVIGATION_DIRECTION_DOWN; + } else if (strcasecmp(str, "replace_current") == 0 || strcasecmp(str, "replace-current") == 0) { + return NAVIGATION_DIRECTION_REPLACE_CURRENT; + } else if (strcasecmp(str, "replace_all") == 0 || strcasecmp(str, "replace-all") == 0) { + return NAVIGATION_DIRECTION_REPLACE_ALL; + } else { + return def; + } +} + +static int mount_identifier_compare__for_tree(void *compare_arg, void *a, void *b) +{ + const char *id_a, *id_b; + + id_a = mount_identifier_get_mount(a); + id_b = mount_identifier_get_mount(b); + + if (!id_a || !id_b || id_a == id_b) { + return 0; + } else { + return strcmp(id_a, id_b); + } +} + +void navigation_initialize(void) +{ +} + +void navigation_shutdown(void) +{ +} + + +mount_identifier_t * mount_identifier_new(const char *mount) +{ + mount_identifier_t *n; + + if (!mount) + return NULL; + + n = refobject_new__new(mount_identifier_t, NULL, mount, NULL); + if (!n) + return NULL; + + return n; +} + +int mount_identifier_compare(mount_identifier_t *a, mount_identifier_t *b) +{ + return mount_identifier_compare__for_tree(NULL, a, b); +} + +static inline int navigation_history_pop(navigation_history_t *history) +{ + if (history->fill == 0) + return 0; + history->fill--; + refobject_unref(history->history[history->fill]); + history->history[history->fill] = NULL; + return 0; +} + +static inline int navigation_history_push(navigation_history_t *history, mount_identifier_t *identifier) +{ + if (history->fill > 0 && mount_identifier_compare(history->history[history->fill - 1], identifier) == 0) + return 0; + + if (refobject_ref(identifier) != 0) { + ICECAST_LOG_ERROR("Can not reference identifier=%p, BAD.", identifier); + return -1; + } + + if (history->fill == (sizeof(history->history)/sizeof(*history->history))) { + refobject_unref(history->history[0]); + memmove(history->history, &(history->history[1]), sizeof(history->history) - sizeof(*history->history)); + history->fill--; + } + + history->history[history->fill++] = identifier; + + return 0; +} + +void navigation_history_clear(navigation_history_t *history) +{ + if (!history) + return; + while (navigation_history_pop(history)); +} + +mount_identifier_t * navigation_history_get_up(navigation_history_t *history) +{ + if (!history) + return NULL; + + if (history->fill < 2) + return NULL; + + if (refobject_ref(history->history[history->fill - 2]) != 0) + return NULL; + + return history->history[history->fill - 2]; +} + +int navigation_history_navigate_to(navigation_history_t *history, mount_identifier_t *identifier, navigation_direction_t direction) +{ + ICECAST_LOG_DDEBUG("Called with history=%p, identifier=%p (%#H), direction=%s", history, identifier, mount_identifier_get_mount(identifier), navigation_direction_to_str(direction)); + + if (!history || !identifier) + return -1; + + if (direction == NAVIGATION_DIRECTION_UP && history->fill < 2) + direction = NAVIGATION_DIRECTION_REPLACE_ALL; + + switch (direction) { + case NAVIGATION_DIRECTION_UP: + if (history->fill < 2) + return -1; + if (mount_identifier_compare(history->history[history->fill - 2], identifier) != 0) + return -1; + return navigation_history_pop(history); + break; + case NAVIGATION_DIRECTION_DOWN: + return navigation_history_push(history, identifier); + break; + case NAVIGATION_DIRECTION_REPLACE_CURRENT: + if (history->fill == 0) { + return navigation_history_push(history, identifier); + } else { + if (history->fill > 1 && mount_identifier_compare(history->history[history->fill - 2], identifier) == 0) { + return navigation_history_pop(history); + } + + if (refobject_ref(identifier) != 0) { + ICECAST_LOG_ERROR("Can not reference identifier=%p, BAD.", identifier); + return -1; + } + refobject_unref(history->history[history->fill - 1]); + history->history[history->fill - 1] = identifier; + return 0; + } + break; + case NAVIGATION_DIRECTION_REPLACE_ALL: + navigation_history_clear(history); + if (history->fill != 0) + return -1; + return navigation_history_push(history, identifier); + break; + } + + return -1; +} diff --git a/src/navigation.h b/src/navigation.h new file mode 100644 index 00000000..c38d6d5b --- /dev/null +++ b/src/navigation.h @@ -0,0 +1,47 @@ +/* Icecast + * + * This program is distributed under the GNU General Public License, version 2. + * A copy of this license is included with this source. + * + * Copyright 2020, Philipp "ph3-der-loewe" Schafft + */ + +#ifndef __NAVIGATION_H__ +#define __NAVIGATION_H__ + +#include + +#include "refobject.h" + +#define MAX_NAVIGATION_HISTORY_SIZE 8 + +typedef struct { + mount_identifier_t *history[MAX_NAVIGATION_HISTORY_SIZE]; + size_t fill; +} navigation_history_t; + +typedef enum { + NAVIGATION_DIRECTION_UP, + NAVIGATION_DIRECTION_DOWN, + NAVIGATION_DIRECTION_REPLACE_CURRENT, + NAVIGATION_DIRECTION_REPLACE_ALL +} navigation_direction_t; + +REFOBJECT_FORWARD_TYPE(mount_identifier_t); + +const char * navigation_direction_to_str(navigation_direction_t dir); +navigation_direction_t navigation_str_to_direction(const char *str, navigation_direction_t def); + +void navigation_initialize(void); +void navigation_shutdown(void); + +mount_identifier_t * mount_identifier_new(const char *mount); +#define mount_identifier_get_mount(identifier) refobject_get_name((identifier)) +int mount_identifier_compare(mount_identifier_t *a, mount_identifier_t *b); + +#define navigation_history_init(history) memset((history), 0, sizeof(navigation_history_t)) +void navigation_history_clear(navigation_history_t *history); +mount_identifier_t * navigation_history_get_up(navigation_history_t *history); +int navigation_history_navigate_to(navigation_history_t *history, mount_identifier_t *identifier, navigation_direction_t direction); + +#endif diff --git a/src/slave.c b/src/slave.c index cb4a5f7f..f108f0c8 100644 --- a/src/slave.c +++ b/src/slave.c @@ -469,7 +469,7 @@ static void *start_relay_stream (void *arg) fallback_source = source_find_mount(relay->source->fallback_mount); if (fallback_source != NULL) - source_move_clients(relay->source, fallback_source, NULL); + source_move_clients(relay->source, fallback_source, NULL, NAVIGATION_DIRECTION_DOWN); avl_tree_unlock(global.source_tree); } @@ -538,7 +538,7 @@ static void check_relay_stream (relay_t *relay) { relay->source->on_demand = relay->config->on_demand; - if (source->fallback_mount && source->fallback_override) + if (source->fallback_mount && source->fallback_override != FALLBACK_OVERRIDE_NONE) { source_t *fallback; avl_tree_rlock (global.source_tree); diff --git a/src/source.c b/src/source.c index 69af0046..fdecdd92 100644 --- a/src/source.c +++ b/src/source.c @@ -60,6 +60,7 @@ #include "event.h" #include "slave.h" #include "acl.h" +#include "navigation.h" #undef CATMODULE #define CATMODULE "source" @@ -105,6 +106,7 @@ source_t *source_reserve (const char *mount) /* make duplicates for strings or similar */ src->mount = strdup(mount); + src->identifier = mount_identifier_new(mount); src->max_listeners = -1; thread_mutex_create(&src->lock); @@ -152,7 +154,7 @@ source_t *source_find_mount_raw(const char *mount) * check the fallback, and so on. Must have a global source lock to call * this function. */ -source_t *source_find_mount(const char *mount) +source_t *source_find_mount_with_history(const char *mount, navigation_history_t *history) { source_t *source = NULL; ice_config_t *config; @@ -164,10 +166,20 @@ source_t *source_find_mount(const char *mount) { source = source_find_mount_raw(mount); - if (source) - { + if (source) { + if (history) + navigation_history_navigate_to(history, source->identifier, NAVIGATION_DIRECTION_DOWN); + if (source->running || source->on_demand) break; + } else { + if (history) { + mount_identifier_t *identifier = mount_identifier_new(mount); + if (identifier) { + navigation_history_navigate_to(history, identifier, NAVIGATION_DIRECTION_DOWN); + refobject_unref(identifier); + } + } } /* we either have a source which is not active (relay) or no source @@ -186,7 +198,6 @@ source_t *source_find_mount(const char *mount) return source; } - int source_compare_sources(void *arg, void *a, void *b) { source_t *srca = (source_t *)a; @@ -315,6 +326,7 @@ void source_free_source (source_t *source) /* make sure all YP entries have gone */ yp_remove (source->mount); + refobject_unref(source->identifier); free (source->mount); free (source); @@ -342,7 +354,13 @@ client_t *source_find_client(source_t *source, connection_id_t id) return NULL; } -static inline void source_move_clients__single(source_t *source, avl_tree *from, avl_tree *to, client_t *client) { +static inline int source_move_clients__single(source_t *source, source_t *dest, avl_tree *from, avl_tree *to, client_t *client, navigation_direction_t direction) { + if (navigation_history_navigate_to(&(client->history), dest->identifier, direction) != 0) { + ICECAST_LOG_DWARN("Can not change history: navigation of client=%p{.con->id=%llu, ...} from source=%p{.mount=%#H, ...} to dest=%p{.mount=%#H, ...} with direction %s failed", + client, (unsigned long long int)client->con->id, source, source->mount, dest, dest->mount, navigation_direction_to_str(direction)); + return -1; + } + avl_delete(from, client, NULL); /* when switching a client to a different queue, be wary of the @@ -357,6 +375,7 @@ static inline void source_move_clients__single(source_t *source, avl_tree *from, } avl_insert(to, (void *)client); + return 0; } /* Move clients from source to dest provided dest is running @@ -364,7 +383,7 @@ static inline void source_move_clients__single(source_t *source, avl_tree *from, * The only lock that should be held when this is called is the * source tree lock */ -void source_move_clients(source_t *source, source_t *dest, connection_id_t *id) +void source_move_clients(source_t *source, source_t *dest, connection_id_t *id, navigation_direction_t direction) { unsigned long count = 0; if (strcmp(source->mount, dest->mount) == 0) { @@ -411,24 +430,30 @@ void source_move_clients(source_t *source, source_t *dest, connection_id_t *id) fakeclient.con->id = *id; if (avl_get_by_key(source->client_tree, &fakeclient, &result) == 0) { - source_move_clients__single(source, source->client_tree, dest->pending_tree, result); - count++; + if (source_move_clients__single(source, dest, source->client_tree, dest->pending_tree, result, direction) == 0) + count++; } } else { - while (1) { - avl_node *node = avl_get_first(source->pending_tree); - if (node == NULL) - break; - source_move_clients__single(source, source->pending_tree, dest->pending_tree, node->key); - count++; + avl_node *next; + + next = avl_get_first(source->pending_tree); + while (next) { + avl_node *node = next; + + next = avl_get_next(next); + + if (source_move_clients__single(source, dest, source->pending_tree, dest->pending_tree, node->key, direction) == 0) + count++; } - while (1) { - avl_node *node = avl_get_first(source->client_tree); - if (node == NULL) - break; - source_move_clients__single(source, source->client_tree, dest->pending_tree, node->key); - count++; + next = avl_get_first(source->client_tree); + while (next) { + avl_node *node = next; + + next = avl_get_next(next); + + if (source_move_clients__single(source, dest, source->client_tree, dest->pending_tree, node->key, direction) == 0) + count++; } } @@ -656,15 +681,27 @@ static void source_init (source_t *source) ** loop or jingle track or whatever the fallback is used for */ - if (source->fallback_override && source->fallback_mount) - { + ICECAST_LOG_DDEBUG("source=%p{.mount=%#H, .fallback_override=%i, .fallback_mount=%#H, ...}", source, source->mount, (int)source->fallback_override, source->fallback_mount); + if (source->fallback_override != FALLBACK_OVERRIDE_NONE && source->fallback_mount) { source_t *fallback_source; avl_tree_rlock(global.source_tree); fallback_source = source_find_mount(source->fallback_mount); - if (fallback_source) - source_move_clients(fallback_source, source, NULL); + if (fallback_source) { + ICECAST_LOG_DDEBUG("source=%p{.mount=%#H, .fallback_override=%i, ...}, fallback_source=%p{.mount=%#H, ...}", source, source->mount, (int)source->fallback_override, fallback_source, fallback_source->mount); + switch (source->fallback_override) { + case FALLBACK_OVERRIDE_NONE: + /* no-op */ + break; + case FALLBACK_OVERRIDE_ALL: + source_move_clients(fallback_source, source, NULL, NAVIGATION_DIRECTION_REPLACE_CURRENT); + break; + case FALLBACK_OVERRIDE_OWN: + source_move_clients(fallback_source, source, NULL, NAVIGATION_DIRECTION_UP); + break; + } + } avl_tree_unlock(global.source_tree); } @@ -863,7 +900,7 @@ static void source_shutdown (source_t *source) fallback_source = source_find_mount(source->fallback_mount); if (fallback_source != NULL) - source_move_clients(source, fallback_source, NULL); + source_move_clients(source, fallback_source, NULL, NAVIGATION_DIRECTION_DOWN); avl_tree_unlock(global.source_tree); } diff --git a/src/source.h b/src/source.h index e35e9d90..9184454d 100644 --- a/src/source.h +++ b/src/source.h @@ -33,7 +33,8 @@ struct source_tag { http_parser_t *parser; time_t client_stats_update; - char *mount; + char *mount; // TODO: Should we at some point migrate away from this to only use identifier? + mount_identifier_t *identifier; /* If this source drops, try to move all clients to this fallback */ char *fallback_mount; @@ -60,7 +61,7 @@ struct source_tag { unsigned long prev_listeners; long max_listeners; int yp_public; - int fallback_override; + fallback_override_t fallback_override; int fallback_when_full; int shoutcast_compat; @@ -91,12 +92,13 @@ void *source_client_thread (void *arg); void source_client_callback (client_t *client, void *source); void source_update_settings (ice_config_t *config, source_t *source, mount_proxy *mountinfo); void source_clear_source (source_t *source); -source_t *source_find_mount(const char *mount); +#define source_find_mount(mount) source_find_mount_with_history((mount), NULL) +source_t *source_find_mount_with_history(const char *mount, navigation_history_t *history); source_t *source_find_mount_raw(const char *mount); client_t *source_find_client(source_t *source, connection_id_t id); int source_compare_sources(void *arg, void *a, void *b); void source_free_source(source_t *source); -void source_move_clients(source_t *source, source_t *dest, connection_id_t *id); +void source_move_clients(source_t *source, source_t *dest, connection_id_t *id, navigation_direction_t direction); int source_remove_client(void *key); void source_main(source_t *source); void source_recheck_mounts (int update_all);