Merge branch 'master' of git.linphone.org:linphone

This commit is contained in:
Simon Morlat 2016-08-25 16:20:01 +02:00
commit dc6422a849
8 changed files with 147 additions and 88 deletions

View file

@ -278,6 +278,4 @@ install(FILES ${LINPHONE_HEADER_FILES}
PERMISSIONS OWNER_READ OWNER_WRITE GROUP_READ WORLD_READ
)
if(ENABLE_DOC)
add_subdirectory(help)
endif()
add_subdirectory(help)

View file

@ -470,8 +470,8 @@ static void _is_account_used_cb(LinphoneXmlRpcRequest *request) {
LinphoneAccountCreator *creator = (LinphoneAccountCreator *)linphone_xml_rpc_request_get_user_data(request);
if (creator->callbacks->is_account_used != NULL) {
LinphoneAccountCreatorStatus status = LinphoneAccountCreatorReqFailed;
const char* resp = linphone_xml_rpc_request_get_string_response(request);
if (linphone_xml_rpc_request_get_status(request) == LinphoneXmlRpcStatusOk) {
const char* resp = linphone_xml_rpc_request_get_string_response(request);
status = (strcmp(resp, "ERROR_ACCOUNT_DOESNT_EXIST") == 0) ? LinphoneAccountCreatorAccountNotExist : (
(strcmp(resp, "ERROR_ALIAS_DOESNT_EXIST") == 0) ? LinphoneAccountCreatorAccountExist :
LinphoneAccountCreatorAccountExistWithAlias);
@ -479,7 +479,7 @@ static void _is_account_used_cb(LinphoneXmlRpcRequest *request) {
set_string(&creator->phone_number, resp, FALSE);
}
}
creator->callbacks->is_account_used(creator, status);
creator->callbacks->is_account_used(creator, status, resp);
}
}
@ -487,7 +487,7 @@ LinphoneAccountCreatorStatus linphone_account_creator_is_account_used(LinphoneAc
LinphoneXmlRpcRequest *request;
if (!creator->username && !creator->phone_number) {
if (creator->callbacks->is_account_used != NULL) {
creator->callbacks->is_account_used(creator, LinphoneAccountCreatorReqFailed);
creator->callbacks->is_account_used(creator, LinphoneAccountCreatorReqFailed, "Missing required parameters");
}
return LinphoneAccountCreatorReqFailed;
}
@ -510,11 +510,11 @@ static void _create_account_cb(LinphoneXmlRpcRequest *request) {
LinphoneAccountCreator *creator = (LinphoneAccountCreator *)linphone_xml_rpc_request_get_user_data(request);
if (creator->callbacks->create_account != NULL) {
LinphoneAccountCreatorStatus status = LinphoneAccountCreatorReqFailed;
const char* resp = linphone_xml_rpc_request_get_string_response(request);
if (linphone_xml_rpc_request_get_status(request) == LinphoneXmlRpcStatusOk) {
const char* resp = linphone_xml_rpc_request_get_string_response(request);
status = (strcmp(resp, "OK") == 0) ? LinphoneAccountCreatorAccountCreated : LinphoneAccountCreatorAccountNotCreated;
}
creator->callbacks->create_account(creator, status);
creator->callbacks->create_account(creator, status, resp);
}
}
@ -552,7 +552,7 @@ LinphoneAccountCreatorStatus linphone_account_creator_create_account(LinphoneAcc
if (!identity || (!(request = _create_account_with_phone(creator))
&& !(request = _create_account_with_email(creator)))) {
if (creator->callbacks->create_account != NULL) {
creator->callbacks->create_account(creator, LinphoneAccountCreatorReqFailed);
creator->callbacks->create_account(creator, LinphoneAccountCreatorReqFailed, "Missing required parameters");
}
if (identity) ms_free(identity);
return LinphoneAccountCreatorReqFailed;
@ -572,8 +572,8 @@ static void _activate_account_cb(LinphoneXmlRpcRequest *request) {
LinphoneAccountCreator *creator = (LinphoneAccountCreator *)linphone_xml_rpc_request_get_user_data(request);
if (creator->callbacks->activate_account != NULL) {
LinphoneAccountCreatorStatus status = LinphoneAccountCreatorReqFailed;
const char* resp = linphone_xml_rpc_request_get_string_response(request);
if (linphone_xml_rpc_request_get_status(request) == LinphoneXmlRpcStatusOk) {
const char* resp = linphone_xml_rpc_request_get_string_response(request);
if (strcmp(resp, "ERROR_ACCOUNT_ALREADY_ACTIVATED") == 0) {
status = LinphoneAccountCreatorAccountAlreadyActivated;
} else if (strstr(resp, "ERROR_") == resp) {
@ -583,7 +583,7 @@ static void _activate_account_cb(LinphoneXmlRpcRequest *request) {
set_string(&creator->ha1, resp, FALSE);
}
}
creator->callbacks->activate_account(creator, status);
creator->callbacks->activate_account(creator, status, resp);
}
}
@ -592,7 +592,7 @@ LinphoneAccountCreatorStatus linphone_account_creator_activate_account(LinphoneA
char *identity = _get_identity(creator);
if (!identity || !creator->activation_code) {
if (creator->callbacks->is_account_activated != NULL) {
creator->callbacks->is_account_activated(creator, LinphoneAccountCreatorReqFailed);
creator->callbacks->is_account_activated(creator, LinphoneAccountCreatorReqFailed, "Missing required parameters");
}
return LinphoneAccountCreatorReqFailed;
}
@ -623,11 +623,11 @@ static void _is_account_activated_cb(LinphoneXmlRpcRequest *request) {
LinphoneAccountCreator *creator = (LinphoneAccountCreator *)linphone_xml_rpc_request_get_user_data(request);
if (creator->callbacks->is_account_activated != NULL) {
LinphoneAccountCreatorStatus status = LinphoneAccountCreatorReqFailed;
const char* resp = linphone_xml_rpc_request_get_string_response(request);
if (linphone_xml_rpc_request_get_status(request) == LinphoneXmlRpcStatusOk) {
const char* resp = linphone_xml_rpc_request_get_string_response(request);
status = (strcmp(resp, "OK") == 0) ? LinphoneAccountCreatorAccountActivated : LinphoneAccountCreatorAccountNotActivated;
}
creator->callbacks->is_account_activated(creator, status);
creator->callbacks->is_account_activated(creator, status, resp);
}
}
@ -636,7 +636,7 @@ LinphoneAccountCreatorStatus linphone_account_creator_is_account_activated(Linph
char *identity = _get_identity(creator);
if (!identity) {
if (creator->callbacks->is_account_activated != NULL) {
creator->callbacks->is_account_activated(creator, LinphoneAccountCreatorReqFailed);
creator->callbacks->is_account_activated(creator, LinphoneAccountCreatorReqFailed, "Missing required parameters");
}
return LinphoneAccountCreatorReqFailed;
}
@ -657,11 +657,11 @@ static void _link_phone_number_with_account_cb(LinphoneXmlRpcRequest *request) {
LinphoneAccountCreator *creator = (LinphoneAccountCreator *)linphone_xml_rpc_request_get_user_data(request);
if (creator->callbacks->link_phone_number_with_account != NULL) {
LinphoneAccountCreatorStatus status = LinphoneAccountCreatorReqFailed;
const char* resp = linphone_xml_rpc_request_get_string_response(request);
if (linphone_xml_rpc_request_get_status(request) == LinphoneXmlRpcStatusOk) {
const char* resp = linphone_xml_rpc_request_get_string_response(request);
status = (strcmp(resp, "OK") == 0) ? LinphoneAccountCreatorOK : LinphoneAccountCreatorReqFailed;
}
creator->callbacks->link_phone_number_with_account(creator, status);
creator->callbacks->link_phone_number_with_account(creator, status, resp);
}
}
@ -669,7 +669,7 @@ LinphoneAccountCreatorStatus linphone_account_creator_link_phone_number_with_acc
LinphoneXmlRpcRequest *request;
if (!creator->phone_number || !creator->username) {
if (creator->callbacks->link_phone_number_with_account != NULL) {
creator->callbacks->link_phone_number_with_account(creator, LinphoneAccountCreatorReqFailed);
creator->callbacks->link_phone_number_with_account(creator, LinphoneAccountCreatorReqFailed, "Missing required parameters");
}
return LinphoneAccountCreatorReqFailed;
}
@ -690,11 +690,11 @@ static void _activate_phone_number_link_cb(LinphoneXmlRpcRequest *request) {
LinphoneAccountCreator *creator = (LinphoneAccountCreator *)linphone_xml_rpc_request_get_user_data(request);
if (creator->callbacks->activate_phone_number_link != NULL) {
LinphoneAccountCreatorStatus status = LinphoneAccountCreatorReqFailed;
const char* resp = linphone_xml_rpc_request_get_string_response(request);
if (linphone_xml_rpc_request_get_status(request) == LinphoneXmlRpcStatusOk) {
const char* resp = linphone_xml_rpc_request_get_string_response(request);
status = (strstr(resp, "ERROR_") == resp) ? LinphoneAccountCreatorReqFailed : LinphoneAccountCreatorOK;
}
creator->callbacks->activate_phone_number_link(creator, status);
creator->callbacks->activate_phone_number_link(creator, status, resp);
}
}
@ -702,7 +702,7 @@ LinphoneAccountCreatorStatus linphone_account_creator_activate_phone_number_link
LinphoneXmlRpcRequest *request;
if (!creator->phone_number || !creator->username || !creator->activation_code || !creator->password || !creator->domain) {
if (creator->callbacks->activate_phone_number_link != NULL) {
creator->callbacks->activate_phone_number_link(creator, LinphoneAccountCreatorReqFailed);
creator->callbacks->activate_phone_number_link(creator, LinphoneAccountCreatorReqFailed, "Missing required parameters");
}
return LinphoneAccountCreatorReqFailed;
}
@ -710,7 +710,7 @@ LinphoneAccountCreatorStatus linphone_account_creator_activate_phone_number_link
LinphoneXmlRpcArgString, creator->phone_number,
LinphoneXmlRpcArgString, creator->username,
LinphoneXmlRpcArgString, creator->activation_code,
LinphoneXmlRpcArgString, ha1_for_passwd(creator->username, creator->password, creator->domain),
LinphoneXmlRpcArgString, ha1_for_passwd(creator->username, creator->domain, creator->password),
LinphoneXmlRpcArgNone);
linphone_xml_rpc_request_set_user_data(request, creator);
linphone_xml_rpc_request_cbs_set_response(linphone_xml_rpc_request_get_callbacks(request), _activate_phone_number_link_cb);
@ -725,11 +725,11 @@ static void _recover_phone_account_cb(LinphoneXmlRpcRequest *request) {
LinphoneAccountCreator *creator = (LinphoneAccountCreator *)linphone_xml_rpc_request_get_user_data(request);
if (creator->callbacks->recover_phone_account != NULL) {
LinphoneAccountCreatorStatus status = LinphoneAccountCreatorReqFailed;
const char* resp = linphone_xml_rpc_request_get_string_response(request);
if (linphone_xml_rpc_request_get_status(request) == LinphoneXmlRpcStatusOk) {
const char* resp = linphone_xml_rpc_request_get_string_response(request);
status = (strstr(resp, "ERROR_") == resp) ? LinphoneAccountCreatorReqFailed : LinphoneAccountCreatorOK;
}
creator->callbacks->recover_phone_account(creator, status);
creator->callbacks->recover_phone_account(creator, status, resp);
}
}
@ -737,7 +737,7 @@ LinphoneAccountCreatorStatus linphone_account_creator_recover_phone_account(Linp
LinphoneXmlRpcRequest *request;
if (!creator->phone_number) {
if (creator->callbacks->recover_phone_account != NULL) {
creator->callbacks->recover_phone_account(creator, LinphoneAccountCreatorReqFailed);
creator->callbacks->recover_phone_account(creator, LinphoneAccountCreatorReqFailed, "Missing required parameters");
}
return LinphoneAccountCreatorReqFailed;
}

View file

@ -80,7 +80,7 @@ typedef struct _LinphoneAccountCreatorCbs LinphoneAccountCreatorCbs;
* @param[in] creator LinphoneAccountCreator object
* @param[in] status The status of the LinphoneAccountCreator test existence operation that has just finished
**/
typedef void (*LinphoneAccountCreatorCbsStatusCb)(LinphoneAccountCreator *creator, LinphoneAccountCreatorStatus status);
typedef void (*LinphoneAccountCreatorCbsStatusCb)(LinphoneAccountCreator *creator, LinphoneAccountCreatorStatus status, const char* resp);
/**
* Create a LinphoneAccountCreator.

View file

@ -618,14 +618,29 @@ LinphoneOnlineStatus linphone_friend_get_status(const LinphoneFriend *lf){
const LinphonePresenceModel * linphone_friend_get_presence_model(const LinphoneFriend *lf) {
const LinphonePresenceModel *presence = NULL;
LinphoneAddress *addr = linphone_friend_get_address(lf);
if (addr) {
LinphoneFriend* fuckconst = (LinphoneFriend*)lf;
bctbx_list_t* addrs = linphone_friend_get_addresses(fuckconst);
bctbx_list_t* phones = NULL;
while (addrs) {
LinphoneAddress *addr = addrs->data;
char *uri = linphone_address_as_string_uri_only(addr);
presence = linphone_friend_get_presence_model_for_uri_or_tel(lf, uri);
presence = linphone_friend_get_presence_model_for_uri_or_tel(fuckconst, uri);
ms_free(uri);
linphone_address_unref(addr);
if (presence) return presence;
addrs = addrs->next;
}
return presence;
phones = linphone_friend_get_phone_numbers(fuckconst);
while (phones) {
presence = linphone_friend_get_presence_model_for_uri_or_tel(fuckconst, phones->data);
if (presence) return presence;
phones = phones->next;
}
return NULL;
}
const LinphonePresenceModel * linphone_friend_get_presence_model_for_uri_or_tel(const LinphoneFriend *lf, const char *uri_or_tel) {
@ -1211,7 +1226,7 @@ static bool_t linphone_update_table(sqlite3* db) {
}
}
sqlite3_finalize(stmt_version);
if (database_user_version == 0) {
int ret = sqlite3_exec(db,
"BEGIN TRANSACTION;\n"
@ -1252,7 +1267,7 @@ void linphone_core_friends_storage_init(LinphoneCore *lc) {
sqlite3_close(lc->friends_db);
_linphone_sqlite3_open(lc->friends_db_file, &db);
}
lc->friends_db = db;
friends_lists = linphone_core_fetch_friends_lists_from_db(lc);
@ -1734,4 +1749,4 @@ const char * linphone_friend_sip_uri_to_phone_number(LinphoneFriend *lf, const c
void linphone_friend_clear_presence_models(LinphoneFriend *lf) {
lf->presence_models = bctbx_list_free_with_data(lf->presence_models, (bctbx_list_free_func)free_friend_presence);
}
}

View file

@ -102,9 +102,45 @@ static int add_uri_entry(xmlTextWriterPtr writer, int err, const char *uri) {
return err;
}
static bctbx_list_t * uri_list(const LinphoneFriendList *list) {
bctbx_list_t * uri_list = NULL;
bctbx_list_t * elem = NULL;
for (elem = list->friends; elem != NULL; elem = bctbx_list_next(elem)) {
LinphoneFriend *lf = (LinphoneFriend *)bctbx_list_get_data(elem);
bctbx_list_t *iterator;
bctbx_list_t *addresses = linphone_friend_get_addresses(lf);
bctbx_list_t *numbers = linphone_friend_get_phone_numbers(lf);
iterator = addresses;
while (iterator) {
LinphoneAddress *addr = (LinphoneAddress *)bctbx_list_get_data(iterator);
if (addr) {
char *uri = linphone_address_as_string_uri_only(addr);
if (uri) {
if (bctbx_list_find_custom(uri_list, (bctbx_compare_func)strcmp, uri) == NULL) {
uri_list = bctbx_list_insert_sorted(uri_list, uri, (bctbx_compare_func)strcasecmp);
}
}
}
iterator = bctbx_list_next(iterator);
}
iterator = numbers;
while (iterator) {
const char *number = (const char *)bctbx_list_get_data(iterator);
const char *uri = linphone_friend_phone_number_to_sip_uri(lf, number);
if (uri) {
if (bctbx_list_find_custom(uri_list, (bctbx_compare_func)strcmp, uri) == NULL) {
uri_list = bctbx_list_insert_sorted(uri_list, ms_strdup(uri), (bctbx_compare_func)strcasecmp);
}
}
iterator = bctbx_list_next(iterator);
}
if (addresses) bctbx_list_free_with_data(addresses, (bctbx_list_free_func)linphone_address_unref);
}
return uri_list;
}
static char * create_resource_list_xml(const LinphoneFriendList *list) {
char *xml_content = NULL;
bctbx_list_t *elem;
xmlBufferPtr buf;
xmlTextWriterPtr writer;
int err;
@ -135,31 +171,16 @@ static char * create_resource_list_xml(const LinphoneFriendList *list) {
if (err>= 0) {
err = xmlTextWriterStartElement(writer, (const xmlChar *)"list");
}
for (elem = list->friends; elem != NULL; elem = bctbx_list_next(elem)) {
LinphoneFriend *lf = (LinphoneFriend *)bctbx_list_get_data(elem);
bctbx_list_t *iterator;
bctbx_list_t *addresses = linphone_friend_get_addresses(lf);
bctbx_list_t *numbers = linphone_friend_get_phone_numbers(lf);
iterator = addresses;
while (iterator) {
LinphoneAddress *addr = (LinphoneAddress *)bctbx_list_get_data(iterator);
if (addr) {
char *uri = linphone_address_as_string_uri_only(addr);
if (uri) {
err = add_uri_entry(writer, err, uri);
ms_free(uri);
}
}
iterator = bctbx_list_next(iterator);
{
bctbx_list_t* entries = uri_list(list);
bctbx_list_t* it = entries;
while (it) {
err = add_uri_entry(writer, err, it->data);
ms_free(it->data);
it = it->next;
}
iterator = numbers;
while (iterator) {
const char *number = (const char *)bctbx_list_get_data(iterator);
const char *uri = linphone_friend_phone_number_to_sip_uri(lf, number);
if (uri) err = add_uri_entry(writer, err, uri);
iterator = bctbx_list_next(iterator);
}
if (addresses) bctbx_list_free_with_data(addresses, (bctbx_list_free_func)linphone_address_unref);
bctbx_free(entries);
}
if (err >= 0) {
/* Close the "list" element. */
@ -858,7 +879,7 @@ int linphone_friend_list_import_friends_from_vcard4_file(LinphoneFriendList *lis
ms_error("Can't import into a NULL list");
return -1;
}
vcards = linphone_vcard_context_get_vcard_list_from_file(list->lc->vcard_context, vcard_file);
vcards_iterator = vcards;
if (!vcards) {
@ -898,7 +919,7 @@ int linphone_friend_list_import_friends_from_vcard4_buffer(LinphoneFriendList *l
ms_error("Can't import into a NULL list");
return -1;
}
vcards = linphone_vcard_context_get_vcard_list_from_buffer(list->lc->vcard_context, vcard_buffer);
vcards_iterator = vcards;
if (!vcards) {

View file

@ -20,26 +20,51 @@
#
############################################################################
find_package(Doxygen)
if(DOXYGEN_FOUND)
if(DOXYGEN_DOT_FOUND)
set(top_srcdir "${CMAKE_CURRENT_LIST_DIR}/../../")
configure_file(${CMAKE_CURRENT_SOURCE_DIR}/Doxyfile.in ${CMAKE_CURRENT_BINARY_DIR}/Doxyfile)
file(GLOB DOC_INPUT_FILES
[^.]*.c
[^.]*.dox
../[^.]*.h
../[^.]*.c
)
add_custom_command(OUTPUT "${CMAKE_CURRENT_BINARY_DIR}/doc/html/index.html"
COMMAND ${DOXYGEN_EXECUTABLE} ${CMAKE_CURRENT_BINARY_DIR}/Doxyfile
DEPENDS ${CMAKE_CURRENT_BINARY_DIR}/Doxyfile ${DOC_INPUT_FILES}
)
add_custom_target(linphone-doc ALL DEPENDS "${CMAKE_CURRENT_BINARY_DIR}/doc/html/index.html")
install(DIRECTORY "${CMAKE_CURRENT_BINARY_DIR}/doc/html" "${CMAKE_CURRENT_BINARY_DIR}/doc/xml"
DESTINATION "${CMAKE_INSTALL_DATADIR}/doc/linphone-${LINPHONE_VERSION}")
else()
message(WARNING "The dot program is needed to generate the linphone documentation. You can get it from http://www.graphviz.org/.")
if (ENABLE_DOC)
find_package(Doxygen)
if(DOXYGEN_FOUND)
if(DOXYGEN_DOT_FOUND)
set(top_srcdir "${CMAKE_CURRENT_LIST_DIR}/../../")
configure_file(${CMAKE_CURRENT_SOURCE_DIR}/Doxyfile.in ${CMAKE_CURRENT_BINARY_DIR}/Doxyfile)
file(GLOB DOC_INPUT_FILES
[^.]*.c
[^.]*.dox
../[^.]*.h
../[^.]*.c
)
add_custom_command(OUTPUT "${CMAKE_CURRENT_BINARY_DIR}/doc/html/index.html"
COMMAND ${DOXYGEN_EXECUTABLE} ${CMAKE_CURRENT_BINARY_DIR}/Doxyfile
DEPENDS ${CMAKE_CURRENT_BINARY_DIR}/Doxyfile ${DOC_INPUT_FILES}
)
add_custom_target(linphone-doc ALL DEPENDS "${CMAKE_CURRENT_BINARY_DIR}/doc/html/index.html")
install(DIRECTORY "${CMAKE_CURRENT_BINARY_DIR}/doc/html" "${CMAKE_CURRENT_BINARY_DIR}/doc/xml"
DESTINATION "${CMAKE_INSTALL_DATADIR}/doc/linphone-${LINPHONE_VERSION}")
else()
message(WARNING "The dot program is needed to generate the linphone documentation. You can get it from http://www.graphviz.org/.")
endif()
endif()
endif()
if (ENABLE_TOOLS)
set(USE_BUNDLE )
if (IOS)
set(USE_BUNDLE MACOSX_BUNDLE)
endif()
add_definitions(-DIN_LINPHONE)
file(GLOB EXECUTABLES_SOURCE RELATIVE ${CMAKE_CURRENT_SOURCE_DIR} "*.c")
foreach(EXECUTABLE ${EXECUTABLES_SOURCE})
string(REPLACE ".c" "" EXECUTABLE_NAME ${EXECUTABLE})
apply_compile_flags(${EXECUTABLE} "CPP" "C")
add_executable(${EXECUTABLE_NAME} ${USE_BUNDLE} ${EXECUTABLE})
target_link_libraries(${EXECUTABLE_NAME} ${LINPHONE_LIBS_FOR_TOOLS} ${MEDIASTREAMER2_LIBRARIES})
set_target_properties(${EXECUTABLE_NAME} PROPERTIES LINK_FLAGS "${LINPHONE_LDFLAGS}")
if (NOT IOS)
install(TARGETS ${EXECUTABLE_NAME}
RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR}
LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR}
ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR}
PERMISSIONS OWNER_READ OWNER_WRITE OWNER_EXECUTE GROUP_READ GROUP_EXECUTE WORLD_READ WORLD_EXECUTE
)
endif()
endforeach()
endif()

View file

@ -94,17 +94,17 @@ void linphone_android_log_handler(int prio, char *str) {
}
JNIEXPORT void JNICALL Java_org_linphone_core_LinphoneCoreFactoryImpl__1setLogHandler(JNIEnv *env, jobject jfactory, jobject jhandler) {
handler_class = (jclass) env->GetObjectClass(jhandler);
loghandler_id = env->GetMethodID(handler_class, "log", "(Ljava/lang/String;ILjava/lang/String;Ljava/lang/String;Ljava/lang/Throwable;)V");
if (loghandler_id == NULL) {
ms_fatal("log method not found");
}
if (handler_obj) {
env->DeleteGlobalRef(handler_obj);
handler_obj = NULL;
}
if (jhandler) {
handler_class = (jclass) env->GetObjectClass(jhandler);
loghandler_id = env->GetMethodID(handler_class, "log", "(Ljava/lang/String;ILjava/lang/String;Ljava/lang/String;Ljava/lang/Throwable;)V");
if (loghandler_id == NULL) {
ms_fatal("log method not found");
}
handler_obj = env->NewGlobalRef(jhandler);
}
}

@ -1 +1 @@
Subproject commit 110e3cf018187903b5638199831a43f6e9d7a021
Subproject commit ead1858685c9ff21426a3ec53f5286461f62ade2