<commit_before>/*
  libopentts.c - Shared library for easy acces to OpenTTS functions
 *
 * Copyright (C) 2001, 2002, 2003, 2006, 2007, 2008 Brailcom, o.p.s.
 * Copyright (C) 2010 OpenTTS Developers
 *
 * 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 package; see the file COPYING.  If not, write to
 * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
 * Boston, MA 02110-1301, USA.
 *
 */

#ifdef HAVE_CONFIG_H
#include <config.h>
#endif

#include <sys/types.h>
#include <wchar.h>
#include <sys/socket.h>
#include <netinet/tcp.h>
#include <netinet/in.h>
#include <sys/un.h>
#include <arpa/inet.h>
#include <unistd.h>

#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <stdarg.h>
#include <string.h>
#include <ctype.h>
#include <glib.h>
#include <errno.h>
#include <assert.h>

#include <def.h>
#include <opentts/libopentts.h>

/* Comment/uncomment to switch debugging on/off */
// #define LIBOPENTTS_DEBUG 1

#ifdef LIBOPENTTS_DEBUG
/* Debugging */
static FILE *spd_debug;
#endif

/* Unless there is an fatal error, it doesn't print anything */
#define SPD_FATAL(msg) { printf("Fatal error (libopentts) [%s:%d]:"msg, __FILE__, __LINE__); fflush(stdout); exit(EXIT_FAILURE); }

/* --------------  Private functions headers ------------------------*/

static int spd_set_priority(SPDConnection * connection, SPDPriority priority);
static char *escape_dot(const char *text);
static int isanum(char *str);
static char *get_reply(SPDConnection * connection);
static int get_err_code(char *reply);
static char *get_param_str(char *reply, int num, int *err);
static int get_param_int(char *reply, int num, int *err);
static void *xmalloc(size_t bytes);
static void xfree(void *ptr);
static int ret_ok(char *reply);
static void SPD_DBG(char *format, ...);
static void *spd_events_handler(void *);
static void init_debugging(void);
static void do_autospawn(void);
static SPDConnection *make_connection(SPDConnectionMethod method);
static SPDConnection *init_connection_state(SPDConnection * connection,
					    const char *client_name,
					    const char *connection_name,
					    const char *user_name,
					    SPDConnectionMode mode);
static pthread_mutex_t *new_mutex(void);
static void destroy_mutex(pthread_mutex_t * mutex);
static pthread_cond_t *new_cond_variable(void);
static void destroy_cond_variable(pthread_cond_t * cond);

pthread_mutex_t spd_logging_mutex;

#ifndef HAVE_STRNDUP
/* Added by <NAME> - strndup is a gcc-ism */
char *strndup(const char *s, size_t n)
{
	size_t nAvail;
	char *p;

	if (!s)
		return 0;

	if (strlen(s) > n)
		nAvail = n + 1;
	else
		nAvail = strlen(s) + 1;
	p = malloc(nAvail);
	memcpy(p, s, nAvail);
	p[nAvail - 1] = '\0';

	return p;
}
#endif /* HAVE_STRNDUP */

#define INITIAL_BUF_SIZE 120
#define GETLINE_FAILURE -1
static ssize_t otts_getline(char **lineptr, size_t * n, FILE * f)
{
	char ch;
	ssize_t buf_pos = 0;
	ssize_t needed = 2;	/* Always buf_pos + 2 (see below). */
	size_t new_length = 0;
	char *temp;

	if ((n == NULL) || (lineptr == NULL) || (f == NULL)) {
		errno = EINVAL;
		return GETLINE_FAILURE;
	}

	if (errno != 0)
		errno = 0;

	if ((*lineptr == NULL) || (*n == 0)) {
		*n = INITIAL_BUF_SIZE;
		*lineptr = malloc(*n * sizeof(char));

		if (*lineptr == NULL) {
			*n = 0;
			return GETLINE_FAILURE;	/* Out of memory. */
		}
	}

	/*
	 * For each iteration of this loop, the following holds.
	 * There are buf_pos characters in the buffer.  When we read another
	 * character, we want to store it, and we also need enough
	 * room for a NUL.  So we need to realloc as soon as our capacity
	 * becomes less than buf_pos + 2.
	 * Hence the variable "needed" which always equals buf_pos + 2.
	 */

	while ((ch = getc(f)) != EOF) {
		if (errno != 0)
			return GETLINE_FAILURE;

		if (needed > *n) {
			new_length = *n * 2;
			if (new_length <= *n) {	/* Overflow. */
				errno = ENOMEM;
				/* We couldn't store the character, */
				/* so put it back on the stream. */
				ungetc(ch, f);
				return GETLINE_FAILURE;
			}
			temp =
			    (char *)realloc(*lineptr,
					    new_length * sizeof(char));
			if (temp == NULL) {
				ungetc(ch, f);
				return GETLINE_FAILURE;
			}
			*n = new_length;
			*lineptr = temp;
		}
		(*lineptr)[buf_pos++] = ch;

		if (ch == '\n')
			break;

		if (needed == SSIZE_MAX) {
			/* We'll overflow ssize_t on the next round. */
			errno = ENOMEM;
			return GETLINE_FAILURE;
		}
		needed++;
	}
	(*lineptr)[buf_pos] = '\0';

	if (buf_pos == 0) {
		buf_pos = GETLINE_FAILURE;
	}
	return buf_pos;
}

/* --------------------- Public functions ------------------------- */

#define SPD_REPLY_BUF_SIZE 65536

/* Opens a new connection to openttsd.
 * Returns socket file descriptor of the created connection
 * or -1 if no connection was opened. */

SPDConnection *spd_open(const char *client_name, const char *connection_name,
			const char *user_name, SPDConnectionMode mode)
{
	return spd_open2(client_name, connection_name, user_name,
			 mode, SPD_METHOD_UNIX_SOCKET, 1);
}

SPDConnection *spd_open2(const char *client_name, const char *connection_name,
			 const char *user_name, SPDConnectionMode mode,
			 SPDConnectionMethod method, int autospawn)
{
	SPDConnection *connection;
	const char *conn_name;
	const char *usr_name;

	if (client_name == NULL)
		return NULL;

	if (user_name == NULL)
		usr_name = (char *)g_get_user_name();
	else
		usr_name = user_name;

	if (connection_name == NULL)
		conn_name = "main";
	else
		conn_name = connection_name;

	init_debugging();

	connection = make_connection(method);

	/* Autospawn -- check if openttsd is not running and if so, start it */
	if (autospawn && connection == NULL) {
		do_autospawn();
		connection = make_connection(method);
	}

	if (connection != NULL)
		connection = init_connection_state(connection,
						   client_name, conn_name,
						   usr_name, mode);

	return connection;
}

#define RET(r) \
    { \
    pthread_mutex_unlock(connection->ssip_mutex); \
    return r; \
    }

/* Close a connection to openttsd */
void spd_close(SPDConnection * connection)
{

	if (connection->ssip_mutex != NULL) {
		pthread_mutex_lock(connection->ssip_mutex);

		if (connection->mode == SPD_MODE_THREADED) {
			if (connection->events_thread != NULL) {
				pthread_cancel(*connection->events_thread);
				pthread_join(*connection->events_thread, NULL);
				free(connection->events_thread);
			}
			destroy_mutex(connection->mutex_reply_ready);
			destroy_mutex(connection->mutex_reply_ack);
			destroy_cond_variable(connection->cond_reply_ready);
			destroy_cond_variable(connection->cond_reply_ack);
			connection->mode = SPD_MODE_SINGLE;
		}

		pthread_mutex_unlock(connection->ssip_mutex);
		destroy_mutex(connection->ssip_mutex);
	}

	/* close the stream and socket */
	if (connection->stream != NULL) {
		fclose(connection->stream);
		connection->socket = -1;
		/* So we don't try to close the socket twice. */
	}

	if (connection->socket != -1)
		close(connection->socket);

	xfree(connection);
}

/* Helper functions for spd_say. */
static inline int
spd_say_prepare(SPDConnection * connection, SPDPriority priority,
		const char *text, char **escaped_text)
{
	int ret = 0;

	SPD_DBG("Text to say is: %s", text);

	/* Insure that there is no escape sequence in the text */
	*escaped_text = escape_dot(text);
	/* Caller is now responsible for escaped_text. */
	if (*escaped_text == NULL) {	/* Out of memory. */
		SPD_DBG("spd_say could not allocate memory.");
		ret = -1;
	} else {
		/* Set priority */
		SPD_DBG("Setting priority");
		ret = spd_set_priority(connection, priority);
		if (!ret) {
			/* Start the data flow */
			SPD_DBG("Sending SPEAK");
			ret = spd_execute_command_wo_mutex(connection, "speak");
			if (ret) {
				SPD_DBG("Error: Can't start data flow!");
			}
		}
	}

	return ret;
}

static inline int spd_say_sending(SPDConnection * connection, const char *text)
{
	int msg_id = -1;
	int err = 0;
	char *reply = NULL;
	char *pret = NULL;

	/* Send data */
	SPD_DBG("Sending data");
	pret = spd_send_data_wo_mutex(connection, text, SPD_NO_REPLY);
	if (pret == NULL) {
		SPD_DBG("Can't send data wo mutex");
	} else {
		/* Terminate data flow */
		SPD_DBG("Terminating data flow");
		err =
		    spd_execute_command_with_reply(connection, "\r\n.", &reply);
		if (err) {
			SPD_DBG("Can't terminate data flow");
		} else {
			msg_id = get_param_int(reply, 1, &err);
			if (err < 0) {
				SPD_DBG
				    ("Can't determine SSIP message unique ID parameter.");
				msg_id = -1;
			}
		}
	}

	xfree(reply);
	xfree(pret);
	return msg_id;
}

/* Say TEXT with priority PRIORITY.
 * Returns msg_uid on success, -1 otherwise. */
int spd_say(SPDConnection * connection, SPDPriority priority, const char *text)
{
	char *escaped_text = NULL;
	int msg_id = -1;
	int prepare_failed = 0;

	if (text != NULL) {
		pthread_mutex_lock(connection->ssip_mutex);

		prepare_failed =
		    spd_say_prepare(connection, priority, text, &escaped_text);
		if (!prepare_failed)
			msg_id = spd_say_sending(connection, escaped_text);

		xfree(escaped_text);
		pthread_mutex_unlock(connection->ssip_mutex);
	} else {
		SPD_DBG("spd_say called with a NULL argument for <text>");
	}

	SPD_DBG("Returning from spd_say");
	return msg_id;
}

/* The same as spd_say, accepts also formated strings */
int
spd_sayf(SPDConnection * connection, SPDPriority priority, const char *format,
	 ...)
{
	static int ret;
	va_list args;
	char *buf;

	if (format == NULL)
		return -1;

	/* Print the text to buffer */
	va_start(args, format);
	buf = g_strdup_vprintf(format, args);
	va_end(args);

	/* Send the buffer to openttsd */
	ret = spd_say(connection, priority, buf);
	xfree(buf);

	return ret;
}

int spd_stop(SPDConnection * connection)
{
	return spd_execute_command(connection, "STOP SELF");
}

int spd_stop_all(SPDConnection * connection)
{
	return spd_execute_command(connection, "STOP ALL");
}

int spd_stop_uid(SPDConnection * connection, int target_uid)
{
	static char command[16];

	sprintf(command, "STOP %d", target_uid);
	return spd_execute_command(connection, command);
}

int spd_cancel(SPDConnection * connection)
{
	return spd_execute_command(connection, "CANCEL SELF");
}

int spd_cancel_all(SPDConnection * connection)
{
	return spd_execute_command(connection, "CANCEL ALL");
}

int spd_cancel_uid(SPDConnection * connection, int target_uid)
{
	static char command[16];

	sprintf(command, "CANCEL %d", target_uid);
	return spd_execute_command(connection, command);
}

int spd_pause(SPDConnection * connection)
{
	return spd_execute_command(connection, "PAUSE SELF");
}

int spd_pause_all(SPDConnection * connection)
{
	return spd_execute_command(connection, "PAUSE ALL");
}

int spd_pause_uid(SPDConnection * connection, int target_uid)
{
	char command[16];

	sprintf(command, "PAUSE %d", target_uid);
	return spd_execute_command(connection, command);
}

int spd_resume(SPDConnection * connection)
{
	return spd_execute_command(connection, "RESUME SELF");
}

int spd_resume_all(SPDConnection * connection)
{
	return spd_execute_command(connection, "RESUME ALL");
}

int spd_resume_uid(SPDConnection * connection, int target_uid)
{
	static char command[16];

	sprintf(command, "RESUME %d", target_uid);
	return spd_execute_command(connection, command);
}

int
spd_key(SPDConnection * connection, SPDPriority priority, const char *key_name)
{
	char *command_key;
	int ret;

	if (key_name == NULL)
		return -1;

	pthread_mutex_lock(connection->ssip_mutex);

	ret = spd_set_priority(connection, priority);
	if (ret)
		RET(-1);

	command_key = g_strdup_printf("KEY %s", key_name);
	ret = spd_execute_command_wo_mutex(connection, command_key);
	xfree(command_key);
	if (ret)
		RET(-1);

	pthread_mutex_unlock(connection->ssip_mutex);

	return 0;
}

int
spd_char(SPDConnection * connection, SPDPriority priority,
	 const char *character)
{
	static char command[16];
	int ret;

	if (character == NULL)
		return -1;
	if (strlen(character) > 6)
		return -1;

	pthread_mutex_lock(connection->ssip_mutex);

	ret = spd_set_priority(connection, priority);
	if (ret)
		RET(-1);

	sprintf(command, "CHAR %s", character);
	ret = spd_execute_command_wo_mutex(connection, command);
	if (ret)
		RET(-1);

	pthread_mutex_unlock(connection->ssip_mutex);

	return 0;
}

int
spd_wchar(SPDConnection * connection, SPDPriority priority, wchar_t wcharacter)
{
	static char command[16];
	char character[8];
	int ret;

	pthread_mutex_lock(connection->ssip_mutex);

	ret = wcrtomb(character, wcharacter, NULL);
	if (ret <= 0)
		RET(-1);

	ret = spd_set_priority(connection, priority);
	if (ret)
		RET(-1);

	assert(character != NULL);
	sprintf(command, "CHAR %s", character);
	ret = spd_execute_command_wo_mutex(connection, command);
	if (ret)
		RET(-1);

	pthread_mutex_unlock(connection->ssip_mutex);

	return 0;
}

int
spd_sound_icon(SPDConnection * connection, SPDPriority priority,
	       const char *icon_name)
{
	char *command;
	int ret;

	if (icon_name == NULL)
		return -1;

	pthread_mutex_lock(connection->ssip_mutex);

	ret = spd_set_priority(connection, priority);
	if (ret)
		RET(-1);

	command = g_strdup_printf("SOUND_ICON %s", icon_name);
	ret = spd_execute_command_wo_mutex(connection, command);
	xfree(command);
	if (ret)
		RET(-1);

	pthread_mutex_unlock(connection->ssip_mutex);

	return 0;
}

int
spd_w_set_punctuation(SPDConnection * connection, SPDPunctuation type,
		      const char *who)
{
	char command[32];
	int ret;

	if (type == SPD_PUNCT_ALL)
		sprintf(command, "SET %s PUNCTUATION all", who);
	if (type == SPD_PUNCT_NONE)
		sprintf(command, "SET %s PUNCTUATION none", who);
	if (type == SPD_PUNCT_SOME)
		sprintf(command, "SET %s PUNCTUATION some", who);

	ret = spd_execute_command(connection, command);

	return ret;
}

int
spd_w_set_capital_letters(SPDConnection * connection, SPDCapitalLetters type,
			  const char *who)
{
	char command[64];
	int ret;

	if (type == SPD_CAP_NONE)
		sprintf(command, "SET %s CAP_LET_RECOGN none", who);
	if (type == SPD_CAP_SPELL)
		sprintf(command, "SET %s CAP_LET_RECOGN spell", who);
	if (type == SPD_CAP_ICON)
		sprintf(command, "SET %s CAP_LET_RECOGN icon", who);

	ret = spd_execute_command(connection, command);

	return ret;
}

int
spd_w_set_spelling(SPDConnection * connection, SPDSpelling type,
		   const char *who)
{
	char command[32];
	int ret;

	if (type == SPD_SPELL_ON)
		sprintf(command, "SET %s SPELLING on", who);
	if (type == SPD_SPELL_OFF)
		sprintf(command, "SET %s SPELLING off", who);

	ret = spd_execute_command(connection, command);

	return ret;
}

int spd_set_data_mode(SPDConnection * connection, SPDDataMode mode)
{
	char command[32];
	int ret;

	if (mode == SPD_DATA_TEXT)
		sprintf(command, "SET SELF SSML_MODE off");
	if (mode == SPD_DATA_SSML)
		sprintf(command, "SET SELF SSML_MODE on");

	ret = spd_execute_command(connection, command);

	return ret;
}

int
spd_w_set_voice_type(SPDConnection * connection, SPDVoiceType type,
		     const char *who)
{
	static char command[64];

	switch (type) {
	case SPD_MALE1:
		sprintf(command, "SET %s VOICE MALE1", who);
		break;
	case SPD_MALE2:
		sprintf(command, "SET %s VOICE MALE2", who);
		break;
	case SPD_MALE3:
		sprintf(command, "SET %s VOICE MALE3", who);
		break;
	case SPD_FEMALE1:
		sprintf(command, "SET %s VOICE FEMALE1", who);
		break;
	case SPD_FEMALE2:
		sprintf(command, "SET %s VOICE FEMALE2", who);
		break;
	case SPD_FEMALE3:
		sprintf(command, "SET %s VOICE FEMALE3", who);
		break;
	case SPD_CHILD_MALE:
		sprintf(command, "SET %s VOICE CHILD_MALE", who);
		break;
	case SPD_CHILD_FEMALE:
		sprintf(command, "SET %s VOICE CHILD_FEMALE", who);
		break;
	default:
		return -1;
	}

	return spd_execute_command(connection, command);
}

#define SPD_SET_COMMAND_INT(param, ssip_name, condition) \
    int \
    spd_w_set_ ## param (SPDConnection *connection, signed int val, const char* who) \
    { \
        static char command[64]; \
        if ((!condition)) return -1; \
        sprintf(command, "SET %s " #ssip_name " %d", who, val); \
        return spd_execute_command(connection, command); \
    } \
    int \
    spd_set_ ## param (SPDConnection *connection, signed int val) \
    { \
        return spd_w_set_ ## param (connection, val, "SELF"); \
    } \
    int \
    spd_set_ ## param ## _all(SPDConnection *connection, signed int val) \
    { \
        return spd_w_set_ ## param (connection, val, "ALL"); \
    } \
    int \
    spd_set_ ## param ## _uid(SPDConnection *connection, signed int val, unsigned int uid) \
    { \
        char who[8]; \
        sprintf(who, "%d", uid); \
        return spd_w_set_ ## param (connection, val, who); \
    }

#define SPD_SET_COMMAND_STR(param, ssip_name) \
    int \
    spd_w_set_ ## param (SPDConnection *connection, const char *str, const char* who) \
    { \
        char *command; \
        int ret; \
        if (str == NULL) return -1; \
        command = g_strdup_printf("SET %s " #param " %s", \
                              who, str); \
        ret = spd_execute_command(connection, command); \
        xfree(command); \
        return ret; \
    } \
    int \
    spd_set_ ## param (SPDConnection *connection, const char *str) \
    { \
        return spd_w_set_ ## param (connection, str, "SELF"); \
    } \
    int \
    spd_set_ ## param ## _all(SPDConnection *connection, const char *str) \
    { \
        return spd_w_set_ ## param (connection, str, "ALL"); \
    } \
    int \
    spd_set_ ## param ## _uid(SPDConnection *connection, const char *str, unsigned int uid) \
    { \
        char who[8]; \
        sprintf(who, "%d", uid); \
        return spd_w_set_ ## param (connection, str, who); \
    }

#define SPD_SET_COMMAND_SPECIAL(param, type) \
    int \
    spd_set_ ## param (SPDConnection *connection, type val) \
    { \
        return spd_w_set_ ## param (connection, val, "SELF"); \
    } \
    int \
    spd_set_ ## param ## _all(SPDConnection *connection, type val) \
    { \
        return spd_w_set_ ## param (connection, val, "ALL"); \
    } \
    int \
    spd_set_ ## param ## _uid(SPDConnection *connection, type val, unsigned int uid) \
    { \
        char who[8]; \
        sprintf(who, "%d", uid); \
        return spd_w_set_ ## param (connection, val, who); \
    }

	SPD_SET_COMMAND_INT(voice_rate, RATE, ((val >= OTTS_VOICE_RATE_MIN)
	                                       && (val <= OTTS_VOICE_RATE_MAX)))
	SPD_SET_COMMAND_INT(voice_pitch, PITCH, ((val >= OTTS_VOICE_PITCH_MIN)
	                                         && (val <= OTTS_VOICE_PITCH_MAX)))
	SPD_SET_COMMAND_INT(volume, VOLUME, ((val >= OTTS_VOICE_VOLUME_MIN)
	                                     && (val <= OTTS_VOICE_VOLUME_MAX)))

	SPD_SET_COMMAND_STR(language, LANGUAGE)
	SPD_SET_COMMAND_STR(output_module, OUTPUT_MODULE)
	SPD_SET_COMMAND_STR(synthesis_voice, SYNTHESIS_VOICE)

	SPD_SET_COMMAND_SPECIAL(punctuation, SPDPunctuation)
	SPD_SET_COMMAND_SPECIAL(capital_letters, SPDCapitalLetters)
	SPD_SET_COMMAND_SPECIAL(spelling, SPDSpelling)
	SPD_SET_COMMAND_SPECIAL(voice_type, SPDVoiceType)
#undef SPD_SET_COMMAND_INT
#undef SPD_SET_COMMAND_STR
#undef SPD_SET_COMMAND_SPECIAL
int
spd_set_notification_on(SPDConnection * connection,
			SPDNotification notification)
{
	if (connection->mode == SPD_MODE_THREADED)
		return spd_set_notification(connection, notification, "on");
	else
		return -1;
}

int
spd_set_notification_off(SPDConnection * connection,
			 SPDNotification notification)
{
	if (connection->mode == SPD_MODE_THREADED)
		return spd_set_notification(connection, notification, "off");
	else
		return -1;
}

#define NOTIFICATION_SET(val, ssip_val) \
    if (notification & val){ \
	sprintf(command, "SET SELF NOTIFICATION "ssip_val" %s", state);\
	ret = spd_execute_command_wo_mutex(connection, command);\
	if (ret < 0) RET(-1);\
    }

int
spd_set_notification(SPDConnection * connection, SPDNotification notification,
		     const char *state)
{
	static char command[64];
	int ret;

	if (connection->mode != SPD_MODE_THREADED)
		return -1;

	if (state == NULL) {
		SPD_DBG("Requested state is NULL");
		return -1;
	}
	if (strcmp(state, "on") && strcmp(state, "off")) {
		SPD_DBG("Invalid argument for spd_set_notification: %s", state);
		return -1;
	}

	pthread_mutex_lock(connection->ssip_mutex);

	NOTIFICATION_SET(SPD_INDEX_MARKS, "index_marks");
	NOTIFICATION_SET(SPD_BEGIN, "begin");
	NOTIFICATION_SET(SPD_END, "end");
	NOTIFICATION_SET(SPD_CANCEL, "cancel");
	NOTIFICATION_SET(SPD_PAUSE, "pause");
	NOTIFICATION_SET(SPD_RESUME, "resume");
	NOTIFICATION_SET(SPD_RESUME, "pause");

	pthread_mutex_unlock(connection->ssip_mutex);

	return 0;
}

#undef NOTIFICATION_SET

/* spd_list_modules retrieves information about the available output modules.
   The return value is a null-terminated array of strings containing output module
   names.
*/

char **spd_list_modules(SPDConnection * connection)
{
	char **available_modules;
	available_modules =
	    spd_execute_command_with_list_reply(connection,
						"LIST OUTPUT_MODULES");
	return available_modules;
}

char **spd_list_voices(SPDConnection * connection)
{
	char **voices;
	voices = spd_execute_command_with_list_reply(connection, "LIST VOICES");
	return voices;
}

SPDVoice **spd_list_synthesis_voices(SPDConnection * connection)
{
	char **svoices_str;
	SPDVoice **svoices;
	int i, num_items;
	svoices_str =
	    spd_execute_command_with_list_reply(connection,
						"LIST SYNTHESIS_VOICES");

	if (svoices_str == NULL)
		return NULL;

	for (i = 0;; i++)
		if (svoices_str[i] == NULL)
			break;
	num_items = i;
	svoices = (SPDVoice **) malloc((num_items + 1) * sizeof(SPDVoice *));

	for (i = 0; i <= num_items; i++) {
		const char delimiters[] = " ";
		char *running;

		if (svoices_str[i] == NULL)
			break;
		running = strdup(svoices_str[i]);

		svoices[i] = (SPDVoice *) malloc(sizeof(SPDVoice));
		svoices[i]->name = strsep(&running, delimiters);
		svoices[i]->language = strsep(&running, delimiters);
		svoices[i]->variant = strsep(&running, delimiters);
		assert(svoices[i]->name != NULL);
	}

	svoices[num_items] = NULL;

	return svoices;
}

char **spd_execute_command_with_list_reply(SPDConnection * connection,
					   char *command)
{
	char *reply, *line;
	int err;
	int max_items = 50;
	char **result;
	int i, ret;

	result = malloc((max_items + 1) * sizeof(char *));
<commit_msg>fix off-by-one error in spd_list_synthesis_voices<commit_after>/*
  libopentts.c - Shared library for easy acces to OpenTTS functions
 *
 * Copyright (C) 2001, 2002, 2003, 2006, 2007, 2008 Brailcom, o.p.s.
 * Copyright (C) 2010 OpenTTS Developers
 *
 * 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 package; see the file COPYING.  If not, write to
 * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
 * Boston, MA 02110-1301, USA.
 *
 */

#ifdef HAVE_CONFIG_H
#include <config.h>
#endif

#include <sys/types.h>
#include <wchar.h>
#include <sys/socket.h>
#include <netinet/tcp.h>
#include <netinet/in.h>
#include <sys/un.h>
#include <arpa/inet.h>
#include <unistd.h>

#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <stdarg.h>
#include <string.h>
#include <ctype.h>
#include <glib.h>
#include <errno.h>
#include <assert.h>

#include <def.h>
#include <opentts/libopentts.h>

/* Comment/uncomment to switch debugging on/off */
// #define LIBOPENTTS_DEBUG 1

#ifdef LIBOPENTTS_DEBUG
/* Debugging */
static FILE *spd_debug;
#endif

/* Unless there is an fatal error, it doesn't print anything */
#define SPD_FATAL(msg) { printf("Fatal error (libopentts) [%s:%d]:"msg, __FILE__, __LINE__); fflush(stdout); exit(EXIT_FAILURE); }

/* --------------  Private functions headers ------------------------*/

static int spd_set_priority(SPDConnection * connection, SPDPriority priority);
static char *escape_dot(const char *text);
static int isanum(char *str);
static char *get_reply(SPDConnection * connection);
static int get_err_code(char *reply);
static char *get_param_str(char *reply, int num, int *err);
static int get_param_int(char *reply, int num, int *err);
static void *xmalloc(size_t bytes);
static void xfree(void *ptr);
static int ret_ok(char *reply);
static void SPD_DBG(char *format, ...);
static void *spd_events_handler(void *);
static void init_debugging(void);
static void do_autospawn(void);
static SPDConnection *make_connection(SPDConnectionMethod method);
static SPDConnection *init_connection_state(SPDConnection * connection,
					    const char *client_name,
					    const char *connection_name,
					    const char *user_name,
					    SPDConnectionMode mode);
static pthread_mutex_t *new_mutex(void);
static void destroy_mutex(pthread_mutex_t * mutex);
static pthread_cond_t *new_cond_variable(void);
static void destroy_cond_variable(pthread_cond_t * cond);

pthread_mutex_t spd_logging_mutex;

#ifndef HAVE_STRNDUP
/* Added by <NAME> - strndup is a gcc-ism */
char *strndup(const char *s, size_t n)
{
	size_t nAvail;
	char *p;

	if (!s)
		return 0;

	if (strlen(s) > n)
		nAvail = n + 1;
	else
		nAvail = strlen(s) + 1;
	p = malloc(nAvail);
	memcpy(p, s, nAvail);
	p[nAvail - 1] = '\0';

	return p;
}
#endif /* HAVE_STRNDUP */

#define INITIAL_BUF_SIZE 120
#define GETLINE_FAILURE -1
static ssize_t otts_getline(char **lineptr, size_t * n, FILE * f)
{
	char ch;
	ssize_t buf_pos = 0;
	ssize_t needed = 2;	/* Always buf_pos + 2 (see below). */
	size_t new_length = 0;
	char *temp;

	if ((n == NULL) || (lineptr == NULL) || (f == NULL)) {
		errno = EINVAL;
		return GETLINE_FAILURE;
	}

	if (errno != 0)
		errno = 0;

	if ((*lineptr == NULL) || (*n == 0)) {
		*n = INITIAL_BUF_SIZE;
		*lineptr = malloc(*n * sizeof(char));

		if (*lineptr == NULL) {
			*n = 0;
			return GETLINE_FAILURE;	/* Out of memory. */
		}
	}

	/*
	 * For each iteration of this loop, the following holds.
	 * There are buf_pos characters in the buffer.  When we read another
	 * character, we want to store it, and we also need enough
	 * room for a NUL.  So we need to realloc as soon as our capacity
	 * becomes less than buf_pos + 2.
	 * Hence the variable "needed" which always equals buf_pos + 2.
	 */

	while ((ch = getc(f)) != EOF) {
		if (errno != 0)
			return GETLINE_FAILURE;

		if (needed > *n) {
			new_length = *n * 2;
			if (new_length <= *n) {	/* Overflow. */
				errno = ENOMEM;
				/* We couldn't store the character, */
				/* so put it back on the stream. */
				ungetc(ch, f);
				return GETLINE_FAILURE;
			}
			temp =
			    (char *)realloc(*lineptr,
					    new_length * sizeof(char));
			if (temp == NULL) {
				ungetc(ch, f);
				return GETLINE_FAILURE;
			}
			*n = new_length;
			*lineptr = temp;
		}
		(*lineptr)[buf_pos++] = ch;

		if (ch == '\n')
			break;

		if (needed == SSIZE_MAX) {
			/* We'll overflow ssize_t on the next round. */
			errno = ENOMEM;
			return GETLINE_FAILURE;
		}
		needed++;
	}
	(*lineptr)[buf_pos] = '\0';

	if (buf_pos == 0) {
		buf_pos = GETLINE_FAILURE;
	}
	return buf_pos;
}

/* --------------------- Public functions ------------------------- */

#define SPD_REPLY_BUF_SIZE 65536

/* Opens a new connection to openttsd.
 * Returns socket file descriptor of the created connection
 * or -1 if no connection was opened. */

SPDConnection *spd_open(const char *client_name, const char *connection_name,
			const char *user_name, SPDConnectionMode mode)
{
	return spd_open2(client_name, connection_name, user_name,
			 mode, SPD_METHOD_UNIX_SOCKET, 1);
}

SPDConnection *spd_open2(const char *client_name, const char *connection_name,
			 const char *user_name, SPDConnectionMode mode,
			 SPDConnectionMethod method, int autospawn)
{
	SPDConnection *connection;
	const char *conn_name;
	const char *usr_name;

	if (client_name == NULL)
		return NULL;

	if (user_name == NULL)
		usr_name = (char *)g_get_user_name();
	else
		usr_name = user_name;

	if (connection_name == NULL)
		conn_name = "main";
	else
		conn_name = connection_name;

	init_debugging();

	connection = make_connection(method);

	/* Autospawn -- check if openttsd is not running and if so, start it */
	if (autospawn && connection == NULL) {
		do_autospawn();
		connection = make_connection(method);
	}

	if (connection != NULL)
		connection = init_connection_state(connection,
						   client_name, conn_name,
						   usr_name, mode);

	return connection;
}

#define RET(r) \
    { \
    pthread_mutex_unlock(connection->ssip_mutex); \
    return r; \
    }

/* Close a connection to openttsd */
void spd_close(SPDConnection * connection)
{

	if (connection->ssip_mutex != NULL) {
		pthread_mutex_lock(connection->ssip_mutex);

		if (connection->mode == SPD_MODE_THREADED) {
			if (connection->events_thread != NULL) {
				pthread_cancel(*connection->events_thread);
				pthread_join(*connection->events_thread, NULL);
				free(connection->events_thread);
			}
			destroy_mutex(connection->mutex_reply_ready);
			destroy_mutex(connection->mutex_reply_ack);
			destroy_cond_variable(connection->cond_reply_ready);
			destroy_cond_variable(connection->cond_reply_ack);
			connection->mode = SPD_MODE_SINGLE;
		}

		pthread_mutex_unlock(connection->ssip_mutex);
		destroy_mutex(connection->ssip_mutex);
	}

	/* close the stream and socket */
	if (connection->stream != NULL) {
		fclose(connection->stream);
		connection->socket = -1;
		/* So we don't try to close the socket twice. */
	}

	if (connection->socket != -1)
		close(connection->socket);

	xfree(connection);
}

/* Helper functions for spd_say. */
static inline int
spd_say_prepare(SPDConnection * connection, SPDPriority priority,
		const char *text, char **escaped_text)
{
	int ret = 0;

	SPD_DBG("Text to say is: %s", text);

	/* Insure that there is no escape sequence in the text */
	*escaped_text = escape_dot(text);
	/* Caller is now responsible for escaped_text. */
	if (*escaped_text == NULL) {	/* Out of memory. */
		SPD_DBG("spd_say could not allocate memory.");
		ret = -1;
	} else {
		/* Set priority */
		SPD_DBG("Setting priority");
		ret = spd_set_priority(connection, priority);
		if (!ret) {
			/* Start the data flow */
			SPD_DBG("Sending SPEAK");
			ret = spd_execute_command_wo_mutex(connection, "speak");
			if (ret) {
				SPD_DBG("Error: Can't start data flow!");
			}
		}
	}

	return ret;
}

static inline int spd_say_sending(SPDConnection * connection, const char *text)
{
	int msg_id = -1;
	int err = 0;
	char *reply = NULL;
	char *pret = NULL;

	/* Send data */
	SPD_DBG("Sending data");
	pret = spd_send_data_wo_mutex(connection, text, SPD_NO_REPLY);
	if (pret == NULL) {
		SPD_DBG("Can't send data wo mutex");
	} else {
		/* Terminate data flow */
		SPD_DBG("Terminating data flow");
		err =
		    spd_execute_command_with_reply(connection, "\r\n.", &reply);
		if (err) {
			SPD_DBG("Can't terminate data flow");
		} else {
			msg_id = get_param_int(reply, 1, &err);
			if (err < 0) {
				SPD_DBG
				    ("Can't determine SSIP message unique ID parameter.");
				msg_id = -1;
			}
		}
	}

	xfree(reply);
	xfree(pret);
	return msg_id;
}

/* Say TEXT with priority PRIORITY.
 * Returns msg_uid on success, -1 otherwise. */
int spd_say(SPDConnection * connection, SPDPriority priority, const char *text)
{
	char *escaped_text = NULL;
	int msg_id = -1;
	int prepare_failed = 0;

	if (text != NULL) {
		pthread_mutex_lock(connection->ssip_mutex);

		prepare_failed =
		    spd_say_prepare(connection, priority, text, &escaped_text);
		if (!prepare_failed)
			msg_id = spd_say_sending(connection, escaped_text);

		xfree(escaped_text);
		pthread_mutex_unlock(connection->ssip_mutex);
	} else {
		SPD_DBG("spd_say called with a NULL argument for <text>");
	}

	SPD_DBG("Returning from spd_say");
	return msg_id;
}

/* The same as spd_say, accepts also formated strings */
int
spd_sayf(SPDConnection * connection, SPDPriority priority, const char *format,
	 ...)
{
	static int ret;
	va_list args;
	char *buf;

	if (format == NULL)
		return -1;

	/* Print the text to buffer */
	va_start(args, format);
	buf = g_strdup_vprintf(format, args);
	va_end(args);

	/* Send the buffer to openttsd */
	ret = spd_say(connection, priority, buf);
	xfree(buf);

	return ret;
}

int spd_stop(SPDConnection * connection)
{
	return spd_execute_command(connection, "STOP SELF");
}

int spd_stop_all(SPDConnection * connection)
{
	return spd_execute_command(connection, "STOP ALL");
}

int spd_stop_uid(SPDConnection * connection, int target_uid)
{
	static char command[16];

	sprintf(command, "STOP %d", target_uid);
	return spd_execute_command(connection, command);
}

int spd_cancel(SPDConnection * connection)
{
	return spd_execute_command(connection, "CANCEL SELF");
}

int spd_cancel_all(SPDConnection * connection)
{
	return spd_execute_command(connection, "CANCEL ALL");
}

int spd_cancel_uid(SPDConnection * connection, int target_uid)
{
	static char command[16];

	sprintf(command, "CANCEL %d", target_uid);
	return spd_execute_command(connection, command);
}

int spd_pause(SPDConnection * connection)
{
	return spd_execute_command(connection, "PAUSE SELF");
}

int spd_pause_all(SPDConnection * connection)
{
	return spd_execute_command(connection, "PAUSE ALL");
}

int spd_pause_uid(SPDConnection * connection, int target_uid)
{
	char command[16];

	sprintf(command, "PAUSE %d", target_uid);
	return spd_execute_command(connection, command);
}

int spd_resume(SPDConnection * connection)
{
	return spd_execute_command(connection, "RESUME SELF");
}

int spd_resume_all(SPDConnection * connection)
{
	return spd_execute_command(connection, "RESUME ALL");
}

int spd_resume_uid(SPDConnection * connection, int target_uid)
{
	static char command[16];

	sprintf(command, "RESUME %d", target_uid);
	return spd_execute_command(connection, command);
}

int
spd_key(SPDConnection * connection, SPDPriority priority, const char *key_name)
{
	char *command_key;
	int ret;

	if (key_name == NULL)
		return -1;

	pthread_mutex_lock(connection->ssip_mutex);

	ret = spd_set_priority(connection, priority);
	if (ret)
		RET(-1);

	command_key = g_strdup_printf("KEY %s", key_name);
	ret = spd_execute_command_wo_mutex(connection, command_key);
	xfree(command_key);
	if (ret)
		RET(-1);

	pthread_mutex_unlock(connection->ssip_mutex);

	return 0;
}

int
spd_char(SPDConnection * connection, SPDPriority priority,
	 const char *character)
{
	static char command[16];
	int ret;

	if (character == NULL)
		return -1;
	if (strlen(character) > 6)
		return -1;

	pthread_mutex_lock(connection->ssip_mutex);

	ret = spd_set_priority(connection, priority);
	if (ret)
		RET(-1);

	sprintf(command, "CHAR %s", character);
	ret = spd_execute_command_wo_mutex(connection, command);
	if (ret)
		RET(-1);

	pthread_mutex_unlock(connection->ssip_mutex);

	return 0;
}

int
spd_wchar(SPDConnection * connection, SPDPriority priority, wchar_t wcharacter)
{
	static char command[16];
	char character[8];
	int ret;

	pthread_mutex_lock(connection->ssip_mutex);

	ret = wcrtomb(character, wcharacter, NULL);
	if (ret <= 0)
		RET(-1);

	ret = spd_set_priority(connection, priority);
	if (ret)
		RET(-1);

	assert(character != NULL);
	sprintf(command, "CHAR %s", character);
	ret = spd_execute_command_wo_mutex(connection, command);
	if (ret)
		RET(-1);

	pthread_mutex_unlock(connection->ssip_mutex);

	return 0;
}

int
spd_sound_icon(SPDConnection * connection, SPDPriority priority,
	       const char *icon_name)
{
	char *command;
	int ret;

	if (icon_name == NULL)
		return -1;

	pthread_mutex_lock(connection->ssip_mutex);

	ret = spd_set_priority(connection, priority);
	if (ret)
		RET(-1);

	command = g_strdup_printf("SOUND_ICON %s", icon_name);
	ret = spd_execute_command_wo_mutex(connection, command);
	xfree(command);
	if (ret)
		RET(-1);

	pthread_mutex_unlock(connection->ssip_mutex);

	return 0;
}

int
spd_w_set_punctuation(SPDConnection * connection, SPDPunctuation type,
		      const char *who)
{
	char command[32];
	int ret;

	if (type == SPD_PUNCT_ALL)
		sprintf(command, "SET %s PUNCTUATION all", who);
	if (type == SPD_PUNCT_NONE)
		sprintf(command, "SET %s PUNCTUATION none", who);
	if (type == SPD_PUNCT_SOME)
		sprintf(command, "SET %s PUNCTUATION some", who);

	ret = spd_execute_command(connection, command);

	return ret;
}

int
spd_w_set_capital_letters(SPDConnection * connection, SPDCapitalLetters type,
			  const char *who)
{
	char command[64];
	int ret;

	if (type == SPD_CAP_NONE)
		sprintf(command, "SET %s CAP_LET_RECOGN none", who);
	if (type == SPD_CAP_SPELL)
		sprintf(command, "SET %s CAP_LET_RECOGN spell", who);
	if (type == SPD_CAP_ICON)
		sprintf(command, "SET %s CAP_LET_RECOGN icon", who);

	ret = spd_execute_command(connection, command);

	return ret;
}

int
spd_w_set_spelling(SPDConnection * connection, SPDSpelling type,
		   const char *who)
{
	char command[32];
	int ret;

	if (type == SPD_SPELL_ON)
		sprintf(command, "SET %s SPELLING on", who);
	if (type == SPD_SPELL_OFF)
		sprintf(command, "SET %s SPELLING off", who);

	ret = spd_execute_command(connection, command);

	return ret;
}

int spd_set_data_mode(SPDConnection * connection, SPDDataMode mode)
{
	char command[32];
	int ret;

	if (mode == SPD_DATA_TEXT)
		sprintf(command, "SET SELF SSML_MODE off");
	if (mode == SPD_DATA_SSML)
		sprintf(command, "SET SELF SSML_MODE on");

	ret = spd_execute_command(connection, command);

	return ret;
}

int
spd_w_set_voice_type(SPDConnection * connection, SPDVoiceType type,
		     const char *who)
{
	static char command[64];

	switch (type) {
	case SPD_MALE1:
		sprintf(command, "SET %s VOICE MALE1", who);
		break;
	case SPD_MALE2:
		sprintf(command, "SET %s VOICE MALE2", who);
		break;
	case SPD_MALE3:
		sprintf(command, "SET %s VOICE MALE3", who);
		break;
	case SPD_FEMALE1:
		sprintf(command, "SET %s VOICE FEMALE1", who);
		break;
	case SPD_FEMALE2:
		sprintf(command, "SET %s VOICE FEMALE2", who);
		break;
	case SPD_FEMALE3:
		sprintf(command, "SET %s VOICE FEMALE3", who);
		break;
	case SPD_CHILD_MALE:
		sprintf(command, "SET %s VOICE CHILD_MALE", who);
		break;
	case SPD_CHILD_FEMALE:
		sprintf(command, "SET %s VOICE CHILD_FEMALE", who);
		break;
	default:
		return -1;
	}

	return spd_execute_command(connection, command);
}

#define SPD_SET_COMMAND_INT(param, ssip_name, condition) \
    int \
    spd_w_set_ ## param (SPDConnection *connection, signed int val, const char* who) \
    { \
        static char command[64]; \
        if ((!condition)) return -1; \
        sprintf(command, "SET %s " #ssip_name " %d", who, val); \
        return spd_execute_command(connection, command); \
    } \
    int \
    spd_set_ ## param (SPDConnection *connection, signed int val) \
    { \
        return spd_w_set_ ## param (connection, val, "SELF"); \
    } \
    int \
    spd_set_ ## param ## _all(SPDConnection *connection, signed int val) \
    { \
        return spd_w_set_ ## param (connection, val, "ALL"); \
    } \
    int \
    spd_set_ ## param ## _uid(SPDConnection *connection, signed int val, unsigned int uid) \
    { \
        char who[8]; \
        sprintf(who, "%d", uid); \
        return spd_w_set_ ## param (connection, val, who); \
    }

#define SPD_SET_COMMAND_STR(param, ssip_name) \
    int \
    spd_w_set_ ## param (SPDConnection *connection, const char *str, const char* who) \
    { \
        char *command; \
        int ret; \
        if (str == NULL) return -1; \
        command = g_strdup_printf("SET %s " #param " %s", \
                              who, str); \
        ret = spd_execute_command(connection, command); \
        xfree(command); \
        return ret; \
    } \
    int \
    spd_set_ ## param (SPDConnection *connection, const char *str) \
    { \
        return spd_w_set_ ## param (connection, str, "SELF"); \
    } \
    int \
    spd_set_ ## param ## _all(SPDConnection *connection, const char *str) \
    { \
        return spd_w_set_ ## param (connection, str, "ALL"); \
    } \
    int \
    spd_set_ ## param ## _uid(SPDConnection *connection, const char *str, unsigned int uid) \
    { \
        char who[8]; \
        sprintf(who, "%d", uid); \
        return spd_w_set_ ## param (connection, str, who); \
    }

#define SPD_SET_COMMAND_SPECIAL(param, type) \
    int \
    spd_set_ ## param (SPDConnection *connection, type val) \
    { \
        return spd_w_set_ ## param (connection, val, "SELF"); \
    } \
    int \
    spd_set_ ## param ## _all(SPDConnection *connection, type val) \
    { \
        return spd_w_set_ ## param (connection, val, "ALL"); \
    } \
    int \
    spd_set_ ## param ## _uid(SPDConnection *connection, type val, unsigned int uid) \
    { \
        char who[8]; \
        sprintf(who, "%d", uid); \
        return spd_w_set_ ## param (connection, val, who); \
    }

	SPD_SET_COMMAND_INT(voice_rate, RATE, ((val >= OTTS_VOICE_RATE_MIN)
	                                       && (val <= OTTS_VOICE_RATE_MAX)))
	SPD_SET_COMMAND_INT(voice_pitch, PITCH, ((val >= OTTS_VOICE_PITCH_MIN)
	                                         && (val <= OTTS_VOICE_PITCH_MAX)))
	SPD_SET_COMMAND_INT(volume, VOLUME, ((val >= OTTS_VOICE_VOLUME_MIN)
	                                     && (val <= OTTS_VOICE_VOLUME_MAX)))

	SPD_SET_COMMAND_STR(language, LANGUAGE)
	SPD_SET_COMMAND_STR(output_module, OUTPUT_MODULE)
	SPD_SET_COMMAND_STR(synthesis_voice, SYNTHESIS_VOICE)

	SPD_SET_COMMAND_SPECIAL(punctuation, SPDPunctuation)
	SPD_SET_COMMAND_SPECIAL(capital_letters, SPDCapitalLetters)
	SPD_SET_COMMAND_SPECIAL(spelling, SPDSpelling)
	SPD_SET_COMMAND_SPECIAL(voice_type, SPDVoiceType)
#undef SPD_SET_COMMAND_INT
#undef SPD_SET_COMMAND_STR
#undef SPD_SET_COMMAND_SPECIAL
int
spd_set_notification_on(SPDConnection * connection,
			SPDNotification notification)
{
	if (connection->mode == SPD_MODE_THREADED)
		return spd_set_notification(connection, notification, "on");
	else
		return -1;
}

int
spd_set_notification_off(SPDConnection * connection,
			 SPDNotification notification)
{
	if (connection->mode == SPD_MODE_THREADED)
		return spd_set_notification(connection, notification, "off");
	else
		return -1;
}

#define NOTIFICATION_SET(val, ssip_val) \
    if (notification & val){ \
	sprintf(command, "SET SELF NOTIFICATION "ssip_val" %s", state);\
	ret = spd_execute_command_wo_mutex(connection, command);\
	if (ret < 0) RET(-1);\
    }

int
spd_set_notification(SPDConnection * connection, SPDNotification notification,
		     const char *state)
{
	static char command[64];
	int ret;

	if (connection->mode != SPD_MODE_THREADED)
		return -1;

	if (state == NULL) {
		SPD_DBG("Requested state is NULL");
		return -1;
	}
	if (strcmp(state, "on") && strcmp(state, "off")) {
		SPD_DBG("Invalid argument for spd_set_notification: %s", state);
		return -1;
	}

	pthread_mutex_lock(connection->ssip_mutex);

	NOTIFICATION_SET(SPD_INDEX_MARKS, "index_marks");
	NOTIFICATION_SET(SPD_BEGIN, "begin");
	NOTIFICATION_SET(SPD_END, "end");
	NOTIFICATION_SET(SPD_CANCEL, "cancel");
	NOTIFICATION_SET(SPD_PAUSE, "pause");
	NOTIFICATION_SET(SPD_RESUME, "resume");
	NOTIFICATION_SET(SPD_RESUME, "pause");

	pthread_mutex_unlock(connection->ssip_mutex);

	return 0;
}

#undef NOTIFICATION_SET

/* spd_list_modules retrieves information about the available output modules.
   The return value is a null-terminated array of strings containing output module
   names.
*/

char **spd_list_modules(SPDConnection * connection)
{
	char **available_modules;
	available_modules =
	    spd_execute_command_with_list_reply(connection,
						"LIST OUTPUT_MODULES");
	return available_modules;
}

char **spd_list_voices(SPDConnection * connection)
{
	char **voices;
	voices = spd_execute_command_with_list_reply(connection, "LIST VOICES");
	return voices;
}

SPDVoice **spd_list_synthesis_voices(SPDConnection * connection)
{
	char **svoices_str;
	SPDVoice **svoices;
	int i, num_items;
	svoices_str =
	    spd_execute_command_with_list_reply(connection,
						"LIST SYNTHESIS_VOICES");

	if (svoices_str == NULL)
		return NULL;

	for (i = 0;; i++)
		if (svoices_str[i] == NULL)
			break;
	num_items = i;
	svoices = (SPDVoice **) malloc((num_items + 1) * sizeof(SPDVoice *));

	for (i = 0; i < num_items; i++) {
		const char delimiters[] = " ";
		char *running;

		if (svoices_str[i] == NULL)
			break;
		running = strdup(svoices_str[i]);

		svoices[i] = (SPDVoice *) malloc(sizeof(SPDVoice));
		svoices[i]->name = strsep(&running, delimiters);
		svoices[i]->language = strsep(&running, delimiters);
		svoices[i]->variant = strsep(&running, delimiters);
		assert(svoices[i]->name != NULL);
	}

	svoices[num_items] = NULL;

	return svoices;
}

char **spd_execute_command_with_list_reply(SPDConnection * connection,
					   char *command)
{
	char *reply, *line;
	int err;
	int max_items = 50;
	char **result;
	int i, ret;

	result = malloc((max_items + 1) * sizeof(char *));

{'fraction_non_alphanumeric': 0.12733334795674395, 'fraction_numerical': 0.007194718024084757, 'mean_word_length': 3.936654033567948, 'pattern_counts': {'":': 0, '<': 63, '<?xml version=': 0, '>': 137, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 14, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '4083560', 'n_tokens_mistral': 17387, 'n_tokens_neox': 16305, 'n_words': 4098}