X-Git-Url: http://review.tizen.org/git/?a=blobdiff_plain;f=common%2Ftts_config_mgr.c;h=1522950376d9ff5fce2c289e2c3d8d2d6079b4e4;hb=f09e5290b213b4bb7f6f2bdf49188008e363aec6;hp=1304fc059f1aec1a5425faa1395b9480f5ae9818;hpb=382e722c5df1397ac181d7af4a49e3fe33623035;p=platform%2Fcore%2Fuifw%2Ftts.git diff --git a/common/tts_config_mgr.c b/common/tts_config_mgr.c old mode 100755 new mode 100644 index 1304fc0..1522950 --- a/common/tts_config_mgr.c +++ b/common/tts_config_mgr.c @@ -43,6 +43,9 @@ static GSList* g_engine_list = NULL; static GSList* g_config_client_list = NULL; static tts_config_s* g_config_info = NULL; +extern char g_engine_id[128]; +extern char g_setting[128]; +extern char g_language[128]; static Ecore_Fd_Handler* g_config_fd_handler_noti = NULL; static int g_config_fd_noti; @@ -87,7 +90,7 @@ int __tts_config_mgr_check_engine_is_valid(const char* engine_id) return -1; } - if (0 == strcmp(engine_id, engine_info->uuid)) { + if (NULL != engine_info->uuid && 0 == strcmp(engine_id, engine_info->uuid)) { SLOG(LOG_DEBUG, tts_tag(), "Default engine is valid : %s", engine_id); return 0; } @@ -108,11 +111,16 @@ int __tts_config_mgr_check_engine_is_valid(const char* engine_id) return TTS_CONFIG_ERROR_OPERATION_FAILED; } - if (NULL != g_config_info->engine_id) free(g_config_info->engine_id); - if (NULL != g_config_info->setting) free(g_config_info->setting); - - g_config_info->engine_id = strdup(engine_info->uuid); - g_config_info->setting = strdup(engine_info->setting); + if (NULL != engine_info->uuid) { + memset(g_engine_id, '\0', sizeof(g_engine_id)); + g_config_info->engine_id = g_engine_id; + strncpy(g_config_info->engine_id, engine_info->uuid, sizeof(g_engine_id) - 1); + } + if (NULL != engine_info->setting) { + memset(g_setting, '\0', sizeof(g_setting)); + g_config_info->setting = g_setting; + strncpy(g_config_info->setting, engine_info->setting, sizeof(g_setting) - 1); + } SLOG(LOG_DEBUG, tts_tag(), "Default engine is changed : %s", g_config_info->engine_id); @@ -129,14 +137,16 @@ int __tts_config_mgr_check_engine_is_valid(const char* engine_id) voice = iter_voice->data; if (NULL != voice) { - if (NULL != voice->language && NULL != g_config_info->language) { + if (NULL != voice->language) { if (0 == strcmp(voice->language, g_config_info->language)) { if (voice->type == g_config_info->type) { /* language is valid */ is_valid_voice = true; - free(g_config_info->language); - g_config_info->language = strdup(voice->language); + memset(g_language, '\0', sizeof(g_language)); + g_config_info->language = g_language; + strncpy(g_config_info->language, voice->language, sizeof(g_language) - 1); + g_config_info->type = voice->type; SLOG(LOG_DEBUG, tts_tag(), "Default voice is changed : lang(%s) type(%d)", voice->language, voice->type); @@ -151,25 +161,24 @@ int __tts_config_mgr_check_engine_is_valid(const char* engine_id) if (false == is_valid_voice) { /* Select first voice as default */ - if (NULL != g_config_info->language) { - free(g_config_info->language); - - iter_voice = g_slist_nth(engine_info->voices, 0); - if (NULL == iter_voice) { - SLOG(LOG_ERROR, tts_tag(), "Fail to get voice list"); - return TTS_CONFIG_ERROR_OPERATION_FAILED; - } - voice = iter_voice->data; + memset(g_language, '\0', sizeof(g_language)); + g_config_info->language = g_language; - if (NULL == voice || NULL == voice->language) { - SLOG(LOG_ERROR, tts_tag(), "Fail to get voice info from list"); - return TTS_CONFIG_ERROR_OPERATION_FAILED; - } + iter_voice = g_slist_nth(engine_info->voices, 0); + if (NULL == iter_voice) { + SLOG(LOG_ERROR, tts_tag(), "Fail to get voice list"); + return TTS_CONFIG_ERROR_OPERATION_FAILED; + } + voice = iter_voice->data; - g_config_info->language = strdup(voice->language); - g_config_info->type = voice->type; - SLOG(LOG_DEBUG, tts_tag(), "Default voice is changed : lang(%s) type(%d)", voice->language, voice->type); + if (NULL == voice || NULL == voice->language) { + SLOG(LOG_ERROR, tts_tag(), "Fail to get voice info from list"); + return TTS_CONFIG_ERROR_OPERATION_FAILED; } + strncpy(g_config_info->language, voice->language, sizeof(g_language) - 1); + + g_config_info->type = voice->type; + SLOG(LOG_DEBUG, tts_tag(), "Default voice is changed : lang(%s) type(%d)", voice->language, voice->type); } if (0 != tts_parser_set_engine(g_config_info->engine_id, g_config_info->setting, @@ -321,7 +330,7 @@ int __tts_config_mgr_select_lang(const char* engine_id, char** language, int* ty Eina_Bool tts_config_mgr_inotify_event_cb(void* data, Ecore_Fd_Handler *fd_handler) { - SLOG(LOG_DEBUG, tts_tag(), "===== Config changed callback event"); + SLOG(LOG_DEBUG, tts_tag(), "@@@ Config changed callback event"); int length; struct inotify_event event; @@ -330,8 +339,7 @@ Eina_Bool tts_config_mgr_inotify_event_cb(void* data, Ecore_Fd_Handler *fd_handl length = read(g_config_fd_noti, &event, sizeof(struct inotify_event)); if (0 > length) { SLOG(LOG_ERROR, tts_tag(), "[ERROR] Empty Inotify event"); - SLOG(LOG_DEBUG, tts_tag(), "====="); - SLOG(LOG_DEBUG, tts_tag(), " "); + SLOG(LOG_DEBUG, tts_tag(), "@@@"); return ECORE_CALLBACK_DONE; } @@ -354,16 +362,14 @@ Eina_Bool tts_config_mgr_inotify_event_cb(void* data, Ecore_Fd_Handler *fd_handl /* engine changed */ if (NULL != engine || NULL != setting) { if (NULL != engine) { - if (NULL != g_config_info->engine_id) - free(g_config_info->engine_id); - - g_config_info->engine_id = strdup(engine); + memset(g_engine_id, '\0', sizeof(g_engine_id)); + g_config_info->engine_id = g_engine_id; + strncpy(g_config_info->engine_id, engine, sizeof(g_engine_id) - 1); } if (NULL != setting) { - if (NULL != g_config_info->setting) - free(g_config_info->setting); - - g_config_info->setting = strdup(setting); + memset(g_setting, '\0', sizeof(g_setting)); + g_config_info->setting = g_setting; + strncpy(g_config_info->setting, setting, sizeof(g_setting) - 1); } SECURE_SLOG(LOG_DEBUG, tts_tag(), "Engine change(%s)", g_config_info->engine_id); @@ -399,10 +405,9 @@ Eina_Bool tts_config_mgr_inotify_event_cb(void* data, Ecore_Fd_Handler *fd_handl before_type = g_config_info->type; if (NULL != lang) { - if (NULL != g_config_info->language) - free(g_config_info->language); - - g_config_info->language = strdup(lang); + memset(g_language, '\0', sizeof(g_language)); + g_config_info->language = g_language; + strncpy(g_config_info->language, lang, sizeof(g_language) - 1); } if (-1 != voice_type) { g_config_info->type = voice_type; @@ -430,6 +435,7 @@ Eina_Bool tts_config_mgr_inotify_event_cb(void* data, Ecore_Fd_Handler *fd_handl if (NULL != before_lang) { free(before_lang); + before_lang = NULL; } } @@ -477,15 +483,29 @@ Eina_Bool tts_config_mgr_inotify_event_cb(void* data, Ecore_Fd_Handler *fd_handl } } - if (NULL != engine) free(engine); - if (NULL != setting) free(setting); - if (NULL != lang) free(lang); + if (NULL != engine) { + free(engine); + engine = NULL; + } + if (NULL != setting) { + free(setting); + setting = NULL; + } + if (NULL != lang) { + free(lang); + lang = NULL; + } + } else if (IN_DELETE_SELF == event.mask) { + SLOG(LOG_ERROR, tts_tag(), "[ERROR] IN_DELETE_SELF event"); + + tts_parser_unload_config(g_config_info); + tts_parser_reset(); + tts_parser_load_config(&g_config_info); } else { - SLOG(LOG_ERROR, tts_tag(), "[ERROR] Undefined event"); + SLOG(LOG_ERROR, tts_tag(), "[ERROR] Undefined event (0x%x)", event.mask); } - SLOG(LOG_DEBUG, tts_tag(), "====="); - SLOG(LOG_DEBUG, tts_tag(), " "); + SLOG(LOG_DEBUG, tts_tag(), "@@@"); return ECORE_CALLBACK_PASS_ON; } @@ -503,7 +523,7 @@ int __tts_config_mgr_register_config_event() } g_config_fd_noti = fd; - wd = inotify_add_watch(fd, TTS_CONFIG, IN_CLOSE_WRITE); + wd = inotify_add_watch(fd, TTS_CONFIG, IN_CLOSE_WRITE|IN_DELETE_SELF); g_config_wd_noti = wd; g_config_fd_handler_noti = ecore_main_fd_handler_add(fd, ECORE_FD_READ, @@ -547,6 +567,7 @@ int __tts_config_set_auto_language() char temp_lang[6] = {'\0', }; strncpy(temp_lang, value, 5); free(value); + value = NULL; if (true == __tts_config_mgr_check_lang_is_valid(g_config_info->engine_id, temp_lang, g_config_info->type)) { /* tts default voice change */ @@ -566,8 +587,9 @@ int __tts_config_set_auto_language() before_lang = strdup(g_config_info->language); before_type = g_config_info->type; - free(g_config_info->language); - g_config_info->language = strdup(temp_lang); + memset(g_language, '\0', sizeof(g_language)); + g_config_info->language = g_language; + strncpy(g_config_info->language, temp_lang, sizeof(g_language) - 1); SECURE_SLOG(LOG_DEBUG, tts_tag(), "[Config] Default voice : lang(%s) type(%d)", g_config_info->language, g_config_info->type); @@ -594,6 +616,7 @@ int __tts_config_set_auto_language() if (NULL != before_lang) { free(before_lang); + before_lang = NULL; } } else { /* Display language is not valid */ @@ -636,14 +659,15 @@ int __tts_config_set_auto_language() iter = g_slist_next(iter); } - if (NULL != g_config_info->language) { - free(g_config_info->language); - g_config_info->language = strdup(tmp_language); - } + + memset(g_language, '\0', sizeof(g_language)); + g_config_info->language = g_language; + strncpy(g_config_info->language, tmp_language, sizeof(g_language) - 1); g_config_info->type = tmp_type; free(tmp_language); + tmp_language = NULL; } return 0; @@ -746,8 +770,6 @@ void __tts_config_release_engine() int __tts_config_mgr_get_engine_info() { DIR *dp = NULL; - int ret = -1; - struct dirent entry; struct dirent *dirp = NULL; char filepath[512] = {'\0',}; @@ -764,13 +786,12 @@ int __tts_config_mgr_get_engine_info() SLOG(LOG_DEBUG, tts_tag(), "[CONFIG] No default directory : %s", TTS_DEFAULT_ENGINE_INFO); } else { do { - ret = readdir_r(dp, &entry, &dirp); - if (0 != ret) { - SLOG(LOG_ERROR, tts_tag(), "[CONFIG] Fail to read directory"); - break; - } + dirp = readdir(dp); if (NULL != dirp) { + if (!strcmp(".", dirp->d_name) || !strcmp("..", dirp->d_name)) + continue; + filesize = strlen(TTS_DEFAULT_ENGINE_INFO) + strlen(dirp->d_name) + 2; if (filesize >= 512) { SECURE_SLOG(LOG_ERROR, tts_tag(), "[CONFIG ERROR] File path is too long : %s", dirp->d_name); @@ -803,13 +824,12 @@ int __tts_config_mgr_get_engine_info() SLOG(LOG_DEBUG, tts_tag(), "[CONFIG] No downloadable directory : %s", TTS_DOWNLOAD_ENGINE_INFO); } else { do { - ret = readdir_r(dp, &entry, &dirp); - if (0 != ret) { - SLOG(LOG_ERROR, tts_tag(), "[CONFIG] Fail to read directory"); - break; - } + dirp = readdir(dp); if (NULL != dirp) { + if (!strcmp(".", dirp->d_name) || !strcmp("..", dirp->d_name)) + continue; + filesize = strlen(TTS_DOWNLOAD_ENGINE_INFO) + strlen(dirp->d_name) + 2; if (filesize >= 512) { SECURE_SLOG(LOG_ERROR, tts_tag(), "[CONFIG ERROR] File path is too long : %s", dirp->d_name); @@ -844,7 +864,7 @@ int __tts_config_mgr_get_engine_info() static Eina_Bool __tts_config_mgr_engine_config_inotify_event_callback(void* data, Ecore_Fd_Handler *fd_handler) { - SLOG(LOG_DEBUG, tts_tag(), "===== Engine config updated callback event"); + SLOG(LOG_DEBUG, tts_tag(), "@@@ Engine config updated callback event"); tts_engine_inotify_s *ino = (tts_engine_inotify_s *)data; int dir_fd = ino->dir_fd; @@ -856,8 +876,7 @@ static Eina_Bool __tts_config_mgr_engine_config_inotify_event_callback(void* dat length = read(dir_fd, &event, sizeof(struct inotify_event)); if (0 > length) { SLOG(LOG_ERROR, tts_tag(), "[ERROR] Empty Inotify event"); - SLOG(LOG_DEBUG, tts_tag(), "====="); - SLOG(LOG_DEBUG, tts_tag(), " "); + SLOG(LOG_DEBUG, tts_tag(), "@@@"); return ECORE_CALLBACK_DONE; } @@ -883,7 +902,10 @@ static Eina_Bool __tts_config_mgr_engine_config_inotify_event_callback(void* dat } else { SLOG(LOG_DEBUG, tts_tag(), "[DEBUG] Saved default voice : lang(%s), type(%d)", g_config_info->language, g_config_info->type); } - if (NULL != temp_lang) free(temp_lang); + if (NULL != temp_lang) { + free(temp_lang); + temp_lang = NULL; + } } GSList *iter = NULL; @@ -909,8 +931,7 @@ static Eina_Bool __tts_config_mgr_engine_config_inotify_event_callback(void* dat SLOG(LOG_ERROR, tts_tag(), "[ERROR] Undefined event"); } - SLOG(LOG_DEBUG, tts_tag(), "====="); - SLOG(LOG_DEBUG, tts_tag(), " "); + SLOG(LOG_DEBUG, tts_tag(), "@@@"); return ECORE_CALLBACK_PASS_ON; } @@ -985,6 +1006,7 @@ static int __tts_config_mgr_unregister_engine_config_updated_event() close(tmp->dir_fd); free(tmp); + tmp = NULL; } g_ino_list = g_list_remove_link(g_ino_list, iter); @@ -1024,6 +1046,12 @@ int tts_config_mgr_initialize(int uid) return TTS_CONFIG_ERROR_OUT_OF_MEMORY; } temp_client->uid = uid; + temp_client->engine_cb = NULL; + temp_client->voice_cb = NULL; + temp_client->speech_cb = NULL; + temp_client->pitch_cb = NULL; + temp_client->screen_cb = NULL; + temp_client->user_data = NULL; g_config_client_list = g_slist_append(g_config_client_list, temp_client); @@ -1036,6 +1064,12 @@ int tts_config_mgr_initialize(int uid) return TTS_CONFIG_ERROR_OUT_OF_MEMORY; } temp_client->uid = uid; + temp_client->engine_cb = NULL; + temp_client->voice_cb = NULL; + temp_client->speech_cb = NULL; + temp_client->pitch_cb = NULL; + temp_client->screen_cb = NULL; + temp_client->user_data = NULL; g_config_client_list = g_slist_append(g_config_client_list, temp_client); } @@ -1102,6 +1136,7 @@ int tts_config_mgr_initialize(int uid) __tts_config_release_client(uid); __tts_config_release_engine(); tts_parser_unload_config(g_config_info); + g_config_info = NULL; return TTS_CONFIG_ERROR_ENGINE_NOT_FOUND; } @@ -1118,24 +1153,26 @@ int tts_config_mgr_initialize(int uid) __tts_config_release_client(uid); __tts_config_release_engine(); tts_parser_unload_config(g_config_info); + g_config_info = NULL; return TTS_CONFIG_ERROR_OPERATION_FAILED; } if (NULL != tmp_language) { - if (NULL != g_config_info->language) { - free(g_config_info->language); - g_config_info->language = strdup(tmp_language); - } + memset(g_language, '\0', sizeof(g_language)); + g_config_info->language = g_language; + strncpy(g_config_info->language, tmp_language, sizeof(g_language) - 1); g_config_info->type = tmp_type; free(tmp_language); + tmp_language = NULL; if (0 != tts_parser_set_voice(g_config_info->language, g_config_info->type)) { SLOG(LOG_ERROR, tts_tag(), "[ERROR] Fail to save config"); __tts_config_release_client(uid); __tts_config_release_engine(); tts_parser_unload_config(g_config_info); + g_config_info = NULL; return TTS_CONFIG_ERROR_OPERATION_FAILED; } } @@ -1143,7 +1180,7 @@ int tts_config_mgr_initialize(int uid) } /* print daemon config */ - SLOG(LOG_DEBUG, tts_tag(), "== TTS config =="); + SLOG(LOG_DEBUG, tts_tag(), "@@@ TTS config @@@"); SECURE_SLOG(LOG_DEBUG, tts_tag(), " engine : %s", g_config_info->engine_id); SECURE_SLOG(LOG_DEBUG, tts_tag(), " setting : %s", g_config_info->setting); SECURE_SLOG(LOG_DEBUG, tts_tag(), " auto voice : %s", g_config_info->auto_voice ? "on" : "off"); @@ -1151,13 +1188,14 @@ int tts_config_mgr_initialize(int uid) SECURE_SLOG(LOG_DEBUG, tts_tag(), " voice type : %d", g_config_info->type); SECURE_SLOG(LOG_DEBUG, tts_tag(), " speech rate : %d", g_config_info->speech_rate); SECURE_SLOG(LOG_DEBUG, tts_tag(), " pitch : %d", g_config_info->pitch); - SLOG(LOG_DEBUG, tts_tag(), "================="); + SLOG(LOG_DEBUG, tts_tag(), "@@@@@"); if (0 != __tts_config_mgr_register_config_event()) { SLOG(LOG_ERROR, tts_tag(), "[ERROR] Fail to register config event"); __tts_config_release_client(uid); __tts_config_release_engine(); tts_parser_unload_config(g_config_info); + g_config_info = NULL; return TTS_CONFIG_ERROR_OPERATION_FAILED; } @@ -1189,6 +1227,7 @@ int tts_config_mgr_finalize(int uid) __tts_config_release_engine(); tts_parser_unload_config(g_config_info); + g_config_info = NULL; __tts_config_mgr_unregister_engine_config_updated_event(); @@ -1452,10 +1491,8 @@ int tts_config_mgr_set_engine(const char* engine) return TTS_CONFIG_ERROR_INVALID_PARAMETER; /* Check current engine id with new engine id */ - if (NULL != g_config_info->engine_id) { - if (0 == strcmp(g_config_info->engine_id, engine)) - return 0; - } + if (0 == strcmp(g_config_info->engine_id, engine)) + return 0; if (0 >= g_slist_length(g_engine_list)) { SLOG(LOG_ERROR, tts_tag(), "[ERROR] There is no engine!!"); @@ -1492,18 +1529,14 @@ int tts_config_mgr_set_engine(const char* engine) continue; } - if (NULL != g_config_info->engine_id) - free(g_config_info->engine_id); - - g_config_info->engine_id = strdup(engine); - - if (NULL != g_config_info->setting) { - free(g_config_info->setting); - g_config_info->setting = NULL; - } + memset(g_engine_id, '\0', sizeof(g_engine_id)); + g_config_info->engine_id = g_engine_id; + strncpy(g_config_info->engine_id, engine, sizeof(g_engine_id) - 1); if (NULL != engine_info->setting) { - g_config_info->setting = strdup(engine_info->setting); + memset(g_setting, '\0', sizeof(g_setting)); + g_config_info->setting = g_setting; + strncpy(g_config_info->setting, engine_info->setting, sizeof(g_setting) - 1); } /* Engine is valid*/ @@ -1538,17 +1571,17 @@ int tts_config_mgr_set_engine(const char* engine) } if (false == is_valid_voice) { - if (NULL != g_config_info->language) { - free(g_config_info->language); - - iter_voice = g_slist_nth(engine_info->voices, 0); - if (NULL != iter_voice) { - voice = iter_voice->data; - if (NULL != voice) { - if (NULL != voice->language) - g_config_info->language = strdup(voice->language); - g_config_info->type = voice->type; - } + memset(g_language, '\0', sizeof(g_language)); + g_config_info->language = g_language; + + iter_voice = g_slist_nth(engine_info->voices, 0); + if (NULL != iter_voice) { + voice = iter_voice->data; + if (NULL != voice) { + if (NULL != voice->language) + strncpy(g_config_info->language, voice->language, sizeof(g_language) - 1); + + g_config_info->type = voice->type; } } } @@ -1648,7 +1681,7 @@ int tts_config_mgr_get_voice(char** language, int* type) if (NULL == language || NULL == type) return TTS_CONFIG_ERROR_INVALID_PARAMETER; - if (NULL != g_config_info->language) { + if (0 != strlen(g_config_info->language)) { *language = strdup(g_config_info->language); *type = g_config_info->type; } else { @@ -1677,19 +1710,15 @@ int tts_config_mgr_set_voice(const char* language, int type) } /* Check language is valid */ - if (NULL != g_config_info->language) { - if (0 != tts_parser_set_voice(language, type)) { - SLOG(LOG_ERROR, tts_tag(), "Fail to save default voice"); - return TTS_CONFIG_ERROR_OPERATION_FAILED; - } - free(g_config_info->language); - g_config_info->language = strdup(language); - g_config_info->type = type; - - } else { - SLOG(LOG_ERROR, tts_tag(), "language is NULL"); + if (0 != tts_parser_set_voice(language, type)) { + SLOG(LOG_ERROR, tts_tag(), "Fail to save default voice"); return TTS_CONFIG_ERROR_OPERATION_FAILED; } + memset(g_language, '\0', sizeof(g_language)); + g_config_info->language = g_language; + strncpy(g_config_info->language, language, sizeof(g_language) - 1); + + g_config_info->type = type; return 0; } @@ -1908,7 +1937,7 @@ bool tts_config_check_default_voice_is_valid(const char* language, int type) if (NULL == language) return false; - if (NULL == g_config_info->engine_id) { + if (0 == strlen(g_config_info->engine_id)) { SLOG(LOG_ERROR, tts_tag(), "[ERROR] Default engine id is NULL"); return false; } @@ -2014,3 +2043,53 @@ int __tts_config_mgr_print_engine_info() return 0; } + +int tts_config_mgr_get_max_text_size(unsigned int* size) +{ + if (0 >= g_slist_length(g_config_client_list)) { + SLOG(LOG_ERROR, tts_tag(), "Not initialized"); + return TTS_CONFIG_ERROR_INVALID_PARAMETER; + } + + if (NULL == size) { + return TTS_CONFIG_ERROR_INVALID_PARAMETER; + } + + GSList *iter = NULL; + tts_engine_info_s *engine_info = NULL; + + if (0 >= g_slist_length(g_engine_list)) { + SLOG(LOG_ERROR, tts_tag(), "[ERROR] There is no engine!!"); + return TTS_CONFIG_ERROR_ENGINE_NOT_FOUND; + } + + /* Get a first item */ + iter = g_slist_nth(g_engine_list, 0); + + while (NULL != iter) { + engine_info = iter->data; + + if (NULL == engine_info) { + SLOG(LOG_ERROR, tts_tag(), "engine info is NULL"); + return TTS_CONFIG_ERROR_OPERATION_FAILED; + } + + if (0 != strcmp(g_config_info->engine_id, engine_info->uuid)) { + iter = g_slist_next(iter); + continue; + } + + break; + } + + if (NULL == engine_info) { + SLOG(LOG_ERROR, tts_tag(), "engine info is NULL"); + return TTS_CONFIG_ERROR_OPERATION_FAILED; + } + + *size = engine_info->text_size; + SLOG(LOG_DEBUG, tts_tag(), "[DEBUG] Max text size is %d.", *size); + + return 0; +} +