Commit 09025ed3 authored by Ghislain MARY's avatar Ghislain MARY

Add chat room callbacks to handle subscription to registration state of...

Add chat room callbacks to handle subscription to registration state of participants + add new group chat tester.
parent 12660bea
......@@ -291,8 +291,10 @@ void _linphone_chat_room_notify_undecryptable_message_received(LinphoneChatRoom
void _linphone_chat_room_notify_chat_message_received(LinphoneChatRoom *cr, const LinphoneEventLog *event_log);
void _linphone_chat_room_notify_chat_message_sent(LinphoneChatRoom *cr, const LinphoneEventLog *event_log);
void _linphone_chat_room_notify_conference_address_generation(LinphoneChatRoom *cr);
void _linphone_chat_room_notify_participant_device_fetched(LinphoneChatRoom *cr, const LinphoneAddress *participantAddr);
void _linphone_chat_room_notify_participant_device_fetch_requested(LinphoneChatRoom *cr, const LinphoneAddress *participantAddr);
void _linphone_chat_room_notify_participants_capabilities_checked(LinphoneChatRoom *cr, const LinphoneAddress *deviceAddr, const bctbx_list_t *participantsAddr);
void _linphone_chat_room_notify_participant_registration_subscription_requested(LinphoneChatRoom *cr, const LinphoneAddress *participantAddr);
void _linphone_chat_room_notify_participant_registration_unsubscription_requested(LinphoneChatRoom *cr, const LinphoneAddress *participantAddr);
void _linphone_chat_room_notify_chat_message_should_be_stored(LinphoneChatRoom *cr, LinphoneChatMessage *msg);
void _linphone_chat_room_clear_callbacks (LinphoneChatRoom *cr);
......
......@@ -239,7 +239,7 @@ typedef void (*LinphoneChatRoomCbsConferenceAddressGenerationCb) (LinphoneChatRo
* @param[in] cr #LinphoneChatRoom object
* @param[in] participantAddr #LinphoneAddress object
*/
typedef void (*LinphoneChatRoomCbsParticipantDeviceFetchedCb) (LinphoneChatRoom *cr, const LinphoneAddress *participantAddr);
typedef void (*LinphoneChatRoomCbsParticipantDeviceFetchRequestedCb) (LinphoneChatRoom *cr, const LinphoneAddress *participantAddr);
/**
* Callback used when a group chat room server is checking participants capabilities.
......@@ -249,6 +249,20 @@ typedef void (*LinphoneChatRoomCbsParticipantDeviceFetchedCb) (LinphoneChatRoom
*/
typedef void (*LinphoneChatRoomCbsParticipantsCapabilitiesCheckedCb) (LinphoneChatRoom *cr, const LinphoneAddress *deviceAddr, const bctbx_list_t *participantsAddr);
/**
* Callback used when a group chat room server is subscribing to registration state of a participant.
* @param[in] cr #LinphoneChatRoom object
* @param[in] participantAddr #LinphoneAddress object
*/
typedef void (*LinphoneChatRoomCbsParticipantRegistrationSubscriptionRequestedCb) (LinphoneChatRoom *cr, const LinphoneAddress *participantAddr);
/**
* Callback used when a group chat room server is unsubscribing to registration state of a participant.
* @param[in] cr #LinphoneChatRoom object
* @param[in] participantAddr #LinphoneAddress object
*/
typedef void (*LinphoneChatRoomCbsParticipantRegistrationUnsubscriptionRequestedCb) (LinphoneChatRoom *cr, const LinphoneAddress *participantAddr);
/**
* Callback used to tell the core whether or not to store the incoming message in db or not using linphone_chat_message_set_to_be_stored().
* @param[in] cr #LinphoneChatRoom object
......
......@@ -244,18 +244,18 @@ LINPHONE_PUBLIC LinphoneChatRoomCbsConferenceAddressGenerationCb linphone_chat_r
LINPHONE_PUBLIC void linphone_chat_room_cbs_set_conference_address_generation (LinphoneChatRoomCbs *cbs, LinphoneChatRoomCbsConferenceAddressGenerationCb cb);
/**
* Get the participant device getting callback.
* Get the participant device fetching callback.
* @param[in] cbs LinphoneChatRoomCbs object
* @return The participant device getting callback
* @return The participant device fetching callback
*/
LINPHONE_PUBLIC LinphoneChatRoomCbsParticipantDeviceFetchedCb linphone_chat_room_cbs_get_participant_device_fetched (const LinphoneChatRoomCbs *cbs);
LINPHONE_PUBLIC LinphoneChatRoomCbsParticipantDeviceFetchRequestedCb linphone_chat_room_cbs_get_participant_device_fetch_requested (const LinphoneChatRoomCbs *cbs);
/**
* Set the participant device getting callback.
* Set the participant device fetching callback.
* @param[in] cbs LinphoneChatRoomCbs object
* @param[in] cb The participant device getting callback to be used
* @param[in] cb The participant device fetching callback to be used
*/
LINPHONE_PUBLIC void linphone_chat_room_cbs_set_participant_device_fetched (LinphoneChatRoomCbs *cbs, LinphoneChatRoomCbsParticipantDeviceFetchedCb cb);
LINPHONE_PUBLIC void linphone_chat_room_cbs_set_participant_device_fetch_requested (LinphoneChatRoomCbs *cbs, LinphoneChatRoomCbsParticipantDeviceFetchRequestedCb cb);
/**
* Get the participants capabilities callback.
......@@ -271,6 +271,34 @@ LINPHONE_PUBLIC LinphoneChatRoomCbsParticipantsCapabilitiesCheckedCb linphone_ch
*/
LINPHONE_PUBLIC void linphone_chat_room_cbs_set_participants_capabilities_checked (LinphoneChatRoomCbs *cbs, LinphoneChatRoomCbsParticipantsCapabilitiesCheckedCb cb);
/**
* Get the participant registration subscription callback.
* @param[in] cbs LinphoneChatRoomCbs object
* @return The participant registration subscription callback
*/
LINPHONE_PUBLIC LinphoneChatRoomCbsParticipantRegistrationSubscriptionRequestedCb linphone_chat_room_cbs_get_participant_registration_subscription_requested (const LinphoneChatRoomCbs *cbs);
/**
* Set the participant registration subscription callback.
* @param[in] cbs LinphoneChatRoomCbs object
* @param[in] cb The participant registration subscription callback to be used
*/
LINPHONE_PUBLIC void linphone_chat_room_cbs_set_participant_registration_subscription_requested (LinphoneChatRoomCbs *cbs, LinphoneChatRoomCbsParticipantRegistrationSubscriptionRequestedCb cb);
/**
* Get the participant registration unsubscription callback.
* @param[in] cbs LinphoneChatRoomCbs object
* @return The participant registration unsubscription callback
*/
LINPHONE_PUBLIC LinphoneChatRoomCbsParticipantRegistrationUnsubscriptionRequestedCb linphone_chat_room_cbs_get_participant_registration_unsubscription_requested (const LinphoneChatRoomCbs *cbs);
/**
* Set the participant registration unsubscription callback.
* @param[in] cbs LinphoneChatRoomCbs object
* @param[in] cb The participant registration unsubscription callback to be used
*/
LINPHONE_PUBLIC void linphone_chat_room_cbs_set_participant_registration_unsubscription_requested (LinphoneChatRoomCbs *cbs, LinphoneChatRoomCbsParticipantRegistrationUnsubscriptionRequestedCb cb);
/**
* Get the message should be stored callback.
* @param[in] cbs LinphoneChatRoomCbs object
......
......@@ -454,13 +454,22 @@ LINPHONE_PUBLIC void linphone_chat_room_set_conference_address (LinphoneChatRoom
/**
* Set the participant device. This function needs to be called from the
* LinphoneChatRoomCbsParticipantDeviceFetchedCb callback and only there.
* LinphoneChatRoomCbsParticipantDeviceFetchRequestedCb callback and only there.
* @param[in] cr A LinphoneChatRoom object
* @param[in] partAddr The participant address
* @param[in] partDevices \bctbx_list{LinphoneAddress} list of the participant devices to be used by the group chat room
*/
LINPHONE_PUBLIC void linphone_chat_room_set_participant_devices (LinphoneChatRoom *cr, const LinphoneAddress *partAddr, const bctbx_list_t *partDevices);
/**
* Add a participant device.
* This is to used if a new device registers itself after the chat room creation.
* @param[in] cr A #LinphoneChatRoom object
* @param[in] participantAddress The address of the participant for which a new device is to be added
* @param[in] deviceAddress The address of the new device to be added
*/
LINPHONE_PUBLIC void linphone_chat_room_add_participant_device (LinphoneChatRoom *cr, const LinphoneAddress *participantAddress, const LinphoneAddress *deviceAddress);
/**
* Set the participant device. This function needs to be called from the
* LinphoneChatRoomCbsParticipantsCapabilitiesCheckedCb callback and only there.
......
......@@ -39,8 +39,10 @@ struct _LinphoneChatRoomCbs {
LinphoneChatRoomCbsChatMessageReceivedCb chatMessageReceivedCb;
LinphoneChatRoomCbsChatMessageSentCb chatMessageSentCb;
LinphoneChatRoomCbsConferenceAddressGenerationCb conferenceAddressGenerationCb;
LinphoneChatRoomCbsParticipantDeviceFetchedCb participantDeviceFetchedCb;
LinphoneChatRoomCbsParticipantDeviceFetchRequestedCb participantDeviceFetchRequestedCb;
LinphoneChatRoomCbsParticipantsCapabilitiesCheckedCb participantsCapabilitiesChecked;
LinphoneChatRoomCbsParticipantRegistrationSubscriptionRequestedCb participantRegistrationSubscriptionRequestedCb;
LinphoneChatRoomCbsParticipantRegistrationUnsubscriptionRequestedCb participantRegistrationUnsubscriptionRequestedCb;
LinphoneChatRoomCbsShouldChatMessageBeStoredCb shouldMessageBeStoredCb;
};
......@@ -182,12 +184,12 @@ void linphone_chat_room_cbs_set_conference_address_generation (LinphoneChatRoomC
cbs->conferenceAddressGenerationCb = cb;
}
LinphoneChatRoomCbsParticipantDeviceFetchedCb linphone_chat_room_cbs_get_participant_device_fetched (const LinphoneChatRoomCbs *cbs) {
return cbs->participantDeviceFetchedCb;
LinphoneChatRoomCbsParticipantDeviceFetchRequestedCb linphone_chat_room_cbs_get_participant_device_fetch_requested (const LinphoneChatRoomCbs *cbs) {
return cbs->participantDeviceFetchRequestedCb;
}
void linphone_chat_room_cbs_set_participant_device_fetched (LinphoneChatRoomCbs *cbs, LinphoneChatRoomCbsParticipantDeviceFetchedCb cb) {
cbs->participantDeviceFetchedCb = cb;
void linphone_chat_room_cbs_set_participant_device_fetch_requested (LinphoneChatRoomCbs *cbs, LinphoneChatRoomCbsParticipantDeviceFetchRequestedCb cb) {
cbs->participantDeviceFetchRequestedCb = cb;
}
LinphoneChatRoomCbsParticipantsCapabilitiesCheckedCb linphone_chat_room_cbs_get_participants_capabilities_checked (const LinphoneChatRoomCbs *cbs) {
......@@ -198,6 +200,22 @@ void linphone_chat_room_cbs_set_participants_capabilities_checked (LinphoneChatR
cbs->participantsCapabilitiesChecked = cb;
}
LinphoneChatRoomCbsParticipantRegistrationSubscriptionRequestedCb linphone_chat_room_cbs_get_participant_registration_subscription_requested (const LinphoneChatRoomCbs *cbs) {
return cbs->participantRegistrationSubscriptionRequestedCb;
}
void linphone_chat_room_cbs_set_participant_registration_subscription_requested (LinphoneChatRoomCbs *cbs, LinphoneChatRoomCbsParticipantRegistrationSubscriptionRequestedCb cb) {
cbs->participantRegistrationSubscriptionRequestedCb = cb;
}
LinphoneChatRoomCbsParticipantRegistrationUnsubscriptionRequestedCb linphone_chat_room_cbs_get_participant_registration_unsubscription_requested (const LinphoneChatRoomCbs *cbs) {
return cbs->participantRegistrationUnsubscriptionRequestedCb;
}
void linphone_chat_room_cbs_set_participant_registration_unsubscription_requested (LinphoneChatRoomCbs *cbs, LinphoneChatRoomCbsParticipantRegistrationUnsubscriptionRequestedCb cb) {
cbs->participantRegistrationUnsubscriptionRequestedCb = cb;
}
LinphoneChatRoomCbsShouldChatMessageBeStoredCb linphone_chat_room_cbs_get_chat_message_should_be_stored( LinphoneChatRoomCbs *cbs) {
return cbs->shouldMessageBeStoredCb;
}
......
......@@ -382,6 +382,16 @@ void linphone_chat_room_set_participant_devices (LinphoneChatRoom *cr, const Lin
bctbx_free(addrStr);
}
void linphone_chat_room_add_participant_device (LinphoneChatRoom *cr, const LinphoneAddress *participantAddress, const LinphoneAddress *deviceAddress) {
char *participantAddressStr = linphone_address_as_string(participantAddress);
char *deviceAddressStr = linphone_address_as_string(deviceAddress);
LinphonePrivate::ServerGroupChatRoomPrivate *sgcr = dynamic_cast<LinphonePrivate::ServerGroupChatRoomPrivate *>(L_GET_PRIVATE_FROM_C_OBJECT(cr));
if (sgcr)
sgcr->addParticipantDevice(LinphonePrivate::IdentityAddress(participantAddressStr), LinphonePrivate::IdentityAddress(deviceAddressStr));
bctbx_free(participantAddressStr);
bctbx_free(deviceAddressStr);
}
void linphone_chat_room_add_compatible_participants (LinphoneChatRoom *cr, const LinphoneAddress *deviceAddr, const bctbx_list_t *participantsCompatible) {
list<LinphonePrivate::Address> lPartsComp = L_GET_RESOLVED_CPP_LIST_FROM_C_LIST(participantsCompatible, Address);
LinphonePrivate::ServerGroupChatRoomPrivate *sgcr = dynamic_cast<LinphonePrivate::ServerGroupChatRoomPrivate *>(L_GET_PRIVATE_FROM_C_OBJECT(cr));
......@@ -486,14 +496,22 @@ void _linphone_chat_room_notify_conference_address_generation(LinphoneChatRoom *
NOTIFY_IF_EXIST(ConferenceAddressGeneration, conference_address_generation, cr)
}
void _linphone_chat_room_notify_participant_device_fetched(LinphoneChatRoom *cr, const LinphoneAddress *participantAddr) {
NOTIFY_IF_EXIST(ParticipantDeviceFetched, participant_device_fetched, cr, participantAddr)
void _linphone_chat_room_notify_participant_device_fetch_requested(LinphoneChatRoom *cr, const LinphoneAddress *participantAddr) {
NOTIFY_IF_EXIST(ParticipantDeviceFetchRequested, participant_device_fetch_requested, cr, participantAddr)
}
void _linphone_chat_room_notify_participants_capabilities_checked(LinphoneChatRoom *cr, const LinphoneAddress *deviceAddr, const bctbx_list_t *participantsAddr) {
NOTIFY_IF_EXIST(ParticipantsCapabilitiesChecked, participants_capabilities_checked, cr, deviceAddr, participantsAddr)
}
void _linphone_chat_room_notify_participant_registration_subscription_requested(LinphoneChatRoom *cr, const LinphoneAddress *participantAddr) {
NOTIFY_IF_EXIST(ParticipantRegistrationSubscriptionRequested, participant_registration_subscription_requested, cr, participantAddr)
}
void _linphone_chat_room_notify_participant_registration_unsubscription_requested(LinphoneChatRoom *cr, const LinphoneAddress *participantAddr) {
NOTIFY_IF_EXIST(ParticipantRegistrationUnsubscriptionRequested, participant_registration_unsubscription_requested, cr, participantAddr)
}
void _linphone_chat_room_notify_chat_message_should_be_stored(LinphoneChatRoom *cr, LinphoneChatMessage *msg) {
NOTIFY_IF_EXIST(ShouldChatMessageBeStored, chat_message_should_be_stored, cr, msg)
}
......
......@@ -60,6 +60,7 @@ public:
void setConferenceAddress (const IdentityAddress &conferenceAddress);
void setParticipantDevices (const IdentityAddress &addr, const std::list<IdentityAddress> &devices);
void addParticipantDevice (const IdentityAddress &participantAddress, const IdentityAddress &deviceAddress);
void addCompatibleParticipants (const IdentityAddress &deviceAddr, const std::list<IdentityAddress> &compatibleParticipants);
void checkCompatibleParticipants (const IdentityAddress &deviceAddr, const std::list<IdentityAddress> &addressesToCheck);
......@@ -67,17 +68,29 @@ public:
private:
struct Message {
Message (const std::string &from, const std::string &contentType, const std::string &text) : fromAddr(from) {
Message (const std::string &from, const std::string &contentType, const std::string &text, const SalCustomHeader *salCustomHeaders)
: fromAddr(from)
{
content.setContentType(contentType);
if (!text.empty())
content.setBodyFromUtf8(text);
if (salCustomHeaders)
customHeaders = sal_custom_header_clone(salCustomHeaders);
}
~Message () {
if (customHeaders)
sal_custom_header_free(customHeaders);
}
IdentityAddress fromAddr;
Content content;
std::chrono::system_clock::time_point timestamp = std::chrono::system_clock::now();
SalCustomHeader *customHeaders = nullptr;
};
static void copyMessageHeaders (const std::shared_ptr<Message> &fromMessage, const std::shared_ptr<ChatMessage> &toMessage);
void designateAdmin ();
void dispatchMessage (const std::shared_ptr<Message> &message, const std::string &uri);
void finalizeCreation ();
......
......@@ -80,6 +80,8 @@ void ServerGroupChatRoomPrivate::setConferenceAddress (const IdentityAddress &)
void ServerGroupChatRoomPrivate::setParticipantDevices (const IdentityAddress &addr, const list<IdentityAddress> &devices) {}
void ServerGroupChatRoomPrivate::addParticipantDevice (const IdentityAddress &participantAddress, const IdentityAddress &deviceAddress) {}
void ServerGroupChatRoomPrivate::addCompatibleParticipants (const IdentityAddress &deviceAddr, const list<IdentityAddress> &participantCompatible) {}
// -----------------------------------------------------------------------------
......
......@@ -2874,7 +2874,7 @@ static void group_chat_room_unique_one_to_one_chat_room_recreated_from_message_2
linphone_core_manager_destroy(pauline2);
}
#if 0
static void group_chat_room_join_one_to_one_chat_room_with_a_new_device (void) {
LinphoneCoreManager *marie1 = linphone_core_manager_create("marie_rc");
LinphoneCoreManager *pauline = linphone_core_manager_create("pauline_rc");
......@@ -2963,6 +2963,7 @@ static void group_chat_room_join_one_to_one_chat_room_with_a_new_device (void) {
linphone_core_manager_destroy(marie1);
linphone_core_manager_destroy(pauline);
}
#endif
static void group_chat_room_new_unique_one_to_one_chat_room_after_both_participants_left (void) {
LinphoneCoreManager *marie = linphone_core_manager_create("marie_rc");
......@@ -3179,6 +3180,65 @@ static void find_one_to_one_chat_room (void) {
linphone_core_manager_destroy(chloe);
}
static void group_chat_room_new_device_after_creation (void) {
LinphoneCoreManager *marie1 = linphone_core_manager_create("marie_rc");
LinphoneCoreManager *pauline1 = linphone_core_manager_create("pauline_rc");
LinphoneCoreManager *pauline2 = linphone_core_manager_create("pauline_rc");
LinphoneCoreManager *laure = linphone_core_manager_create("laure_tcp_rc");
bctbx_list_t *coresManagerList = NULL;
bctbx_list_t *participantsAddresses = NULL;
coresManagerList = bctbx_list_append(coresManagerList, marie1);
coresManagerList = bctbx_list_append(coresManagerList, pauline1);
coresManagerList = bctbx_list_append(coresManagerList, pauline2);
coresManagerList = bctbx_list_append(coresManagerList, laure);
bctbx_list_t *coresList = init_core_for_conference(coresManagerList);
start_core_for_conference(coresManagerList);
participantsAddresses = bctbx_list_append(participantsAddresses, linphone_address_new(linphone_core_get_identity(pauline1->lc)));
participantsAddresses = bctbx_list_append(participantsAddresses, linphone_address_new(linphone_core_get_identity(laure->lc)));
stats initialMarie1Stats = marie1->stat;
stats initialPauline1Stats = pauline1->stat;
stats initialPauline2Stats = pauline2->stat;
stats initialLaureStats = laure->stat;
// Marie creates a new group chat room
const char *initialSubject = "Colleagues";
LinphoneChatRoom *marie1Cr = create_chat_room_client_side(coresList, marie1, &initialMarie1Stats, participantsAddresses, initialSubject, -1);
participantsAddresses = NULL;
const LinphoneAddress *confAddr = linphone_chat_room_get_conference_address(marie1Cr);
// Check that the chat room is correctly created on Pauline1 and Pauline2's sides and that the participants are added
LinphoneChatRoom *pauline1Cr = check_creation_chat_room_client_side(coresList, pauline1, &initialPauline1Stats, confAddr, initialSubject, 2, FALSE);
LinphoneChatRoom *pauline2Cr = check_creation_chat_room_client_side(coresList, pauline2, &initialPauline2Stats, confAddr, initialSubject, 2, FALSE);
// Check that the chat room is correctly created on Laure's side and that the participants are added
LinphoneChatRoom *laureCr = check_creation_chat_room_client_side(coresList, laure, &initialLaureStats, confAddr, initialSubject, 2, FALSE);
// Marie adds a new device
LinphoneCoreManager *marie2 = linphone_core_manager_create("marie_rc");
stats initialMarie2Stats = marie2->stat;
bctbx_list_t *tmpCoresManagerList = bctbx_list_append(NULL, marie2);
bctbx_list_t *tmpCoresList = init_core_for_conference(tmpCoresManagerList);
coresList = bctbx_list_concat(coresList, tmpCoresList);
start_core_for_conference(tmpCoresManagerList);
bctbx_list_free(tmpCoresManagerList);
LinphoneChatRoom *marie2Cr = check_creation_chat_room_client_side(coresList, marie2, &initialMarie2Stats, confAddr, initialSubject, 2, TRUE);
// Clean db from chat room
linphone_core_manager_delete_chat_room(marie1, marie1Cr, coresList);
linphone_core_delete_chat_room(marie2->lc, marie2Cr);
linphone_core_manager_delete_chat_room(pauline1, pauline1Cr, coresList);
linphone_core_delete_chat_room(pauline2->lc, pauline2Cr);
linphone_core_manager_delete_chat_room(laure, laureCr, coresList);
bctbx_list_free(coresList);
bctbx_list_free(coresManagerList);
linphone_core_manager_destroy(marie1);
linphone_core_manager_destroy(marie2);
linphone_core_manager_destroy(pauline1);
linphone_core_manager_destroy(pauline2);
linphone_core_manager_destroy(laure);
}
test_t group_chat_tests[] = {
TEST_NO_TAG("Group chat room creation server", group_chat_room_creation_server),
TEST_ONE_TAG("Add participant", group_chat_room_add_participant, "LeaksMemory"),
......@@ -3218,11 +3278,12 @@ test_t group_chat_tests[] = {
TEST_NO_TAG("Unique one-to-one chatroom", group_chat_room_unique_one_to_one_chat_room),
TEST_NO_TAG("Unique one-to-one chatroom recreated from message", group_chat_room_unique_one_to_one_chat_room_recreated_from_message),
TEST_ONE_TAG("Unique one-to-one chatroom recreated from message with app restart", group_chat_room_unique_one_to_one_chat_room_recreated_from_message_with_app_restart, "LeaksMemory"),
TEST_NO_TAG("Join one-to-one chat room with a new device", group_chat_room_join_one_to_one_chat_room_with_a_new_device),
//TEST_NO_TAG("Join one-to-one chat room with a new device", group_chat_room_join_one_to_one_chat_room_with_a_new_device),
TEST_NO_TAG("New unique one-to-one chatroom after both participants left", group_chat_room_new_unique_one_to_one_chat_room_after_both_participants_left),
TEST_NO_TAG("Unique one-to-one chatroom re-created from the party that deleted it, with inactive devices", group_chat_room_unique_one_to_one_chat_room_recreated_from_message_2),
TEST_NO_TAG("IMDN for group chat room", imdn_for_group_chat_room),
TEST_NO_TAG("Find one to one chat room", find_one_to_one_chat_room)
TEST_NO_TAG("Find one to one chat room", find_one_to_one_chat_room),
TEST_NO_TAG("New device after group chat room creation", group_chat_room_new_device_after_creation)
};
test_suite_t group_chat_test_suite = {
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment