Index: CHANGES =================================================================== --- CHANGES (revision 412895) +++ CHANGES (working copy) @@ -15,7 +15,26 @@ ------------------------------------------------------------------------------ --- Functionality changes from Asterisk 12.2.0 to Asterisk 12.3.0 ------------ ------------------------------------------------------------------------------ +res_musiconhold +------------------ + * Dynamically controlled file based MoH added. + As a switch between normal and alternative behavior, boolean variable + 'dynamic' is used in MoH config file. + By setting dynamic=yes new behavior is switched on. + When dynamic behavior is swithed on, MoH classes in musiconhold.conf and realtime are ignored, except [default] + class. On the other hand dynamic class named 'default' is ignored too. + New variable 'dynamic_dir' defines directory, where dynamic classes are + defined. Each first level subdirectory of dynamic_dir defines one MoH class + with same name as directory name. + If class directory contains playlist file 'playlist.txt' content of + the file defines audiofiles in class and their order. Otherwise directory + is scanned same way as for standard MoH class with mode=files. + Playlist expects one file on line, without path and without extension. + Files must be placed in class directory. + If first line of playlist contains exactly one character '%', files will be + ordered randomly. + ARI ------------------ * A new Playback URI 'tone' has been added. Tones are specified either as Index: configs/musiconhold.conf.sample =================================================================== --- configs/musiconhold.conf.sample (revision 412895) +++ configs/musiconhold.conf.sample (working copy) @@ -42,6 +42,28 @@ ; understand when it loads. ; + +; By setting dynamic=yes new dynamic behavior is switched on. +; +; All static MoH classes in musiconhold.conf and realtime are ignored, except [default] +; class. On the other hand dynamic class named 'default' is ignored too. +; +; Variable 'dynamic_dir' defines directory, where dynamic classes are +; defined. Each first level subdirectory of dynamic_dir defines one MoH class +; with same name as directory name. +; If class directory contains playlist file 'playlist.txt' content of +; the file defines audiofiles in class and their order. Otherwise directory +; is scanned same way as for standard MoH class with mode=files. +; +; Playlist expects one file on line, without path and without extension. +; Files must be placed in class directory. +; If first line of playlist contains exactly one character '%', files will be +; ordered randomly. + +; dynamic=yes +; dynamic_dir=/var/lib/asterisk/moh-dynamic + + [default] mode=files directory=moh Index: res/res_musiconhold.c =================================================================== --- res/res_musiconhold.c (revision 412895) +++ res/res_musiconhold.c (working copy) @@ -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) /*!< Use dynamic filesystem model for MoH */ /* 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]; @@ -306,6 +314,28 @@ ao2_cleanup(message); } +static int moh_dynamic_check_basedir(void) +{ + struct stat sb; + + 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,95 @@ return class->total_files; } +static int moh_dynamic_create_playlist(struct mohclass *class) { + + char filename_buffer[PATH_MAX]; + char playlist_item[PATH_MAX]; + FILE* f; + char* filenamepos; + int firstline; + int i; + int flen; + int ret; + int pathlen; + int freebuflen; + + + if (strlen(dynamic_dir) + strlen(class->name) + strlen(PLAYLIST_FILE_NAME) + 2 >= PATH_MAX) { + ast_log(LOG_WARNING, "Cannot create playlist for dynamic MoH class '%s' path too long", class->name); + return 0; + } + + sprintf(filename_buffer, "%s/%s/", dynamic_dir, class->name); + pathlen = strlen(filename_buffer); + filenamepos = filename_buffer + pathlen; + + freebuflen = PATH_MAX - pathlen - 1; + + /* create playlist path in filename_buffer */ + strcpy(filenamepos, PLAYLIST_FILE_NAME); + + if (!(f = fopen(filename_buffer, "r"))) { + ast_log(LOG_WARNING, "Cannot open dynamic MOH playlist '%s' Fallback to dirlist\n", filename_buffer); + if ((ret = moh_scan_files(class)) < 0) + return 0; + else + return ret; + } + + ast_debug(4, "Reading playlist '%s' for dynamic class '%s'\n", filename_buffer, class->name); + + for (i = 0; i < class->total_files; i++) + ast_free(class->filearray[i]); + + class->total_files = 0; + firstline = 1; + + while (fgets(playlist_item, sizeof(playlist_item), f)) { + + flen = strlen(playlist_item); + if (playlist_item[flen-1] == '\n') { + flen--; + playlist_item[flen] = '\0'; + } + + /* random ordering if '%' on first line of playlist */ + if (firstline == 1) { + firstline = 0; + if (flen == 1 && playlist_item[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; + + /* audio file name must be without path */ + if (strchr(playlist_item, '/')) + continue; + + if (flen > freebuflen) { + ast_log(LOG_ERROR, "Playlist item '%s' ignored, path too long", playlist_item); + continue; + } + + /* create audio file name in filename_buffer */ + strcpy(filenamepos, playlist_item); + + if (moh_add_file(class, filename_buffer)) { + ast_log(LOG_ERROR, "moh_add_file failed for '%s'\n", filename_buffer); + break; + } + } + + return class->total_files; +} + static int init_files_class(struct mohclass *class) { int res; @@ -1248,6 +1367,27 @@ return 0; } + +static int init_dynamic_class(struct mohclass *class) +{ + int res; + + res = moh_dynamic_create_playlist(class); + + if (res < 0) { + return -1; + } + + if (!res) { + ast_verb(3, "Files not found in %s for dynamic 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 +1397,8 @@ while ((c = ao2_iterator_next(&i))) { if (!strcasecmp(c->mode, "files")) { moh_scan_files(c); + } else if (!strcasecmp(c->mode, "dynamic")) { + moh_dynamic_create_playlist(c); } ao2_ref(c, -1); } @@ -1319,6 +1461,8 @@ return 0; } + + /*! * \note This function owns the reference it gets to moh if unref is true */ @@ -1351,6 +1495,13 @@ } return -1; } + } else if (!strcasecmp(moh->mode, "dynamic")) { + if (init_dynamic_class(moh)) { + if (unref) { + moh = mohclass_unref(moh, "unreffing potential new dynamic 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 +1572,47 @@ return class; } +static struct mohclass* moh_dynamic_create_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_create_playlist(mohclass)) { + mohclass = mohclass_unref(mohclass, "unreffing potential mohclass (moh_dynamic_playlist failed)"); + return NULL; + } + + return mohclass; + } + + return NULL; +} + + +static int moh_dynamic_check_classname(const char* name) { + + if (name == NULL) + return 0; + + if (strchr(name, '/')) + return 0; + + if (strlen(name) >= MAX_MUSICCLASS) + 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 +1620,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,23 +1634,65 @@ * 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 (!moh_dynamic_check_classname(ast_channel_musicclass(chan))) { + ast_log(LOG_WARNING, "Invalid dynamic class name '%s' - ingoring\n", ast_channel_musicclass(chan)); + } else { + mohclass = moh_dynamic_create_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 (!moh_dynamic_check_classname(mclass)) { + ast_log(LOG_WARNING, "Invalid dynamic class name '%s' - ingoring\n", mclass); + } else { + mohclass = moh_dynamic_create_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 (!moh_dynamic_check_classname(interpclass)) { + ast_log(LOG_WARNING, "Invalid dynamic class name '%s' - ingoring\n", interpclass); + } else { + mohclass = moh_dynamic_create_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); + } + } } if (!mohclass && !var) { @@ -1785,11 +2021,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_dynamic_check_basedir()) { + 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 +2256,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 +2281,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); }