(closes issue #11825)
authorJeff Peeler <jpeeler@digium.com>
Wed, 13 Feb 2008 21:04:31 +0000 (21:04 +0000)
committerJeff Peeler <jpeeler@digium.com>
Wed, 13 Feb 2008 21:04:31 +0000 (21:04 +0000)
Reported by: ctooley
Patches:
      additional_eivr_commands.patch uploaded by ctooley (license 136)
Tested by: ctooley

git-svn-id: https://origsvn.digium.com/svn/asterisk/trunk@103662 65c4cc65-6c06-0410-ace0-fbb531ad65f3

apps/app_externalivr.c

index 8092fd9..dadb82e 100644 (file)
@@ -85,7 +85,11 @@ struct gen_state {
        int sample_queue;
 };
 
-static void send_child_event(FILE *handle, const char event, const char *data,
+static int eivr_comm(struct ast_channel *chan, struct ivr_localuser *u, 
+              int eivr_events_fd, int eivr_commands_fd, int eivr_errors_fd, 
+              const char *args);
+
+static void send_eivr_event(FILE *handle, const char event, const char *data,
        const struct ast_channel *chan)
 {
        char tmp[256];
@@ -222,6 +226,69 @@ static struct ast_generator gen =
        generate: gen_generate,
 };
 
+static void ast_eivr_getvariable(struct ast_channel *chan, char *data, char *outbuf, int outbuflen)
+{
+       // original input data: "G,var1,var2,"
+       // data passed as "data":  "var1,var2"
+       char *inbuf, *variable;
+
+       const char *value;
+       char *saveptr;
+       int j;
+
+       outbuf[0] = 0;
+
+       for (j = 1, inbuf = data; ; j++, inbuf = NULL) {
+               variable = strtok_r(inbuf, ",", &saveptr);
+               if (variable == NULL) {
+                       int outstrlen = strlen(outbuf);
+                       if(outstrlen && outbuf[outstrlen - 1] == ',') {
+                               outbuf[outstrlen - 1] = 0;
+                       }
+                       break;
+               }
+               
+               value = pbx_builtin_getvar_helper(chan, variable);
+               if(!value)
+                       value = "";
+               strncat(outbuf,variable,outbuflen);
+               strncat(outbuf,"=",outbuflen);
+               strncat(outbuf,value,outbuflen);
+               strncat(outbuf,",",outbuflen);
+       }
+};
+
+static void ast_eivr_setvariable(struct ast_channel *chan, char *data)
+{
+       char buf[1024];
+       char *value;
+
+       char *inbuf, *variable;
+
+       char *saveptr;
+       int j;
+
+       for(j=1, inbuf=data; ; j++, inbuf=NULL) {
+               variable = strtok_r(inbuf, ",", &saveptr);
+               ast_chan_log(LOG_DEBUG, chan, "Setting up a variable: %s\n", variable);
+               if(variable) {
+                       //variable contains "varname=value"
+                       strncpy(buf, variable, sizeof(buf));
+                       value = strchr(buf, '=');
+                       if(!value) 
+                               value="";
+                       else {
+                               value[0] = 0;
+                               value++;
+                       }
+                       pbx_builtin_setvar_helper(chan, buf, value);
+               }
+               else break;
+
+       }
+
+};
+
 static struct playlist_entry *make_entry(const char *filename)
 {
        struct playlist_entry *entry;
@@ -243,10 +310,7 @@ static int app_exec(struct ast_channel *chan, void *data)
        int res = -1;
        int gen_active = 0;
        int pid;
-       char *buf, *command;
-       FILE *child_commands = NULL;
-       FILE *child_errors = NULL;
-       FILE *child_events = NULL;
+       char *buf, *pipe_delim_argbuf, *pdargbuf_ptr;
        struct ivr_localuser foo = {
                .playlist = AST_LIST_HEAD_INIT_VALUE,
                .finishlist = AST_LIST_HEAD_INIT_VALUE,
@@ -271,25 +335,26 @@ static int app_exec(struct ast_channel *chan, void *data)
        buf = ast_strdupa(data);
        AST_STANDARD_APP_ARGS(args, buf);
 
+       //copy args and replace commas with pipes
+       pipe_delim_argbuf = ast_strdupa(data);
+       while((pdargbuf_ptr = strchr(pipe_delim_argbuf, ',')) != NULL)
+               pdargbuf_ptr[0] = '|';
+       
        if (pipe(child_stdin)) {
                ast_chan_log(LOG_WARNING, chan, "Could not create pipe for child input: %s\n", strerror(errno));
                goto exit;
        }
-
        if (pipe(child_stdout)) {
                ast_chan_log(LOG_WARNING, chan, "Could not create pipe for child output: %s\n", strerror(errno));
                goto exit;
        }
-
        if (pipe(child_stderr)) {
                ast_chan_log(LOG_WARNING, chan, "Could not create pipe for child errors: %s\n", strerror(errno));
                goto exit;
        }
-
        if (chan->_state != AST_STATE_UP) {
                ast_answer(chan);
        }
-
        if (ast_activate_generator(chan, &gen, u) < 0) {
                ast_chan_log(LOG_WARNING, chan, "Failed to activate generator\n");
                goto exit;
@@ -322,17 +387,6 @@ static int app_exec(struct ast_channel *chan, void *data)
                _exit(1);
        } else {
                /* parent process */
-               int child_events_fd = child_stdin[1];
-               int child_commands_fd = child_stdout[0];
-               int child_errors_fd = child_stderr[0];
-               struct ast_frame *f;
-               int ms;
-               int exception;
-               int ready_fd;
-               int waitfds[2] = { child_errors_fd, child_commands_fd };
-               struct ast_channel *rchan;
-
-               pthread_sigmask(SIG_SETMASK, &oldset, NULL);
 
                close(child_stdin[0]);
                child_stdin[0] = 0;
@@ -340,228 +394,263 @@ static int app_exec(struct ast_channel *chan, void *data)
                child_stdout[1] = 0;
                close(child_stderr[1]);
                child_stderr[1] = 0;
+               res = eivr_comm(chan, u, child_stdin[1], child_stdout[0], child_stderr[0], pipe_delim_argbuf);
 
-               if (!(child_events = fdopen(child_events_fd, "w"))) {
-                       ast_chan_log(LOG_WARNING, chan, "Could not open stream for child events\n");
-                       goto exit;
-               }
+               exit:
+               if (gen_active)
+                       ast_deactivate_generator(chan);
 
-               if (!(child_commands = fdopen(child_commands_fd, "r"))) {
-                       ast_chan_log(LOG_WARNING, chan, "Could not open stream for child commands\n");
-                       goto exit;
-               }
+               if (child_stdin[0])
+                       close(child_stdin[0]);
 
-               if (!(child_errors = fdopen(child_errors_fd, "r"))) {
-                       ast_chan_log(LOG_WARNING, chan, "Could not open stream for child errors\n");
-                       goto exit;
-               }
+               if (child_stdin[1])
+                       close(child_stdin[1]);
 
-               setvbuf(child_events, NULL, _IONBF, 0);
-               setvbuf(child_commands, NULL, _IONBF, 0);
-               setvbuf(child_errors, NULL, _IONBF, 0);
+               if (child_stdout[0])
+                       close(child_stdout[0]);
 
-               res = 0;
+               if (child_stdout[1])
+                       close(child_stdout[1]);
 
-               while (1) {
-                       if (ast_test_flag(chan, AST_FLAG_ZOMBIE)) {
-                               ast_chan_log(LOG_NOTICE, chan, "Is a zombie\n");
-                               res = -1;
-                               break;
-                       }
+               if (child_stderr[0])
+                       close(child_stderr[0]);
 
-                       if (ast_check_hangup(chan)) {
-                               ast_chan_log(LOG_NOTICE, chan, "Got check_hangup\n");
-                               send_child_event(child_events, 'H', NULL, chan);
-                               res = -1;
-                               break;
-                       }
+               if (child_stderr[1])
+                       close(child_stderr[1]);
 
-                       ready_fd = 0;
-                       ms = 100;
-                       errno = 0;
-                       exception = 0;
+               while ((entry = AST_LIST_REMOVE_HEAD(&u->playlist, list)))
+                       ast_free(entry);
 
-                       rchan = ast_waitfor_nandfds(&chan, 1, waitfds, 2, &exception, &ready_fd, &ms);
-
-                       if (!AST_LIST_EMPTY(&u->finishlist)) {
-                               AST_LIST_LOCK(&u->finishlist);
-                               while ((entry = AST_LIST_REMOVE_HEAD(&u->finishlist, list))) {
-                                       send_child_event(child_events, 'F', entry->filename, chan);
-                                       ast_free(entry);
-                               }
-                               AST_LIST_UNLOCK(&u->finishlist);
-                       }
-
-                       if (rchan) {
-                               /* the channel has something */
-                               f = ast_read(chan);
-                               if (!f) {
-                                       ast_chan_log(LOG_NOTICE, chan, "Returned no frame\n");
-                                       send_child_event(child_events, 'H', NULL, chan);
-                                       res = -1;
-                                       break;
-                               }
-
-                               if (f->frametype == AST_FRAME_DTMF) {
-                                       send_child_event(child_events, f->subclass, NULL, chan);
-                                       if (u->option_autoclear) {
-                                               if (!u->abort_current_sound && !u->playing_silence)
-                                                       send_child_event(child_events, 'T', NULL, chan);
-                                               AST_LIST_LOCK(&u->playlist);
-                                               while ((entry = AST_LIST_REMOVE_HEAD(&u->playlist, list))) {
-                                                       send_child_event(child_events, 'D', entry->filename, chan);
-                                                       ast_free(entry);
-                                               }
-                                               if (!u->playing_silence)
-                                                       u->abort_current_sound = 1;
-                                               AST_LIST_UNLOCK(&u->playlist);
-                                       }
-                               } else if ((f->frametype == AST_FRAME_CONTROL) && (f->subclass == AST_CONTROL_HANGUP)) {
-                                       ast_chan_log(LOG_NOTICE, chan, "Got AST_CONTROL_HANGUP\n");
-                                       send_child_event(child_events, 'H', NULL, chan);
-                                       ast_frfree(f);
-                                       res = -1;
-                                       break;
-                               }
-                               ast_frfree(f);
-                       } else if (ready_fd == child_commands_fd) {
-                               char input[1024];
-
-                               if (exception || feof(child_commands)) {
-                                       ast_chan_log(LOG_WARNING, chan, "Child process went away\n");
-                                       res = -1;
-                                       break;
-                               }
-
-                               if (!fgets(input, sizeof(input), child_commands))
-                                       continue;
-
-                               command = ast_strip(input);
-
-                               if (option_debug)
-                                       ast_chan_log(LOG_DEBUG, chan, "got command '%s'\n", input);
-
-                               if (strlen(input) < 4)
-                                       continue;
-
-                               if (input[0] == 'S') {
-                                       if (ast_fileexists(&input[2], NULL, u->chan->language) == -1) {
-                                               ast_chan_log(LOG_WARNING, chan, "Unknown file requested '%s'\n", &input[2]);
-                                               send_child_event(child_events, 'Z', NULL, chan);
-                                               strcpy(&input[2], "exception");
-                                       }
-                                       if (!u->abort_current_sound && !u->playing_silence)
-                                               send_child_event(child_events, 'T', NULL, chan);
-                                       AST_LIST_LOCK(&u->playlist);
-                                       while ((entry = AST_LIST_REMOVE_HEAD(&u->playlist, list))) {
-                                               send_child_event(child_events, 'D', entry->filename, chan);
-                                               ast_free(entry);
-                                       }
-                                       if (!u->playing_silence)
-                                               u->abort_current_sound = 1;
-                                       entry = make_entry(&input[2]);
-                                       if (entry)
-                                               AST_LIST_INSERT_TAIL(&u->playlist, entry, list);
-                                       AST_LIST_UNLOCK(&u->playlist);
-                               } else if (input[0] == 'A') {
-                                       if (ast_fileexists(&input[2], NULL, u->chan->language) == -1) {
-                                               ast_chan_log(LOG_WARNING, chan, "Unknown file requested '%s'\n", &input[2]);
-                                               send_child_event(child_events, 'Z', NULL, chan);
-                                               strcpy(&input[2], "exception");
-                                       }
-                                       entry = make_entry(&input[2]);
-                                       if (entry) {
-                                               AST_LIST_LOCK(&u->playlist);
-                                               AST_LIST_INSERT_TAIL(&u->playlist, entry, list);
-                                               AST_LIST_UNLOCK(&u->playlist);
-                                       }
-                               } else if (input[0] == 'E') {
-                                       ast_chan_log(LOG_NOTICE, chan, "Exiting: %s\n", &input[2]);
-                                       send_child_event(child_events, 'E', NULL, chan);
-                                       res = 0;
-                                       break;
-                               } else if (input[0] == 'H') {
-                                       ast_chan_log(LOG_NOTICE, chan, "Hanging up: %s\n", &input[2]);
-                                       send_child_event(child_events, 'H', NULL, chan);
-                                       res = -1;
-                                       break;
-                               } else if (input[0] == 'O') {
-                                       if (!strcasecmp(&input[2], "autoclear"))
-                                               u->option_autoclear = 1;
-                                       else if (!strcasecmp(&input[2], "noautoclear"))
-                                               u->option_autoclear = 0;
-                                       else
-                                               ast_chan_log(LOG_WARNING, chan, "Unknown option requested '%s'\n", &input[2]);
-                               } else if (input[0] == 'V') {
-                                       char *c;
-                                       c = strchr(&input[2], '=');
-                                       if (!c) {
-                                               send_child_event(child_events, 'Z', NULL, chan);
-                                       } else {
-                                               *c++ = '\0';
-                                               pbx_builtin_setvar_helper(chan, &input[2], c);
-                                       }
-                               }
-                       } else if (ready_fd == child_errors_fd) {
-                               char input[1024];
-
-                               if (exception || feof(child_errors)) {
-                                       ast_chan_log(LOG_WARNING, chan, "Child process went away\n");
-                                       res = -1;
-                                       break;
-                               }
-
-                               if (fgets(input, sizeof(input), child_errors)) {
-                                       command = ast_strip(input);
-                                       ast_chan_log(LOG_NOTICE, chan, "stderr: %s\n", command);
-                               }
-                       } else if ((ready_fd < 0) && ms) {
-                               if (errno == 0 || errno == EINTR)
-                                       continue;
-
-                               ast_chan_log(LOG_WARNING, chan, "Wait failed (%s)\n", strerror(errno));
-                               break;
-                       }
-               }
+               return res;
        }
-
- exit:
-       if (gen_active)
-               ast_deactivate_generator(chan);
-
-       if (child_events)
-               fclose(child_events);
-
-       if (child_commands)
-               fclose(child_commands);
-
-       if (child_errors)
-               fclose(child_errors);
-
-       if (child_stdin[0])
-               close(child_stdin[0]);
-
-       if (child_stdin[1])
-               close(child_stdin[1]);
-
-       if (child_stdout[0])
-               close(child_stdout[0]);
-
-       if (child_stdout[1])
-               close(child_stdout[1]);
-
-       if (child_stderr[0])
-               close(child_stderr[0]);
-
-       if (child_stderr[1])
-               close(child_stderr[1]);
-
-       while ((entry = AST_LIST_REMOVE_HEAD(&u->playlist, list)))
-               ast_free(entry);
-
-       return res;
 }
 
+static int eivr_comm(struct ast_channel *chan, struct ivr_localuser *u, 
+                               int eivr_events_fd, int eivr_commands_fd, int eivr_errors_fd, 
+                               const char *args)
+{
+       struct playlist_entry *entry;
+       struct ast_frame *f;
+       int ms;
+       int exception;
+       int ready_fd;
+       int waitfds[2] = { eivr_commands_fd, eivr_errors_fd };
+       struct ast_channel *rchan;
+       char *command;
+       int res = -1;
+  
+       FILE *eivr_commands = NULL;
+       FILE *eivr_errors = NULL;
+       FILE *eivr_events = NULL;
+       if (!(eivr_events = fdopen(eivr_events_fd, "w"))) {
+               ast_chan_log(LOG_WARNING, chan, "Could not open stream to send events\n");
+               goto exit;
+       }
+       if (!(eivr_commands = fdopen(eivr_commands_fd, "r"))) {
+               ast_chan_log(LOG_WARNING, chan, "Could not open stream to receive commands\n");
+               goto exit;
+       }
+       if(eivr_errors_fd) {  /*if opening a socket connection, error stream will not be used*/
+               if (!(eivr_errors = fdopen(eivr_errors_fd, "r"))) {
+                       ast_chan_log(LOG_WARNING, chan, "Could not open stream to receive errors\n");
+                       goto exit;
+               }
+       }
+       setvbuf(eivr_events, NULL, _IONBF, 0);
+       setvbuf(eivr_commands, NULL, _IONBF, 0);
+       if(eivr_errors)
+               setvbuf(eivr_errors, NULL, _IONBF, 0);
+
+       res = 0;
+       while (1) {
+               if (ast_test_flag(chan, AST_FLAG_ZOMBIE)) {
+                       ast_chan_log(LOG_NOTICE, chan, "Is a zombie\n");
+                       res = -1;
+                       break;
+               }
+               if (ast_check_hangup(chan)) {
+                       ast_chan_log(LOG_NOTICE, chan, "Got check_hangup\n");
+                       send_eivr_event(eivr_events, 'H', NULL, chan);
+                       res = -1;
+                       break;
+               }
+               ready_fd = 0;
+               ms = 100;
+               errno = 0;
+               exception = 0;
+               rchan = ast_waitfor_nandfds(&chan, 1, waitfds, 2, &exception, &ready_fd, &ms);
+               if (!AST_LIST_EMPTY(&u->finishlist)) {
+                       AST_LIST_LOCK(&u->finishlist);
+                       while ((entry = AST_LIST_REMOVE_HEAD(&u->finishlist, list))) {
+                               send_eivr_event(eivr_events, 'F', entry->filename, chan);
+                               ast_free(entry);
+                       }
+                       AST_LIST_UNLOCK(&u->finishlist);
+               }
+               if (rchan) {
+                       /* the channel has something */
+                       f = ast_read(chan);
+                       if (!f) {
+                               ast_chan_log(LOG_NOTICE, chan, "Returned no frame\n");
+                               send_eivr_event(eivr_events, 'H', NULL, chan);
+                               res = -1;
+                               break;
+                       }
+                       if (f->frametype == AST_FRAME_DTMF) {
+                               send_eivr_event(eivr_events, f->subclass, NULL, chan);
+                               if (u->option_autoclear) {
+                                       if (!u->abort_current_sound && !u->playing_silence)
+                                               send_eivr_event(eivr_events, 'T', NULL, chan);
+                                       AST_LIST_LOCK(&u->playlist);
+                                       while ((entry = AST_LIST_REMOVE_HEAD(&u->playlist, list))) {
+                                               send_eivr_event(eivr_events, 'D', entry->filename, chan);
+                                               ast_free(entry);
+                                       }
+                                       if (!u->playing_silence)
+                                               u->abort_current_sound = 1;
+                                       AST_LIST_UNLOCK(&u->playlist);
+                               }
+                       } else if ((f->frametype == AST_FRAME_CONTROL) && (f->subclass == AST_CONTROL_HANGUP)) {
+                               ast_chan_log(LOG_NOTICE, chan, "Got AST_CONTROL_HANGUP\n");
+                               send_eivr_event(eivr_events, 'H', NULL, chan);
+                               ast_frfree(f);
+                               res = -1;
+                               break;
+                       }
+                       ast_frfree(f);
+               } else if (ready_fd == eivr_commands_fd) {
+                       char input[1024];
+                       if (exception || feof(eivr_commands)) {
+                               ast_chan_log(LOG_WARNING, chan, "Child process went away\n");
+                               res = -1;
+                               break;
+                       }
+  
+                       if (!fgets(input, sizeof(input), eivr_commands))
+                               continue;
+                       command = ast_strip(input);
+  
+                       if (option_debug)
+                               ast_chan_log(LOG_DEBUG, chan, "got command '%s'\n", input);
+  
+                       if (strlen(input) < 4)
+                               continue;
+  
+                       if (input[0] == 'P') {
+                               send_eivr_event(eivr_events, 'P', args, chan);
+                       } else if (input[0] == 'S') {
+                               if (ast_fileexists(&input[2], NULL, u->chan->language) == -1) {
+                                       ast_chan_log(LOG_WARNING, chan, "Unknown file requested '%s'\n", &input[2]);
+                                       send_eivr_event(eivr_events, 'Z', NULL, chan);
+                                       strcpy(&input[2], "exception");
+                               }
+                               if (!u->abort_current_sound && !u->playing_silence)
+                                       send_eivr_event(eivr_events, 'T', NULL, chan);
+                               AST_LIST_LOCK(&u->playlist);
+                               while ((entry = AST_LIST_REMOVE_HEAD(&u->playlist, list))) {
+                                       send_eivr_event(eivr_events, 'D', entry->filename, chan);
+                                       ast_free(entry);
+                               }
+                               if (!u->playing_silence)
+                                       u->abort_current_sound = 1;
+                               entry = make_entry(&input[2]);
+                               if (entry)
+                                       AST_LIST_INSERT_TAIL(&u->playlist, entry, list);
+                               AST_LIST_UNLOCK(&u->playlist);
+                       } else if (input[0] == 'A') {
+                               if (ast_fileexists(&input[2], NULL, u->chan->language) == -1) {
+                                       ast_chan_log(LOG_WARNING, chan, "Unknown file requested '%s'\n", &input[2]);
+                                       send_eivr_event(eivr_events, 'Z', NULL, chan);
+                                       strcpy(&input[2], "exception");
+                               }
+                               entry = make_entry(&input[2]);
+                               if (entry) {
+                                       AST_LIST_LOCK(&u->playlist);
+                                       AST_LIST_INSERT_TAIL(&u->playlist, entry, list);
+                                       AST_LIST_UNLOCK(&u->playlist);
+                               }
+                       } else if (input[0] == 'G') {
+                               // A get variable message:  "G,variable1,variable2,..."
+                               char response[2048];
+                               ast_chan_log(LOG_NOTICE, chan, "Getting a Variable out of the channel: %s\n", &input[2]);
+                               ast_eivr_getvariable(chan, &input[2], response, sizeof(response));
+                               send_eivr_event(eivr_events, 'G', response, chan);
+                       } else if (input[0] == 'V') {
+                               // A set variable message:  "V,variablename=foo"
+                               ast_chan_log(LOG_NOTICE, chan, "Setting a Variable up: %s\n", &input[2]);
+                               ast_eivr_setvariable(chan, &input[2]);
+                       } else if (input[0] == 'L') {
+                               ast_chan_log(LOG_NOTICE, chan, "Log message from EIVR: %s\n", &input[2]);
+                       } else if (input[0] == 'X') {
+                               ast_chan_log(LOG_NOTICE, chan, "Exiting ExternalIVR: %s\n", &input[2]);
+                               //TODO: add deprecation debug message for X command here
+                               res = 0;
+                               break;
+                       } else if (input[0] == 'E') {
+                               ast_chan_log(LOG_NOTICE, chan, "Exiting: %s\n", &input[2]);
+                               send_eivr_event(eivr_events, 'E', NULL, chan);
+                               res = 0;
+                               break;
+                       } else if (input[0] == 'H') {
+                               ast_chan_log(LOG_NOTICE, chan, "Hanging up: %s\n", &input[2]);
+                               send_eivr_event(eivr_events, 'H', NULL, chan);
+                               res = -1;
+                               break;
+                       } else if (input[0] == 'O') {
+                               if (!strcasecmp(&input[2], "autoclear"))
+                                       u->option_autoclear = 1;
+                               else if (!strcasecmp(&input[2], "noautoclear"))
+                                       u->option_autoclear = 0;
+                               else
+                                       ast_chan_log(LOG_WARNING, chan, "Unknown option requested '%s'\n", &input[2]);
+                       }
+               } else if (eivr_errors_fd && ready_fd == eivr_errors_fd) {
+                       char input[1024];
+  
+                       if (exception || feof(eivr_errors)) {
+                               ast_chan_log(LOG_WARNING, chan, "Child process went away\n");
+                               res = -1;
+                               break;
+                       }
+                       if (fgets(input, sizeof(input), eivr_errors)) {
+                               command = ast_strip(input);
+                               ast_chan_log(LOG_NOTICE, chan, "stderr: %s\n", command);
+                       }
+               } else if ((ready_fd < 0) && ms) { 
+                       if (errno == 0 || errno == EINTR)
+                               continue;
+                       ast_chan_log(LOG_WARNING, chan, "Wait failed (%s)\n", strerror(errno));
+                       break;
+               }
+       }
+  
+exit:
+       if (eivr_events)
+               fclose(eivr_events);
+       if (eivr_commands)
+               fclose(eivr_commands);
+
+       if (eivr_errors)
+               fclose(eivr_errors);
+  
+       return res;
+  }
+
 static int unload_module(void)
 {
        return ast_unregister_application(app);