| |
| /* |
| * server.c - Speech Dispatcher server core |
| * |
| * Copyright (C) 2001,2002,2003, 2004, 2006, 2007 Brailcom, o.p.s |
| * |
| * This is free software; you can redistribute it and/or modify it |
| * under the terms of the GNU General Public License as published by |
| * the Free Software Foundation; either version 2, or (at your option) |
| * any later version. |
| * |
| * This software is distributed in the hope that it will be useful, |
| * but WITHOUT ANY WARRANTY; without even the implied warranty of |
| * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU |
| * General Public License for more details. |
| * |
| * You should have received a copy of the GNU General Public License |
| * along with this program. If not, see <https://www.gnu.org/licenses/>. |
| * |
| * $Id: server.c,v 1.85 2008-06-27 12:28:58 hanke Exp $ |
| */ |
| #ifdef HAVE_CONFIG_H |
| #include <config.h> |
| #endif |
| |
| #include "speechd.h" |
| #include "server.h" |
| #include "set.h" |
| #include "speaking.h" |
| #include "sem_functions.h" |
| #include "history.h" |
| |
| int last_message_id = 0; |
| |
| /* Put a message into its queue. |
| * |
| * Parameters: |
| * new -- the (allocated) message structure to queue, it must contain |
| * the text to be queued in new->buf |
| * fd -- file descriptor of the calling client (positive) |
| * unique id if the client is gone (negative) -- in this |
| * case it means we are reloading the message and the |
| * behavior is slightly different |
| * history_flag -- should this message be included in history? |
| * type -- type of the message (see ../../include/speechd_types.h) |
| * reparted -- if this is a preprocessed message reparted |
| * in more pieces |
| * It returns 0 on success, -1 otherwise. |
| */ |
| |
| #define COPY_SET_STR(name) \ |
| new->settings.name = (char*) g_strdup(settings->name); |
| |
| /* Queue a message _new_. When fd is a positive number, |
| it means we have a new message from the client on connection |
| fd and we should fill in the proper settings. When fd is |
| negative, it's absolute value is the client uid and the |
| message _new_ already contains a fully filled in settings |
| structure which should not be overwritten (on must be cautius |
| that the original client might not be still connected |
| to speechd). _history_flag_ indicates if inclusion into |
| history is desired and _reparted_ flag indicates whether |
| this message is a part of a reparted message (one of a block |
| of messages). */ |
| int |
| queue_message(TSpeechDMessage * new, int fd, int history_flag, |
| SPDMessageType type, int reparted) |
| { |
| TFDSetElement *settings; |
| TSpeechDMessage *message_copy; |
| int id; |
| GList *element; |
| |
| /* Check function parameters */ |
| if (new == NULL) |
| return -1; |
| if (new->buf == NULL) |
| return -1; |
| if (strlen(new->buf) < 1) |
| return -1; |
| |
| /* Find settings for this particular client */ |
| if (fd > 0) { |
| settings = get_client_settings_by_fd(fd); |
| if (settings == NULL) |
| FATAL |
| ("Couldn't find settings for active client, internal error."); |
| } else if (fd < 0) { |
| settings = get_client_settings_by_uid(-fd); |
| } else { |
| if (SPEECHD_DEBUG) |
| FATAL("fd == 0, this shouldn't happen..."); |
| return -1; |
| } |
| |
| MSG(5, "In queue_message desired output module is %s", |
| settings->output_module); |
| |
| if (fd > 0) { |
| /* Copy the settings to the new to-be-queued element */ |
| new->settings = *settings; |
| new->settings.type = type; |
| new->settings.index_mark = NULL; |
| COPY_SET_STR(client_name); |
| COPY_SET_STR(output_module); |
| COPY_SET_STR(msg_settings.voice.language); |
| COPY_SET_STR(msg_settings.voice.name); |
| |
| COPY_SET_STR(index_mark); |
| COPY_SET_STR(audio_output_method); |
| COPY_SET_STR(audio_oss_device); |
| COPY_SET_STR(audio_alsa_device); |
| COPY_SET_STR(audio_nas_server); |
| COPY_SET_STR(audio_pulse_server); |
| COPY_SET_STR(audio_pulse_device); |
| |
| /* And we set the global id (note that this is really global, not |
| * depending on the particular client, but unique) */ |
| last_message_id++; |
| new->id = last_message_id; |
| new->time = time(NULL); |
| |
| new->settings.paused_while_speaking = 0; |
| |
| /* Ignore possible SSML mode for anything but TEXT, as it doesn't make |
| * sense anything else and can cause problems if the message processing |
| * assumes SSML although it isn't */ |
| if (type != SPD_MSGTYPE_TEXT) |
| new->settings.ssml_mode = SPD_DATA_TEXT; |
| } |
| id = new->id; |
| |
| new->settings.reparted = reparted; |
| |
| MSG(5, "Queueing message |%s| with priority %d", new->buf, |
| settings->priority); |
| |
| /* If desired, put the message also into history */ |
| /* NOTE: This should be before we put it into queues() to |
| avoid conflicts with the other thread (it could delete |
| the message before we would copy it) */ |
| // if (history_flag){ |
| if (0) { |
| pthread_mutex_lock(&element_free_mutex); |
| history_add_message(new); |
| pthread_mutex_unlock(&element_free_mutex); |
| } |
| |
| pthread_mutex_lock(&element_free_mutex); |
| /* Put the element new to queue according to it's priority. */ |
| check_locked(&element_free_mutex); |
| switch (settings->priority) { |
| case SPD_IMPORTANT: |
| MessageQueue->p1 = g_list_append(MessageQueue->p1, new); |
| break; |
| case SPD_MESSAGE: |
| MessageQueue->p2 = g_list_append(MessageQueue->p2, new); |
| break; |
| case SPD_TEXT: |
| MessageQueue->p3 = g_list_append(MessageQueue->p3, new); |
| break; |
| case SPD_NOTIFICATION: |
| MessageQueue->p4 = g_list_append(MessageQueue->p4, new); |
| break; |
| case SPD_PROGRESS: |
| MessageQueue->p5 = g_list_append(MessageQueue->p5, new); |
| //clear last_p5_block if we get new block or no block message |
| element = g_list_last(last_p5_block); |
| if (!element || !element->data |
| || ((TSpeechDMessage *) (element->data))-> |
| settings.reparted != new->settings.reparted) { |
| g_list_foreach(last_p5_block, (GFunc) mem_free_message, |
| NULL); |
| g_list_free(last_p5_block); |
| last_p5_block = NULL; |
| } |
| // insert message |
| message_copy = spd_message_copy(new); |
| if (message_copy != NULL) |
| last_p5_block = |
| g_list_append(last_p5_block, message_copy); |
| |
| break; |
| default: |
| FATAL("Nonexistent priority given"); |
| } |
| |
| /* Look what is the highest priority of waiting |
| * messages and take the desired actions on other |
| * messages */ |
| /* TODO: Do the entire resolve_priorities() here is certainly |
| not the best approach possible. Especially the part that |
| calls output_stop() should be moved to speaking.c speak() |
| function in future */ |
| resolve_priorities(settings->priority); |
| pthread_mutex_unlock(&element_free_mutex); |
| |
| speaking_semaphore_post(); |
| |
| MSG(5, "Message inserted into queue."); |
| |
| return id; |
| } |
| |
| #undef COPY_SET_STR |
| |
| /* Switch data mode on for the particular client. */ |
| void server_data_on(int fd) |
| { |
| TSpeechDSock *speechd_socket = speechd_socket_get_by_fd(fd); |
| assert(speechd_socket); |
| /* Mark this client as ,,sending data'' */ |
| speechd_socket->awaiting_data = 1; |
| /* Create new output buffer */ |
| speechd_socket->o_buf = g_string_new(""); |
| MSG(4, "Switching to data mode..."); |
| return; |
| } |
| |
| /* Switch data mode off for the particular client. */ |
| void server_data_off(int fd) |
| { |
| TSpeechDSock *speechd_socket = speechd_socket_get_by_fd(fd); |
| assert(speechd_socket); |
| assert(speechd_socket->o_buf); |
| speechd_socket->o_bytes = 0; |
| g_string_free(speechd_socket->o_buf, 1); |
| speechd_socket->o_buf = NULL; |
| return; |
| } |
| |
| /* Serve the client on _fd_ if we got some activity. */ |
| int serve(int fd) |
| { |
| char *reply; /* Reply to the client */ |
| int ret; |
| |
| { |
| size_t bytes = 0; /* Number of bytes we got */ |
| int buflen = BUF_SIZE; |
| char *buf = (char *)g_malloc(buflen + 1); |
| |
| /* Read data from socket */ |
| /* Read exactly one complete line, the `parse' routine relies on it */ |
| { |
| while (1) { |
| int n = read(fd, buf + bytes, 1); |
| if (n <= 0) { |
| g_free(buf); |
| return -1; |
| } |
| /* Note, bytes is a 0-based index into buf. */ |
| if ((buf[bytes] == '\n') |
| && (bytes >= 1) && (buf[bytes - 1] == '\r')) { |
| buf[++bytes] = '\0'; |
| break; |
| } |
| if (buf[bytes] == '\0') |
| buf[bytes] = '?'; |
| if ((++bytes) == buflen) { |
| buflen *= 2; |
| buf = g_realloc(buf, buflen + 1); |
| } |
| } |
| } |
| |
| /* Parse the data and read the reply */ |
| MSG2(5, "protocol", "%d:DATA:|%s| (%lu)", fd, buf, (unsigned long) bytes); |
| reply = parse(buf, bytes, fd); |
| g_free(buf); |
| } |
| |
| if (reply == NULL) |
| FATAL("Internal error, reply from parse() is NULL!"); |
| |
| /* Send the reply to the socket */ |
| if (strlen(reply) == 0) { |
| g_free(reply); |
| return 0; |
| } |
| if (reply[0] != '9') { /* Don't reply to data etc. */ |
| pthread_mutex_lock(&socket_com_mutex); |
| MSG2(5, "protocol", "%d:REPLY:|%s|", fd, reply); |
| ret = write(fd, reply, strlen(reply)); |
| g_free(reply); |
| pthread_mutex_unlock(&socket_com_mutex); |
| if (ret == -1) { |
| MSG(5, "write() error: %s", strerror(errno)); |
| return -1; |
| } |
| } else { |
| g_free(reply); |
| } |
| |
| return 0; |
| } |