Skip to content
Projects
Groups
Snippets
Help
Loading...
Help
Support
Keyboard shortcuts
?
Submit feedback
Contribute to GitLab
Sign in
Toggle navigation
V
vlc-1.1
Project overview
Project overview
Details
Activity
Releases
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Issues
0
Issues
0
List
Boards
Labels
Milestones
Redmine
Redmine
Merge Requests
0
Merge Requests
0
CI / CD
CI / CD
Pipelines
Jobs
Schedules
Operations
Operations
Metrics
Environments
Analytics
Analytics
CI / CD
Repository
Value Stream
Wiki
Wiki
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Create a new issue
Jobs
Commits
Issue Boards
Open sidebar
videolan
vlc-1.1
Commits
9cb381a0
Commit
9cb381a0
authored
Jul 22, 2004
by
Eric Petit
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
netsync.c: converted to unix file because BeOS' gcc chokes on dos files
parent
64f0cbff
Changes
1
Hide whitespace changes
Inline
Side-by-side
Showing
1 changed file
with
340 additions
and
340 deletions
+340
-340
modules/control/netsync.c
modules/control/netsync.c
+340
-340
No files found.
modules/control/netsync.c
View file @
9cb381a0
/*****************************************************************************
/*****************************************************************************
* netsync.c: synchronisation between several network clients.
* netsync.c: synchronisation between several network clients.
*****************************************************************************
*****************************************************************************
* Copyright (C) 2004 VideoLAN
* Copyright (C) 2004 VideoLAN
* $Id$
* $Id$
*
*
* Authors: Gildas Bazin <gbazin@videolan.org>
* Authors: Gildas Bazin <gbazin@videolan.org>
*
*
* This program is free software; you can redistribute it and/or modify
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
* (at your option) any later version.
*
*
* This program is distributed in the hope that it will be useful,
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
* GNU General Public License for more details.
*
*
* You should have received a copy of the GNU General Public License
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* along with this program; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111, USA.
* Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111, USA.
*****************************************************************************/
*****************************************************************************/
/*****************************************************************************
/*****************************************************************************
* Preamble
* Preamble
*****************************************************************************/
*****************************************************************************/
#include <stdlib.h>
#include <stdlib.h>
#include <vlc/vlc.h>
#include <vlc/vlc.h>
#include <vlc/intf.h>
#include <vlc/intf.h>
#ifdef HAVE_UNISTD_H
#ifdef HAVE_UNISTD_H
# include <unistd.h>
# include <unistd.h>
#endif
#endif
#ifdef HAVE_SYS_TIME_H
#ifdef HAVE_SYS_TIME_H
# include <sys/time.h>
# include <sys/time.h>
#endif
#endif
#include <sys/types.h>
#include <sys/types.h>
#ifdef WIN32
#ifdef WIN32
# include <winsock2.h>
# include <winsock2.h>
# include <ws2tcpip.h>
# include <ws2tcpip.h>
# ifndef IN_MULTICAST
# ifndef IN_MULTICAST
# define IN_MULTICAST(a) IN_CLASSD(a)
# define IN_MULTICAST(a) IN_CLASSD(a)
# endif
# endif
#else
#else
# include <sys/socket.h>
# include <sys/socket.h>
# include <netinet/in.h>
# include <netinet/in.h>
# if HAVE_ARPA_INET_H
# if HAVE_ARPA_INET_H
# include <arpa/inet.h>
# include <arpa/inet.h>
# elif defined( SYS_BEOS )
# elif defined( SYS_BEOS )
# include <net/netdb.h>
# include <net/netdb.h>
# endif
# endif
#endif
#endif
#ifdef UNDER_CE
#ifdef UNDER_CE
# define close(a) CloseHandle(a);
# define close(a) CloseHandle(a);
#elif defined( WIN32 )
#elif defined( WIN32 )
# define close(a) closesocket(a);
# define close(a) closesocket(a);
#endif
#endif
#include "network.h"
#include "network.h"
#define NETSYNC_PORT_MASTER 9875
#define NETSYNC_PORT_MASTER 9875
#define NETSYNC_PORT_SLAVE 9876
#define NETSYNC_PORT_SLAVE 9876
/* Needed for Solaris */
/* Needed for Solaris */
#ifndef INADDR_NONE
#ifndef INADDR_NONE
#define INADDR_NONE 0xffffffff
#define INADDR_NONE 0xffffffff
#endif
#endif
/*****************************************************************************
/*****************************************************************************
* Module descriptor
* Module descriptor
*****************************************************************************/
*****************************************************************************/
static
int
Activate
(
vlc_object_t
*
);
static
int
Activate
(
vlc_object_t
*
);
static
void
Close
(
vlc_object_t
*
);
static
void
Close
(
vlc_object_t
*
);
static
mtime_t
GetClockRef
(
intf_thread_t
*
,
mtime_t
);
static
mtime_t
GetClockRef
(
intf_thread_t
*
,
mtime_t
);
#define NETSYNC_TEXT N_( "Act as master for network synchronisation" )
#define NETSYNC_TEXT N_( "Act as master for network synchronisation" )
#define NETSYNC_LONGTEXT N_( "Allows you to specify if this client should " \
#define NETSYNC_LONGTEXT N_( "Allows you to specify if this client should " \
"act as the master client for the network synchronisation."
)
"act as the master client for the network synchronisation." )
#define MIP_TEXT N_( "Master client ip address" )
#define MIP_TEXT N_( "Master client ip address" )
#define MIP_LONGTEXT N_( "Allows you to specify the ip address of " \
#define MIP_LONGTEXT N_( "Allows you to specify the ip address of " \
"the master client used for the network synchronisation."
)
"the master client used for the network synchronisation." )
vlc_module_begin
();
vlc_module_begin
();
set_description
(
_
(
"Network synchronisation"
)
);
set_description
(
_
(
"Network synchronisation"
)
);
add_bool
(
"netsync-master"
,
0
,
NULL
,
add_bool
(
"netsync-master"
,
0
,
NULL
,
NETSYNC_TEXT
,
NETSYNC_LONGTEXT
,
VLC_TRUE
);
NETSYNC_TEXT
,
NETSYNC_LONGTEXT
,
VLC_TRUE
);
add_string
(
"netsync-master-ip"
,
NULL
,
NULL
,
MIP_TEXT
,
MIP_LONGTEXT
,
add_string
(
"netsync-master-ip"
,
NULL
,
NULL
,
MIP_TEXT
,
MIP_LONGTEXT
,
VLC_TRUE
);
VLC_TRUE
);
set_capability
(
"interface"
,
0
);
set_capability
(
"interface"
,
0
);
set_callbacks
(
Activate
,
Close
);
set_callbacks
(
Activate
,
Close
);
vlc_module_end
();
vlc_module_end
();
struct
intf_sys_t
struct
intf_sys_t
{
{
input_thread_t
*
p_input
;
input_thread_t
*
p_input
;
};
};
/*****************************************************************************
/*****************************************************************************
* Local prototypes
* Local prototypes
*****************************************************************************/
*****************************************************************************/
static
void
Run
(
intf_thread_t
*
p_intf
);
static
void
Run
(
intf_thread_t
*
p_intf
);
/*****************************************************************************
/*****************************************************************************
* Activate: initialize and create stuff
* Activate: initialize and create stuff
*****************************************************************************/
*****************************************************************************/
static
int
Activate
(
vlc_object_t
*
p_this
)
static
int
Activate
(
vlc_object_t
*
p_this
)
{
{
intf_thread_t
*
p_intf
=
(
intf_thread_t
*
)
p_this
;
intf_thread_t
*
p_intf
=
(
intf_thread_t
*
)
p_this
;
msg_Info
(
p_intf
,
"Using the netsync interface module..."
);
msg_Info
(
p_intf
,
"Using the netsync interface module..."
);
p_intf
->
p_sys
=
malloc
(
sizeof
(
intf_sys_t
)
);
p_intf
->
p_sys
=
malloc
(
sizeof
(
intf_sys_t
)
);
if
(
!
p_intf
->
p_sys
)
if
(
!
p_intf
->
p_sys
)
{
{
msg_Err
(
p_intf
,
"no memory"
);
msg_Err
(
p_intf
,
"no memory"
);
return
VLC_ENOMEM
;
return
VLC_ENOMEM
;
}
}
p_intf
->
p_sys
->
p_input
=
NULL
;
p_intf
->
p_sys
->
p_input
=
NULL
;
p_intf
->
pf_run
=
Run
;
p_intf
->
pf_run
=
Run
;
return
VLC_SUCCESS
;
return
VLC_SUCCESS
;
}
}
/*****************************************************************************
/*****************************************************************************
* Close: destroy interface
* Close: destroy interface
*****************************************************************************/
*****************************************************************************/
void
Close
(
vlc_object_t
*
p_this
)
void
Close
(
vlc_object_t
*
p_this
)
{
{
intf_thread_t
*
p_intf
=
(
intf_thread_t
*
)
p_this
;
intf_thread_t
*
p_intf
=
(
intf_thread_t
*
)
p_this
;
free
(
p_intf
->
p_sys
);
free
(
p_intf
->
p_sys
);
}
}
/*****************************************************************************
/*****************************************************************************
* Run: interface thread
* Run: interface thread
*****************************************************************************/
*****************************************************************************/
static
void
Run
(
intf_thread_t
*
p_intf
)
static
void
Run
(
intf_thread_t
*
p_intf
)
{
{
#define MAX_MSG_LENGTH (2 * sizeof(int64_t))
#define MAX_MSG_LENGTH (2 * sizeof(int64_t))
vlc_bool_t
b_master
=
config_GetInt
(
p_intf
,
"netsync-master"
);
vlc_bool_t
b_master
=
config_GetInt
(
p_intf
,
"netsync-master"
);
char
*
psz_master
;
char
*
psz_master
;
char
p_data
[
MAX_MSG_LENGTH
];
char
p_data
[
MAX_MSG_LENGTH
];
int
i_socket
;
int
i_socket
;
if
(
!
b_master
)
if
(
!
b_master
)
{
{
psz_master
=
config_GetPsz
(
p_intf
,
"netsync-master-ip"
);
psz_master
=
config_GetPsz
(
p_intf
,
"netsync-master-ip"
);
if
(
psz_master
==
NULL
)
if
(
psz_master
==
NULL
)
{
{
msg_Err
(
p_intf
,
"master address not specified"
);
msg_Err
(
p_intf
,
"master address not specified"
);
return
;
return
;
}
}
}
}
i_socket
=
net_OpenUDP
(
p_intf
,
NULL
,
i_socket
=
net_OpenUDP
(
p_intf
,
NULL
,
b_master
?
NETSYNC_PORT_MASTER
:
NETSYNC_PORT_SLAVE
,
b_master
?
NETSYNC_PORT_MASTER
:
NETSYNC_PORT_SLAVE
,
b_master
?
NULL
:
psz_master
,
b_master
?
NULL
:
psz_master
,
b_master
?
0
:
NETSYNC_PORT_MASTER
);
b_master
?
0
:
NETSYNC_PORT_MASTER
);
if
(
!
b_master
)
if
(
!
b_master
)
free
(
psz_master
);
free
(
psz_master
);
if
(
i_socket
<
0
)
if
(
i_socket
<
0
)
{
{
msg_Err
(
p_intf
,
"failed opening UDP socket."
);
msg_Err
(
p_intf
,
"failed opening UDP socket."
);
return
;
return
;
}
}
/* High priority thread */
/* High priority thread */
vlc_thread_set_priority
(
p_intf
,
VLC_THREAD_PRIORITY_INPUT
);
vlc_thread_set_priority
(
p_intf
,
VLC_THREAD_PRIORITY_INPUT
);
while
(
!
p_intf
->
b_die
)
while
(
!
p_intf
->
b_die
)
{
{
struct
timeval
timeout
;
struct
timeval
timeout
;
fd_set
fds_r
;
fd_set
fds_r
;
/* Update the input */
/* Update the input */
if
(
p_intf
->
p_sys
->
p_input
==
NULL
)
if
(
p_intf
->
p_sys
->
p_input
==
NULL
)
{
{
p_intf
->
p_sys
->
p_input
=
p_intf
->
p_sys
->
p_input
=
(
input_thread_t
*
)
vlc_object_find
(
p_intf
,
VLC_OBJECT_INPUT
,
(
input_thread_t
*
)
vlc_object_find
(
p_intf
,
VLC_OBJECT_INPUT
,
FIND_ANYWHERE
);
FIND_ANYWHERE
);
}
}
else
if
(
p_intf
->
p_sys
->
p_input
->
b_dead
)
else
if
(
p_intf
->
p_sys
->
p_input
->
b_dead
)
{
{
vlc_object_release
(
p_intf
->
p_sys
->
p_input
);
vlc_object_release
(
p_intf
->
p_sys
->
p_input
);
p_intf
->
p_sys
->
p_input
=
NULL
;
p_intf
->
p_sys
->
p_input
=
NULL
;
}
}
if
(
p_intf
->
p_sys
->
p_input
==
NULL
)
if
(
p_intf
->
p_sys
->
p_input
==
NULL
)
{
{
/* Wait a bit */
/* Wait a bit */
msleep
(
INTF_IDLE_SLEEP
);
msleep
(
INTF_IDLE_SLEEP
);
continue
;
continue
;
}
}
/*
/*
* We now have an input
* We now have an input
*/
*/
/* Initialize file descriptor set and timeout (0.5s) */
/* Initialize file descriptor set and timeout (0.5s) */
FD_ZERO
(
&
fds_r
);
FD_ZERO
(
&
fds_r
);
FD_SET
(
i_socket
,
&
fds_r
);
FD_SET
(
i_socket
,
&
fds_r
);
timeout
.
tv_sec
=
0
;
timeout
.
tv_sec
=
0
;
timeout
.
tv_usec
=
500000
;
timeout
.
tv_usec
=
500000
;
if
(
b_master
)
if
(
b_master
)
{
{
struct
sockaddr_storage
from
;
struct
sockaddr_storage
from
;
mtime_t
i_date
,
i_clockref
,
i_master_clockref
;
mtime_t
i_date
,
i_clockref
,
i_master_clockref
;
int
i_struct_size
,
i_read
,
i_ret
;
int
i_struct_size
,
i_read
,
i_ret
;
/* Don't block */
/* Don't block */
i_ret
=
select
(
i_socket
+
1
,
&
fds_r
,
0
,
0
,
&
timeout
);
i_ret
=
select
(
i_socket
+
1
,
&
fds_r
,
0
,
0
,
&
timeout
);
if
(
i_ret
==
0
)
continue
;
if
(
i_ret
==
0
)
continue
;
if
(
i_ret
<
0
)
if
(
i_ret
<
0
)
{
{
/* Wait a bit */
/* Wait a bit */
msleep
(
INTF_IDLE_SLEEP
);
msleep
(
INTF_IDLE_SLEEP
);
continue
;
continue
;
}
}
/* We received something */
/* We received something */
i_struct_size
=
sizeof
(
from
);
i_struct_size
=
sizeof
(
from
);
i_read
=
recvfrom
(
i_socket
,
p_data
,
MAX_MSG_LENGTH
,
0
,
i_read
=
recvfrom
(
i_socket
,
p_data
,
MAX_MSG_LENGTH
,
0
,
(
struct
sockaddr
*
)
&
from
,
&
i_struct_size
);
(
struct
sockaddr
*
)
&
from
,
&
i_struct_size
);
i_clockref
=
ntoh64
(
*
(
int64_t
*
)
p_data
);
i_clockref
=
ntoh64
(
*
(
int64_t
*
)
p_data
);
i_date
=
mdate
();
i_date
=
mdate
();
*
(
int64_t
*
)
p_data
=
hton64
(
i_date
);
*
(
int64_t
*
)
p_data
=
hton64
(
i_date
);
i_master_clockref
=
GetClockRef
(
p_intf
,
i_clockref
);
i_master_clockref
=
GetClockRef
(
p_intf
,
i_clockref
);
*
(((
int64_t
*
)
p_data
)
+
1
)
=
hton64
(
i_master_clockref
);
*
(((
int64_t
*
)
p_data
)
+
1
)
=
hton64
(
i_master_clockref
);
/* Reply to the sender */
/* Reply to the sender */
sendto
(
i_socket
,
p_data
,
2
*
sizeof
(
int64_t
),
0
,
sendto
(
i_socket
,
p_data
,
2
*
sizeof
(
int64_t
),
0
,
(
struct
sockaddr
*
)
&
from
,
i_struct_size
);
(
struct
sockaddr
*
)
&
from
,
i_struct_size
);
msg_Dbg
(
p_intf
,
"Master clockref: "
I64Fd
" -> "
I64Fd
", from %s "
msg_Dbg
(
p_intf
,
"Master clockref: "
I64Fd
" -> "
I64Fd
", from %s "
"(date: "
I64Fd
")"
,
i_clockref
,
i_master_clockref
,
"(date: "
I64Fd
")"
,
i_clockref
,
i_master_clockref
,
from
.
ss_family
==
AF_INET
from
.
ss_family
==
AF_INET
?
inet_ntoa
(((
struct
sockaddr_in
*
)
&
from
)
->
sin_addr
)
?
inet_ntoa
(((
struct
sockaddr_in
*
)
&
from
)
->
sin_addr
)
:
"non-IPv4"
,
i_date
);
:
"non-IPv4"
,
i_date
);
}
}
else
else
{
{
mtime_t
i_send_date
,
i_receive_date
,
i_master_date
,
i_diff_date
;
mtime_t
i_send_date
,
i_receive_date
,
i_master_date
,
i_diff_date
;
mtime_t
i_master_clockref
,
i_client_clockref
,
i_drift
;
mtime_t
i_master_clockref
,
i_client_clockref
,
i_drift
;
mtime_t
i_clockref
=
0
;
mtime_t
i_clockref
=
0
;
int
i_sent
,
i_read
,
i_ret
;
int
i_sent
,
i_read
,
i_ret
;
/* Send clock request to the master */
/* Send clock request to the master */
*
(
int64_t
*
)
p_data
=
hton64
(
i_clockref
);
*
(
int64_t
*
)
p_data
=
hton64
(
i_clockref
);
i_send_date
=
mdate
();
i_send_date
=
mdate
();
i_sent
=
send
(
i_socket
,
p_data
,
sizeof
(
int64_t
),
0
);
i_sent
=
send
(
i_socket
,
p_data
,
sizeof
(
int64_t
),
0
);
if
(
i_sent
<=
0
)
if
(
i_sent
<=
0
)
{
{
/* Wait a bit */
/* Wait a bit */
msleep
(
INTF_IDLE_SLEEP
);
msleep
(
INTF_IDLE_SLEEP
);
continue
;
continue
;
}
}
/* Don't block */
/* Don't block */
i_ret
=
select
(
i_socket
+
1
,
&
fds_r
,
0
,
0
,
&
timeout
);
i_ret
=
select
(
i_socket
+
1
,
&
fds_r
,
0
,
0
,
&
timeout
);
if
(
i_ret
==
0
)
continue
;
if
(
i_ret
==
0
)
continue
;
if
(
i_ret
<
0
)
if
(
i_ret
<
0
)
{
{
/* Wait a bit */
/* Wait a bit */
msleep
(
INTF_IDLE_SLEEP
);
msleep
(
INTF_IDLE_SLEEP
);
continue
;
continue
;
}
}
i_receive_date
=
mdate
();
i_receive_date
=
mdate
();
i_read
=
recv
(
i_socket
,
p_data
,
MAX_MSG_LENGTH
,
0
);
i_read
=
recv
(
i_socket
,
p_data
,
MAX_MSG_LENGTH
,
0
);
if
(
i_read
<=
0
)
if
(
i_read
<=
0
)
{
{
/* Wait a bit */
/* Wait a bit */
msleep
(
INTF_IDLE_SLEEP
);
msleep
(
INTF_IDLE_SLEEP
);
continue
;
continue
;
}
}
i_master_date
=
ntoh64
(
*
(
int64_t
*
)
p_data
);
i_master_date
=
ntoh64
(
*
(
int64_t
*
)
p_data
);
i_master_clockref
=
ntoh64
(
*
(((
int64_t
*
)
p_data
)
+
1
));
i_master_clockref
=
ntoh64
(
*
(((
int64_t
*
)
p_data
)
+
1
));
i_diff_date
=
i_receive_date
-
i_diff_date
=
i_receive_date
-
((
i_receive_date
-
i_send_date
)
/
2
+
i_master_date
);
((
i_receive_date
-
i_send_date
)
/
2
+
i_master_date
);
i_client_clockref
=
i_drift
=
0
;
i_client_clockref
=
i_drift
=
0
;
if
(
p_intf
->
p_sys
->
p_input
&&
i_master_clockref
)
if
(
p_intf
->
p_sys
->
p_input
&&
i_master_clockref
)
{
{
i_client_clockref
=
GetClockRef
(
p_intf
,
i_clockref
);
i_client_clockref
=
GetClockRef
(
p_intf
,
i_clockref
);
i_drift
=
i_client_clockref
-
i_master_clockref
-
i_diff_date
;
i_drift
=
i_client_clockref
-
i_master_clockref
-
i_diff_date
;
/* Update our clock to match the master's one */
/* Update our clock to match the master's one */
if
(
i_client_clockref
)
if
(
i_client_clockref
)
p_intf
->
p_sys
->
p_input
->
i_pts_delay
-=
i_drift
;
p_intf
->
p_sys
->
p_input
->
i_pts_delay
-=
i_drift
;
}
}
msg_Dbg
(
p_intf
,
"Slave clockref: "
I64Fd
" -> "
I64Fd
" -> "
I64Fd
", "
msg_Dbg
(
p_intf
,
"Slave clockref: "
I64Fd
" -> "
I64Fd
" -> "
I64Fd
", "
"clock diff: "
I64Fd
" drift: "
I64Fd
,
"clock diff: "
I64Fd
" drift: "
I64Fd
,
i_clockref
,
i_master_clockref
,
i_clockref
,
i_master_clockref
,
i_client_clockref
,
i_diff_date
,
i_drift
);
i_client_clockref
,
i_diff_date
,
i_drift
);
/* Wait a bit */
/* Wait a bit */
msleep
(
INTF_IDLE_SLEEP
);
msleep
(
INTF_IDLE_SLEEP
);
}
}
}
}
if
(
p_intf
->
p_sys
->
p_input
)
vlc_object_release
(
p_intf
->
p_sys
->
p_input
);
if
(
p_intf
->
p_sys
->
p_input
)
vlc_object_release
(
p_intf
->
p_sys
->
p_input
);
net_Close
(
i_socket
);
net_Close
(
i_socket
);
}
}
static
mtime_t
GetClockRef
(
intf_thread_t
*
p_intf
,
mtime_t
i_pts
)
static
mtime_t
GetClockRef
(
intf_thread_t
*
p_intf
,
mtime_t
i_pts
)
{
{
input_thread_t
*
p_input
=
p_intf
->
p_sys
->
p_input
;
input_thread_t
*
p_input
=
p_intf
->
p_sys
->
p_input
;
pgrm_descriptor_t
*
p_pgrm
;
pgrm_descriptor_t
*
p_pgrm
;
if
(
!
p_input
)
return
0
;
if
(
!
p_input
)
return
0
;
#if 0
#if 0
p_pgrm = p_input->stream.p_selected_program;
p_pgrm = p_input->stream.p_selected_program;
if( p_pgrm ) return input_ClockGetTS( p_input, p_pgrm, i_pts );
if( p_pgrm ) return input_ClockGetTS( p_input, p_pgrm, i_pts );
#else
#else
#warning "This code is currently broken. FIXME!!!"
#warning "This code is currently broken. FIXME!!!"
#endif
#endif
return
0
;
return
0
;
}
}
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment