Commit d8b8bc53 authored by Simon Morlat's avatar Simon Morlat

add multicall to gstate callback

parent 722ab2d9
......@@ -132,7 +132,7 @@ static void linphonec_bye_received(LinphoneCore *lc, LinphoneCall *call);
static void linphonec_text_received(LinphoneCore *lc, LinphoneChatRoom *cr,
const char *from, const char *msg);
static void linphonec_display_status (LinphoneCore * lc, const char *something);
static void linphonec_general_state (LinphoneCore * lc, LinphoneGeneralState *gstate);
static void linphonec_general_state (LinphoneCore * lc, LinphoneGeneralState *gstate, LinphoneGeneralStateContext gctx);
static void linphonec_dtmf_received(LinphoneCore *lc, int dtmf);
static void print_prompt(LinphoneCore *opm);
void linphonec_out(const char *fmt,...);
......@@ -435,7 +435,7 @@ static void linphonec_dtmf_received(LinphoneCore *lc, int dtmf){
}
static void
linphonec_general_state (LinphoneCore * lc, LinphoneGeneralState *gstate)
linphonec_general_state (LinphoneCore * lc, LinphoneGeneralState *gstate, LinphoneGeneralStateContext gctx)
{
if (show_general_state) {
switch(gstate->new_state) {
......
......@@ -52,6 +52,7 @@ static void call_received(SalOp *h){
const char *from,*to;
char *tmp;
LinphoneAddress *from_parsed;
LinphoneGeneralStateContext gctx;
/* first check if we can answer successfully to this invite */
if (lc->presence_mode!=LINPHONE_STATUS_ONLINE){
......@@ -105,7 +106,8 @@ static void call_received(SalOp *h){
linphone_address_clean(from_parsed);
tmp=linphone_address_as_string(from_parsed);
linphone_address_destroy(from_parsed);
gstate_new_state(lc, GSTATE_CALL_IN_INVITE, tmp);
gctx.call=call;
gstate_new_state(lc, GSTATE_CALL_IN_INVITE, gctx, tmp);
barmesg=ortp_strdup_printf("%s %s%s",tmp,_("is contacting you"),
(sal_call_autoanswer_asked(h)) ?_(" and asked autoanswer."):_("."));
if (lc->vtable.show) lc->vtable.show(lc);
......@@ -136,7 +138,11 @@ static void call_ringing(SalOp *h){
LinphoneCore *lc=(LinphoneCore *)sal_get_user_pointer(sal_op_get_sal(h));
LinphoneCall *call=(LinphoneCall*)sal_op_get_user_pointer(h);
SalMediaDescription *md;
LinphoneGeneralStateContext gctx;
if (call==NULL) return;
gctx.call=call;
if (lc->vtable.display_status)
lc->vtable.display_status(lc,_("Remote ringing."));
if (lc->vtable.ringing_recv)
......@@ -148,7 +154,8 @@ static void call_ringing(SalOp *h){
MSSndCard *ringcard=lc->sound_conf.lsd_card ? lc->sound_conf.lsd_card : lc->sound_conf.play_sndcard;
ms_message("Remote ringing...");
lc->ringstream=ring_start(lc->sound_conf.remote_ring,2000,ringcard);
gstate_new_state(lc, GSTATE_CALL_OUT_RINGING, NULL);
gstate_new_state(lc, GSTATE_CALL_OUT_RINGING, gctx, NULL);
}
}else{
/*accept early media */
......@@ -162,7 +169,7 @@ static void call_ringing(SalOp *h){
if (lc->vtable.show) lc->vtable.show(lc);
if (lc->vtable.display_status)
lc->vtable.display_status(lc,_("Early media."));
gstate_new_state(lc, GSTATE_CALL_OUT_RINGING, NULL);
gstate_new_state(lc, GSTATE_CALL_OUT_RINGING, gctx, NULL);
if (lc->ringstream!=NULL){
ring_stop(lc->ringstream);
lc->ringstream=NULL;
......@@ -183,10 +190,12 @@ static void call_ringing(SalOp *h){
static void call_accepted(SalOp *op){
LinphoneCore *lc=(LinphoneCore *)sal_get_user_pointer(sal_op_get_sal(op));
LinphoneCall *call=(LinphoneCall*)sal_op_get_user_pointer(op);
LinphoneGeneralStateContext gctx;
if (call==NULL){
ms_warning("No call to accept.");
return ;
}
gctx.call=call;
if (call->state==LinphoneCallAVRunning){
ms_message("GET ACK of resume\n");
if(lc->vtable.ack_resumed_recv)
......@@ -219,7 +228,7 @@ static void call_accepted(SalOp *op){
else
{
linphone_core_set_as_current_call (lc,call);
gstate_new_state(lc, GSTATE_CALL_OUT_CONNECTED, NULL);
gstate_new_state(lc, GSTATE_CALL_OUT_CONNECTED, gctx, NULL);
linphone_connect_incoming(lc,call);
}
}else{
......@@ -232,10 +241,12 @@ static void call_accepted(SalOp *op){
static void call_ack(SalOp *op){
LinphoneCore *lc=(LinphoneCore *)sal_get_user_pointer(sal_op_get_sal(op));
LinphoneCall *call=(LinphoneCall*)sal_op_get_user_pointer(op);
LinphoneGeneralStateContext gctx;
if (call==NULL){
ms_warning("No call to be ACK'd");
return ;
}
gctx.call=call;
if (call->media_pending){
if (lc->audiostream->ticker!=NULL){
/*case where we accepted early media */
......@@ -251,7 +262,7 @@ static void call_ack(SalOp *op){
if (call->resultdesc)
sal_media_description_ref(call->resultdesc);
if (call->resultdesc && !sal_media_description_empty(call->resultdesc)){
gstate_new_state(lc, GSTATE_CALL_IN_CONNECTED, NULL);
gstate_new_state(lc, GSTATE_CALL_IN_CONNECTED, gctx, NULL);
linphone_connect_incoming(lc,call);
}else{
/*send a bye*/
......@@ -312,6 +323,8 @@ static void call_updated(SalOp *op){
static void call_terminated(SalOp *op, const char *from){
LinphoneCore *lc=(LinphoneCore *)sal_get_user_pointer(sal_op_get_sal(op));
LinphoneCall *call=(LinphoneCall*)sal_op_get_user_pointer(op);
LinphoneGeneralStateContext gctx;
gctx.call=call;
if (linphone_call_get_state(call)==LinphoneCallTerminated){
ms_warning("call_terminated: ignoring.");
return;
......@@ -329,7 +342,7 @@ static void call_terminated(SalOp *op, const char *from){
if (lc->vtable.display_status!=NULL)
lc->vtable.display_status(lc,_("Call terminated."));
call->state=LinphoneCallTerminated;
gstate_new_state(lc, GSTATE_CALL_END, NULL);
gstate_new_state(lc, GSTATE_CALL_END, gctx, NULL);
if (lc->vtable.bye_recv!=NULL){
LinphoneAddress *addr=linphone_address_new(from);
char *tmp;
......@@ -352,7 +365,9 @@ static void call_failure(SalOp *op, SalError error, SalReason sr, const char *de
char *msg603=_("Call declined.");
char *msg=(char*)details;
LinphoneCall *call=(LinphoneCall*)sal_op_get_user_pointer(op);
LinphoneGeneralStateContext gctx;
gctx.call=call;
if (lc->vtable.show) lc->vtable.show(lc);
if (error==SalErrorNoResponse){
......@@ -412,8 +427,8 @@ static void call_failure(SalOp *op, SalError error, SalReason sr, const char *de
if(call == linphone_core_get_current_call(lc))
linphone_core_stop_media_streams(lc,call);
if (call!=NULL) {
if (sr!=SalReasonDeclined) gstate_new_state(lc, GSTATE_CALL_ERROR, msg);
else gstate_new_state(lc, GSTATE_CALL_END, NULL);
if (sr!=SalReasonDeclined) gstate_new_state(lc, GSTATE_CALL_ERROR, gctx, msg);
else gstate_new_state(lc, GSTATE_CALL_END, gctx, NULL);
linphone_call_set_terminated(call);
}
}
......@@ -450,8 +465,10 @@ static void register_success(SalOp *op, bool_t registered){
LinphoneCore *lc=(LinphoneCore *)sal_get_user_pointer(sal_op_get_sal(op));
LinphoneProxyConfig *cfg=(LinphoneProxyConfig*)sal_op_get_user_pointer(op);
char *msg;
LinphoneGeneralStateContext gctx;
gctx.proxy=cfg;
cfg->registered=registered;
gstate_new_state(lc, GSTATE_REG_OK, NULL);
gstate_new_state(lc, GSTATE_REG_OK,gctx, NULL);
if (cfg->registered) msg=ms_strdup_printf(_("Registration on %s successful."),sal_op_get_proxy(op));
else msg=ms_strdup_printf(_("Unregistration on %s done."),sal_op_get_proxy(op));
if (lc->vtable.display_status)
......@@ -461,9 +478,11 @@ static void register_success(SalOp *op, bool_t registered){
static void register_failure(SalOp *op, SalError error, SalReason reason, const char *details){
LinphoneCore *lc=(LinphoneCore *)sal_get_user_pointer(sal_op_get_sal(op));
LinphoneGeneralStateContext gctx;
char *msg=ortp_strdup_printf(_("Registration on %s failed: %s"),sal_op_get_proxy(op),(details!=NULL) ? details : _("no response timeout"));
if (lc->vtable.display_status) lc->vtable.display_status(lc,msg);
gstate_new_state(lc, GSTATE_REG_FAILED, msg);
gctx.proxy=(LinphoneProxyConfig*)sal_op_get_user_pointer (op);
gstate_new_state(lc, GSTATE_REG_FAILED, gctx, msg);
ms_free(msg);
}
......
......@@ -83,35 +83,36 @@ static void linphone_core_set_state(LinphoneCore *lc, gstate_group_t group, gsta
void gstate_new_state(struct _LinphoneCore *lc,
gstate_t new_state,
LinphoneGeneralStateContext gctx,
const char *message) {
LinphoneGeneralState states_arg;
/* determine the affected group */
if (new_state < GSTATE_REG_NONE)
states_arg.group = GSTATE_GROUP_POWER;
else if (new_state < GSTATE_CALL_IDLE)
states_arg.group = GSTATE_GROUP_REG;
else
states_arg.group = GSTATE_GROUP_CALL;
/* store the new state while remembering the old one */
states_arg.new_state = new_state;
states_arg.old_state = linphone_core_get_state(lc,states_arg.group);
linphone_core_set_state(lc, states_arg.group,new_state);
states_arg.message = message;
/*printf("gstate_new_state: %s\t-> %s\t(%s)\n",
_gstates_text[states_arg.old_state],
_gstates_text[states_arg.new_state],
message);*/
LinphoneGeneralState states_arg;
/* call the virtual method */
if (lc->vtable.general_state)
lc->vtable.general_state(lc, &states_arg);
/* immediately proceed to idle state */
if (new_state == GSTATE_CALL_END ||
new_state == GSTATE_CALL_ERROR)
gstate_new_state(lc, GSTATE_CALL_IDLE, NULL);
/* determine the affected group */
if (new_state < GSTATE_REG_NONE)
states_arg.group = GSTATE_GROUP_POWER;
else if (new_state < GSTATE_CALL_IDLE)
states_arg.group = GSTATE_GROUP_REG;
else
states_arg.group = GSTATE_GROUP_CALL;
/* store the new state while remembering the old one */
states_arg.new_state = new_state;
states_arg.old_state = linphone_core_get_state(lc,states_arg.group);
linphone_core_set_state(lc, states_arg.group,new_state);
states_arg.message = message;
/*printf("gstate_new_state: %s\t-> %s\t(%s)\n",
_gstates_text[states_arg.old_state],
_gstates_text[states_arg.new_state],
message);*/
/* call the virtual method */
if (lc->vtable.general_state)
lc->vtable.general_state(lc, &states_arg, gctx);
/* immediately proceed to idle state */
if (new_state == GSTATE_CALL_END ||
new_state == GSTATE_CALL_ERROR)
gstate_new_state(lc, GSTATE_CALL_IDLE, gctx, NULL);
}
......@@ -166,7 +166,7 @@ void linphone_call_set_terminated(LinphoneCall *call){
linphone_core_notify_all_friends(lc,lc->prev_mode);
linphone_core_update_allocated_audio_bandwidth(lc);
if (call->state==LinphoneCallAVRunning){
if (call->state==LinphoneCallAVRunning || call->state==LinphoneCallPaused){
status=LinphoneCallSuccess;
}
......@@ -289,6 +289,14 @@ void linphone_call_set_user_pointer(LinphoneCall *call, void *user_pointer)
call->user_pointer = user_pointer;
}
/**
* Returns the call log associated to this call.
**/
LinphoneCallLog *linphone_call_get_call_log(const LinphoneCall *call){
return call->log;
}
/**
* @}
**/
......
......@@ -910,13 +910,14 @@ static void linphone_core_free_payload_types(void){
static void linphone_core_init (LinphoneCore * lc, const LinphoneCoreVTable *vtable, const char *config_path,
const char *factory_config_path, void * userdata)
{
LinphoneGeneralStateContext gctx={0};
memset (lc, 0, sizeof (LinphoneCore));
lc->data=userdata;
memcpy(&lc->vtable,vtable,sizeof(LinphoneCoreVTable));
gstate_initialize(lc);
gstate_new_state(lc, GSTATE_POWER_STARTUP, NULL);
gstate_new_state(lc, GSTATE_POWER_STARTUP, gctx, NULL);
ortp_init();
linphone_core_assign_payload_type(&payload_type_pcmu8000,0,NULL);
......@@ -982,7 +983,7 @@ static void linphone_core_init (LinphoneCore * lc, const LinphoneCoreVTable *vta
ui_config_read(lc);
if (lc->vtable.display_status)
lc->vtable.display_status(lc,_("Ready"));
gstate_new_state(lc, GSTATE_POWER_ON, NULL);
gstate_new_state(lc, GSTATE_POWER_ON, gctx, NULL);
lc->auto_net_state_mon=lc->sip_conf.auto_net_state_mon;
lc->ready=TRUE;
......@@ -1902,6 +1903,7 @@ int linphone_core_start_invite(LinphoneCore *lc, LinphoneCall *call, LinphonePro
char *contact;
char *real_url,*barmsg;
char *from;
LinphoneGeneralStateContext gctx;
/*try to be best-effort in giving real local or routable contact address */
contact=get_fixed_contact(lc,call,dest_proxy);
if (contact){
......@@ -1936,7 +1938,10 @@ int linphone_core_start_invite(LinphoneCore *lc, LinphoneCall *call, LinphonePro
if(call == linphone_core_get_current_call(lc))
linphone_core_stop_media_streams(lc,call);
linphone_call_set_terminated (call);
}else gstate_new_state(lc, GSTATE_CALL_OUT_INVITE, real_url);
}else {
gctx.call=call;
gstate_new_state(lc, GSTATE_CALL_OUT_INVITE, gctx, real_url);
}
ms_free(real_url);
ms_free(from);
return err;
......@@ -2406,6 +2411,7 @@ int linphone_core_accept_call(LinphoneCore *lc, LinphoneCall *call)
{
LinphoneProxyConfig *cfg=NULL;
const char *contact=NULL;
LinphoneGeneralStateContext gctx;
if (call==NULL){
//if just one call is present answer the only one ...
......@@ -2441,7 +2447,8 @@ int linphone_core_accept_call(LinphoneCore *lc, LinphoneCall *call)
sal_call_accept(call->op);
if (lc->vtable.display_status!=NULL)
lc->vtable.display_status(lc,_("Connected."));
gstate_new_state(lc, GSTATE_CALL_IN_CONNECTED, NULL);
gctx.call=call;
gstate_new_state(lc, GSTATE_CALL_IN_CONNECTED, gctx, NULL);
call->resultdesc=sal_call_get_final_media_description(call->op);
if (call->resultdesc){
sal_media_description_ref(call->resultdesc);
......@@ -2463,6 +2470,7 @@ int linphone_core_accept_call(LinphoneCore *lc, LinphoneCall *call)
int linphone_core_terminate_call(LinphoneCore *lc, LinphoneCall *the_call)
{
LinphoneCall *call;
LinphoneGeneralStateContext gctx;
if (the_call == NULL){
call = linphone_core_get_current_call(lc);
if(call == NULL){
......@@ -2485,7 +2493,8 @@ int linphone_core_terminate_call(LinphoneCore *lc, LinphoneCall *the_call)
linphone_core_stop_media_streams(lc,call);
if (lc->vtable.display_status!=NULL)
lc->vtable.display_status(lc,_("Call ended") );
gstate_new_state(lc, GSTATE_CALL_END, NULL);
gctx.call=call;
gstate_new_state(lc, GSTATE_CALL_END, gctx, NULL);
linphone_call_set_terminated(call);
return 0;
}
......@@ -3816,6 +3825,7 @@ LpConfig *linphone_core_get_config(LinphoneCore *lc){
static void linphone_core_uninit(LinphoneCore *lc)
{
LinphoneGeneralStateContext gctx;
while(lc->calls)
{
LinphoneCall *the_call = lc->calls->data;
......@@ -3830,7 +3840,8 @@ static void linphone_core_uninit(LinphoneCore *lc)
if (lc->friends)
ms_list_for_each(lc->friends,(void (*)(void *))linphone_friend_close_subscriptions);
gstate_new_state(lc, GSTATE_POWER_SHUTDOWN, NULL);
gctx.call=NULL;
gstate_new_state(lc, GSTATE_POWER_SHUTDOWN, gctx, NULL);
#ifdef VIDEO_ENABLED
if (lc->previewstream!=NULL){
video_preview_stop(lc->previewstream);
......@@ -3857,7 +3868,7 @@ static void linphone_core_uninit(LinphoneCore *lc)
linphone_core_free_payload_types();
ortp_exit();
gstate_new_state(lc, GSTATE_POWER_OFF, NULL);
gstate_new_state(lc, GSTATE_POWER_OFF, gctx, NULL);
}
static void set_network_reachable(LinphoneCore* lc,bool_t isReachable){
......
......@@ -97,30 +97,6 @@ void linphone_address_destroy(LinphoneAddress *u);
struct _SipSetupContext;
/**
* The LinphoneCall object represents a call issued or received by the LinphoneCore
**/
struct _LinphoneCall;
typedef struct _LinphoneCall LinphoneCall;
typedef enum _LinphoneCallState{
LinphoneCallInit,
LinphoneCallPreEstablishing,
LinphoneCallRinging,
LinphoneCallAVRunning,
LinphoneCallPaused,
LinphoneCallTerminated
}LinphoneCallState;
LinphoneCallState linphone_call_get_state(const LinphoneCall *call);
bool_t linphone_call_asked_to_autoanswer(LinphoneCall *call);
bool_t linphone_call_paused(LinphoneCall *call);
const LinphoneAddress * linphone_core_get_current_call_remote_address(struct _LinphoneCore *lc);
const LinphoneAddress * linphone_call_get_remote_address(const LinphoneCall *call);
char *linphone_call_get_remote_address_as_string(const LinphoneCall *call);
void linphone_call_ref(LinphoneCall *call);
void linphone_call_unref(LinphoneCall *call);
/**
* Enum representing the direction of a call.
......@@ -178,6 +154,33 @@ const rtp_stats_t *linphone_call_log_get_local_stats(const LinphoneCallLog *cl);
const rtp_stats_t *linphone_call_log_get_remote_stats(const LinphoneCallLog *cl);
char * linphone_call_log_to_str(LinphoneCallLog *cl);
/**
* The LinphoneCall object represents a call issued or received by the LinphoneCore
**/
struct _LinphoneCall;
typedef struct _LinphoneCall LinphoneCall;
typedef enum _LinphoneCallState{
LinphoneCallInit,
LinphoneCallPreEstablishing,
LinphoneCallRinging,
LinphoneCallAVRunning,
LinphoneCallPaused,
LinphoneCallTerminated
}LinphoneCallState;
LinphoneCallState linphone_call_get_state(const LinphoneCall *call);
bool_t linphone_call_asked_to_autoanswer(LinphoneCall *call);
bool_t linphone_call_paused(LinphoneCall *call);
const LinphoneAddress * linphone_core_get_current_call_remote_address(struct _LinphoneCore *lc);
const LinphoneAddress * linphone_call_get_remote_address(const LinphoneCall *call);
char *linphone_call_get_remote_address_as_string(const LinphoneCall *call);
void linphone_call_ref(LinphoneCall *call);
void linphone_call_unref(LinphoneCall *call);
LinphoneCallLog *linphone_call_get_call_log(const LinphoneCall *call);
typedef enum{
LinphoneSPWait,
LinphoneSPDeny,
......@@ -405,10 +408,11 @@ struct _LinphoneGeneralState {
};
typedef struct _LinphoneGeneralState LinphoneGeneralState;
/* private: set a new state */
void gstate_new_state(struct _LinphoneCore *lc, gstate_t new_state, const char *message);
/*private*/
void gstate_initialize(struct _LinphoneCore *lc) ;
typedef union _LinphoneGeneralStateContext{
LinphoneCall *call;
LinphoneProxyConfig *proxy;
}LinphoneGeneralStateContext;
/**
* @addtogroup initializing
......@@ -458,7 +462,7 @@ typedef void (*CallLogUpdated)(struct _LinphoneCore *lc, struct _LinphoneCallLog
/** Callback prototype */
typedef void (*TextMessageReceived)(struct _LinphoneCore *lc, LinphoneChatRoom *room, const char *from, const char *message);
/** Callback prototype */
typedef void (*GeneralStateChange)(struct _LinphoneCore *lc, LinphoneGeneralState *gstate);
typedef void (*GeneralStateChange)(struct _LinphoneCore *lc, LinphoneGeneralState *gstate, LinphoneGeneralStateContext ctx);
/** Callback prototype */
typedef void (*DtmfReceived)(struct _LinphoneCore* lc, int dtmf);
/** Callback prototype */
......
......@@ -55,7 +55,10 @@
#endif
/* private: set a new state */
void gstate_new_state(struct _LinphoneCore *lc, gstate_t new_state, LinphoneGeneralStateContext context, const char *message);
/*private*/
void gstate_initialize(struct _LinphoneCore *lc);
struct _LinphoneCall
{
......
......@@ -259,7 +259,10 @@ static char *guess_contact_for_register(LinphoneProxyConfig *obj){
}
static void linphone_proxy_config_register(LinphoneProxyConfig *obj){
LinphoneGeneralStateContext gctx;
const char *id_str;
gctx.proxy=obj;
if (obj->reg_identity!=NULL) id_str=obj->reg_identity;
else id_str=linphone_core_get_primary_contact(obj->lc);
if (obj->reg_sendregister){
......@@ -272,9 +275,9 @@ static void linphone_proxy_config_register(LinphoneProxyConfig *obj){
ms_free(contact);
sal_op_set_user_pointer(obj->op,obj);
if (!sal_register(obj->op,obj->reg_proxy,obj->reg_identity,obj->expires)) {
gstate_new_state(obj->lc,GSTATE_REG_PENDING,NULL);
gstate_new_state(obj->lc,GSTATE_REG_PENDING, gctx, NULL);
} else {
gstate_new_state(obj->lc,GSTATE_REG_FAILED,NULL);
gstate_new_state(obj->lc,GSTATE_REG_FAILED, gctx, NULL);
}
}
}
......
......@@ -53,7 +53,7 @@ static void linphone_gtk_display_warning(LinphoneCore *lc, const char *warning);
static void linphone_gtk_display_url(LinphoneCore *lc, const char *msg, const char *url);
static void linphone_gtk_display_question(LinphoneCore *lc, const char *question);
static void linphone_gtk_call_log_updated(LinphoneCore *lc, LinphoneCallLog *cl);
static void linphone_gtk_general_state(LinphoneCore *lc, LinphoneGeneralState *gstate);
static void linphone_gtk_general_state(LinphoneCore *lc, LinphoneGeneralState *gstate, LinphoneGeneralStateContext gctx);
static void linphone_gtk_refer_received(LinphoneCore *lc, LinphoneCall *call, const char *refer_to);
static gboolean linphone_gtk_auto_answer(GtkWidget *incall_window);
......@@ -879,7 +879,7 @@ static void linphone_gtk_call_log_updated(LinphoneCore *lc, LinphoneCallLog *cl)
if (w) linphone_gtk_call_log_update(w);
}
static void linphone_gtk_general_state(LinphoneCore *lc, LinphoneGeneralState *gstate){
static void linphone_gtk_general_state(LinphoneCore *lc, LinphoneGeneralState *gstate, LinphoneGeneralStateContext gctx){
switch(gstate->new_state){
case GSTATE_CALL_OUT_CONNECTED:
case GSTATE_CALL_IN_CONNECTED:
......
mediastreamer2 @ 55e0624b
Subproject commit 17e879de84d894df6a1875665594567c1cf43617
Subproject commit 55e0624b53e984eecc6b5a4eedef467456968886
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