diff -u -r trunk/res/res_musiconhold.c trunk-moh/res/res_musiconhold.c --- trunk/res/res_musiconhold.c 2014-04-15 13:15:18.701363782 +0200 +++ trunk-moh/res/res_musiconhold.c 2014-04-15 13:52:46.240363129 +0200 @@ -46,8 +46,11 @@ #include #include #include +#include #include #include +#include +#include #ifdef SOLARIS #include @@ -79,6 +82,8 @@ #define HANDLE_REF 1 #define DONT_UNREF 0 +#define PLAYLIST_FILE_NAME "playlist.txt" +\ /*** DOCUMENTATION @@ -183,12 +188,15 @@ #define MOH_CACHERTCLASSES (1 << 5) /*!< Should we use a separate instance of MOH for each user or not */ #define MOH_ANNOUNCEMENT (1 << 6) /*!< Do we play announcement files between songs on this channel? */ +#define MOH_DYNAMIC (1 << 7) /* Custom astobj2 flag */ #define MOH_NOTDELETED (1 << 30) /*!< Find only records that aren't deleted? */ static struct ast_flags global_flags[1] = {{0}}; /*!< global MOH_ flags */ +static char* dynamic_dir = NULL; + struct mohclass { char name[MAX_MUSICCLASS]; char dir[256]; @@ -230,6 +238,8 @@ static struct ao2_container *mohclasses; +static struct stat sb; + #define LOCAL_MPG_123 "/usr/local/bin/mpg123" #define MPG_123 "/usr/bin/mpg123" #define MAX_MP3S 256 @@ -306,6 +316,26 @@ ao2_cleanup(message); } +static int moh_check_dynamic_dir(void) +{ + if (dynamic_dir == NULL) + return 0; + + if (dynamic_dir[0] != '/') + return 0; + + if (stat(dynamic_dir,&sb) == -1) + return 0; + + if (!S_ISDIR(sb.st_mode)) + return 0; + + if (access(dynamic_dir, R_OK | X_OK) == -1) + return 0; + + return 1; +} + static void moh_files_release(struct ast_channel *chan, void *data) { struct moh_files_state *state; @@ -1222,6 +1252,80 @@ return class->total_files; } +static int moh_dynamic_playlist(struct mohclass *class) { + + char dir_path[PATH_MAX]; + char playlist[PATH_MAX]; + char filepath[PATH_MAX]; + char filename[PATH_MAX]; + FILE* f; + int firstline; + int i; + int flen; + int ret; + + ast_copy_string(dir_path, dynamic_dir, sizeof(dir_path)); + strncat(dir_path, "/", sizeof(dir_path) - 1); + strncat(dir_path, class->name, sizeof(dir_path) - 1); + + ast_copy_string(playlist, dir_path, sizeof(playlist)); + strncat(playlist, "/", sizeof(playlist)); + strncat(playlist, PLAYLIST_FILE_NAME, sizeof(playlist)); + + + if (!(f = fopen(playlist, "r"))) { + ast_log(LOG_WARNING, "Cannot open dynamic MOH playlist '%s' Fallback to dirlist\n", playlist); + if ((ret = moh_scan_files(class)) < 0) + return 0; + else + return ret; + } + + ast_debug(4, "Reading playlist '%s' for dynamic class '%s'\n", playlist, class->name); + + for (i = 0; i < class->total_files; i++) + ast_free(class->filearray[i]); + + class->total_files = 0; + firstline = 1; + + while (fgets(filename, sizeof(filename), f)) { + + flen = strlen(filename); + if (filename[flen-1] == '\n') { + flen--; + filename[flen] = '\0'; + } + + if (firstline == 1) { + firstline= 0; + if (flen == 1 && filename[0] == '%') { + ast_set_flag(class, MOH_RANDOMIZE); + ast_clear_flag(class, MOH_SORTALPHA); + continue; + } else { + ast_clear_flag(class, MOH_RANDOMIZE); + ast_set_flag(class, MOH_SORTALPHA); + } + } + + if (flen == 0) + continue; + + if (strchr(filename, '/')) + continue; + + snprintf(filepath, sizeof(filepath), "%s/%s", dir_path, filename); + + if (moh_add_file(class, filepath)) { + ast_log(LOG_ERROR, "moh_add_file failed for '%s'\n", filepath); + break; + } + } + + return class->total_files; +} + static int init_files_class(struct mohclass *class) { int res; @@ -1248,6 +1352,32 @@ return 0; } + +static int init_dynamic_class(struct mohclass *class) +{ + int res; + + res = moh_dynamic_playlist(class); + + if (res < 0) { + return -1; + } + + if (!res) { + if (option_verbose > 2) { + ast_verbose(VERBOSE_PREFIX_3 "Files not found in %s for moh class:%s\n", + class->dir, class->name); + } + return -1; + } + + return 0; +} + + + + + static void moh_rescan_files(void) { struct ao2_iterator i; struct mohclass *c; @@ -1257,6 +1387,8 @@ while ((c = ao2_iterator_next(&i))) { if (!strcasecmp(c->mode, "files")) { moh_scan_files(c); + } else if (!strcasecmp(c->mode, "dynamic")) { + moh_dynamic_playlist(c); } ao2_ref(c, -1); } @@ -1319,6 +1451,8 @@ return 0; } + + /*! * \note This function owns the reference it gets to moh if unref is true */ @@ -1351,6 +1485,13 @@ } return -1; } + } else if (!strcasecmp(moh->mode, "dynamic")) { + if (init_dynamic_class(moh)) { + if (unref) { + moh = mohclass_unref(moh, "unreffing potential new moh class (init_files_class failed)"); + } + return -1; + } } else if (!strcasecmp(moh->mode, "mp3") || !strcasecmp(moh->mode, "mp3nb") || !strcasecmp(moh->mode, "quietmp3") || !strcasecmp(moh->mode, "quietmp3nb") || !strcasecmp(moh->mode, "httpmp3") || !strcasecmp(moh->mode, "custom")) { @@ -1421,6 +1562,44 @@ return class; } +static struct mohclass* create_dynamic_class(const char* name) +{ + struct mohclass* mohclass; + + if ((mohclass = moh_class_malloc())) { + mohclass->realtime = 0; + ast_copy_string(mohclass->name, name, sizeof(mohclass->name)); + strcpy(mohclass->mode, "dynamic"); + snprintf(mohclass->dir, sizeof(mohclass->dir), "%s/%s", dynamic_dir, name); + + time(&mohclass->start); + mohclass->start -= respawn_time; + + if (!moh_dynamic_playlist(mohclass)) { + mohclass = mohclass_unref(mohclass, "unreffing potential mohclass (moh_dynamic_playlist failed)"); + return NULL; + } + + return mohclass; + } + + return NULL; +} + + +static int check_dynamic_class_name(const char* name) { + + if (!name) + return 0; + + if (strchr(name, '/')) + return 0; + + return 1; +} + + + static int local_ast_moh_start(struct ast_channel *chan, const char *mclass, const char *interpclass) { struct mohclass *mohclass = NULL; @@ -1428,6 +1607,8 @@ struct ast_variable *var = NULL; int res; int realtime_possible = ast_check_realtime("musiconhold"); + int skiptodefault = 0; + /* The following is the order of preference for which class to use: * 1) The channels explicitly set musicclass, which should *only* be @@ -1440,22 +1621,64 @@ * option. * 4) The default class. */ - if (!ast_strlen_zero(ast_channel_musicclass(chan))) { - mohclass = get_mohbyname(ast_channel_musicclass(chan), 1, 0); - if (!mohclass && realtime_possible) { - var = ast_load_realtime("musiconhold", "name", ast_channel_musicclass(chan), SENTINEL); + + + + + if (ast_test_flag(global_flags, MOH_DYNAMIC)) { + ast_debug(1, "MOH is dynamic\n"); + if (!ast_strlen_zero(ast_channel_musicclass(chan))) { + ast_debug(1, "chan->musicclass is '%s'\n", ast_channel_musicclass(chan)); + if (!strcmp(ast_channel_musicclass(chan), "default")) { + skiptodefault = 1; + } else if (!check_dynamic_class_name(ast_channel_musicclass(chan))) { + ast_log(LOG_WARNING, "Invalid dynamic class name '%s' - ingoring\n", ast_channel_musicclass(chan)); + } else { + mohclass = create_dynamic_class(ast_channel_musicclass(chan)); + } } - } - if (!mohclass && !var && !ast_strlen_zero(mclass)) { - mohclass = get_mohbyname(mclass, 1, 0); - if (!mohclass && realtime_possible) { - var = ast_load_realtime("musiconhold", "name", mclass, SENTINEL); + + if (!skiptodefault && !mohclass && !ast_strlen_zero(mclass)) { + ast_debug(1, "mclass is '%s'\n", mclass); + if (!strcmp(mclass, "default")) { + skiptodefault = 1; + } else if (!check_dynamic_class_name(mclass)) { + ast_log(LOG_WARNING, "Invalid dynamic class name '%s' - ingoring\n", mclass); + } else { + mohclass = create_dynamic_class(mclass); + } } - } - if (!mohclass && !var && !ast_strlen_zero(interpclass)) { - mohclass = get_mohbyname(interpclass, 1, 0); - if (!mohclass && realtime_possible) { - var = ast_load_realtime("musiconhold", "name", interpclass, SENTINEL); + + if (!skiptodefault && !mohclass && !ast_strlen_zero(interpclass)) { + ast_debug(1, "interpclass is '%s'\n", interpclass); + if (!strcmp(interpclass, "default")) { + skiptodefault = 1; + } else if (!check_dynamic_class_name(interpclass)) { + ast_log(LOG_WARNING, "Invalid dynamic class name '%s' - ingoring\n", interpclass); + } else { + mohclass = create_dynamic_class(interpclass); + } + } + + } else { + + if (!ast_strlen_zero(ast_channel_musicclass(chan))) { + mohclass = get_mohbyname(ast_channel_musicclass(chan), 1, 0); + if (!mohclass && realtime_possible) { + var = ast_load_realtime("musiconhold", "name", ast_channel_musicclass(chan), SENTINEL); + } + } + if (!mohclass && !var && !ast_strlen_zero(mclass)) { + mohclass = get_mohbyname(mclass, 1, 0); + if (!mohclass && realtime_possible) { + var = ast_load_realtime("musiconhold", "name", mclass, SENTINEL); + } + } + if (!mohclass && !var && !ast_strlen_zero(interpclass)) { + mohclass = get_mohbyname(interpclass, 1, 0); + if (!mohclass && realtime_possible) { + var = ast_load_realtime("musiconhold", "name", interpclass, SENTINEL); + } } } @@ -1785,11 +2008,23 @@ for (var = ast_variable_browse(cfg, cat); var; var = var->next) { if (!strcasecmp(var->name, "cachertclasses")) { ast_set2_flag(global_flags, ast_true(var->value), MOH_CACHERTCLASSES); + } else if (!strcasecmp(var->name, "dynamic")) { + ast_set2_flag(global_flags, ast_true(var->value), MOH_DYNAMIC); + } else if (!strcasecmp(var->name, "dynamic_dir")) { + if (dynamic_dir != NULL) + ast_free(dynamic_dir); + dynamic_dir = ast_strdup(var->value); } else { ast_log(LOG_WARNING, "Unknown option '%s' in [general] section of musiconhold.conf\n", var->name); } } } + + if (ast_test_flag(global_flags, MOH_DYNAMIC) && !moh_check_dynamic_dir()) { + ast_set2_flag(global_flags, 0, MOH_DYNAMIC); + ast_log(LOG_WARNING, "Invalid dynamic MOH directory '%s'. Disabling dynamic feature\n", dynamic_dir); + } + /* These names were deprecated in 1.4 and should not be used until after the next major release. */ if (!strcasecmp(cat, "classes") || !strcasecmp(cat, "moh_files") || !strcasecmp(cat, "general")) { @@ -2008,7 +2243,7 @@ return AST_MODULE_LOAD_DECLINE; } - if (!load_moh_classes(0) && ast_check_realtime("musiconhold") == 0) { /* No music classes configured, so skip it */ + if (!load_moh_classes(0) && ast_check_realtime("musiconhold") == 0 && !ast_test_flag(global_flags, MOH_DYNAMIC)) { /* No music classes configured, so skip it */ ast_log(LOG_WARNING, "No music on hold classes configured, " "disabling music on hold.\n"); } else { @@ -2033,7 +2268,7 @@ static int reload(void) { - if (load_moh_classes(1)) { + if (load_moh_classes(1) || ast_test_flag(global_flags, MOH_DYNAMIC)) { ast_install_music_functions(local_ast_moh_start, local_ast_moh_stop, local_ast_moh_cleanup); }