Skip to content
Projects
Groups
Snippets
Help
This project
Loading...
Sign in / Register
Toggle navigation
M
mpd
Project
Project
Details
Activity
Cycle Analytics
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Charts
Issues
0
Issues
0
List
Board
Labels
Milestones
Merge Requests
0
Merge Requests
0
CI / CD
CI / CD
Pipelines
Jobs
Schedules
Charts
Registry
Registry
Wiki
Wiki
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Charts
Create a new issue
Jobs
Commits
Issue Boards
Open sidebar
Иван Мажукин
mpd
Commits
a4a8ac0c
Commit
a4a8ac0c
authored
Jan 07, 2009
by
Max Kellermann
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
curl: use GQueue instead of dlist.h for buffer list
Get rid of the non-portable Linux list library, part II.
parent
fa503e31
Show whitespace changes
Inline
Side-by-side
Showing
1 changed file
with
137 additions
and
68 deletions
+137
-68
input_curl.c
src/input_curl.c
+137
-68
No files found.
src/input_curl.c
View file @
a4a8ac0c
...
@@ -18,7 +18,6 @@
...
@@ -18,7 +18,6 @@
#include "input_curl.h"
#include "input_curl.h"
#include "input_stream.h"
#include "input_stream.h"
#include "dlist.h"
#include "config.h"
#include "config.h"
#include "tag.h"
#include "tag.h"
#include "icy_metadata.h"
#include "icy_metadata.h"
...
@@ -38,8 +37,6 @@ static const off_t max_rewind_size = 64 * 1024;
...
@@ -38,8 +37,6 @@ static const off_t max_rewind_size = 64 * 1024;
* Buffers created by input_curl_writefunction().
* Buffers created by input_curl_writefunction().
*/
*/
struct
buffer
{
struct
buffer
{
struct
list_head
siblings
;
/** size of the payload */
/** size of the payload */
size_t
size
;
size_t
size
;
...
@@ -62,7 +59,7 @@ struct input_curl {
...
@@ -62,7 +59,7 @@ struct input_curl {
/** list of buffers, where input_curl_writefunction() appends
/** list of buffers, where input_curl_writefunction() appends
to, and input_curl_read() reads from them */
to, and input_curl_read() reads from them */
struct
list_head
buffers
;
GQueue
*
buffers
;
/** has something been added to the buffers list? */
/** has something been added to the buffers list? */
bool
buffered
;
bool
buffered
;
...
@@ -71,7 +68,7 @@ struct input_curl {
...
@@ -71,7 +68,7 @@ struct input_curl {
bool
eof
;
bool
eof
;
/** limited list of old buffers, for rewinding */
/** limited list of old buffers, for rewinding */
struct
list_head
rewind
;
GQueue
*
rewind
;
/** error message provided by libcurl */
/** error message provided by libcurl */
char
error
[
CURL_ERROR_SIZE
];
char
error
[
CURL_ERROR_SIZE
];
...
@@ -107,6 +104,16 @@ void input_curl_global_finish(void)
...
@@ -107,6 +104,16 @@ void input_curl_global_finish(void)
curl_global_cleanup
();
curl_global_cleanup
();
}
}
static
void
buffer_free_callback
(
gpointer
data
,
G_GNUC_UNUSED
gpointer
user_data
)
{
struct
buffer
*
buffer
=
data
;
assert
(
buffer
->
consumed
<=
buffer
->
size
);
g_free
(
data
);
}
/**
/**
* Frees the current "libcurl easy" handle, and everything associated
* Frees the current "libcurl easy" handle, and everything associated
* with it.
* with it.
...
@@ -126,18 +133,12 @@ input_curl_easy_free(struct input_curl *c)
...
@@ -126,18 +133,12 @@ input_curl_easy_free(struct input_curl *c)
g_free
(
c
->
range
);
g_free
(
c
->
range
);
c
->
range
=
NULL
;
c
->
range
=
NULL
;
while
(
!
list_empty
(
&
c
->
buffers
))
{
g_queue_foreach
(
c
->
buffers
,
buffer_free_callback
,
NULL
);
struct
buffer
*
buffer
=
(
struct
buffer
*
)
c
->
buffers
.
next
;
g_queue_clear
(
c
->
buffers
);
list_del
(
&
buffer
->
siblings
);
g_free
(
buffer
);
if
(
c
->
rewind
!=
NULL
)
{
}
g_queue_foreach
(
c
->
rewind
,
buffer_free_callback
,
NULL
);
g_queue_clear
(
c
->
rewind
);
while
(
!
list_empty
(
&
c
->
rewind
))
{
struct
buffer
*
buffer
=
(
struct
buffer
*
)
c
->
rewind
.
next
;
list_del
(
&
buffer
->
siblings
);
g_free
(
buffer
);
}
}
}
}
...
@@ -158,6 +159,10 @@ input_curl_free(struct input_stream *is)
...
@@ -158,6 +159,10 @@ input_curl_free(struct input_stream *is)
if
(
c
->
multi
!=
NULL
)
if
(
c
->
multi
!=
NULL
)
curl_multi_cleanup
(
c
->
multi
);
curl_multi_cleanup
(
c
->
multi
);
g_queue_free
(
c
->
buffers
);
if
(
c
->
rewind
!=
NULL
)
g_queue_free
(
c
->
rewind
);
g_free
(
c
->
url
);
g_free
(
c
->
url
);
g_free
(
c
);
g_free
(
c
);
}
}
...
@@ -236,33 +241,33 @@ input_curl_select(struct input_curl *c)
...
@@ -236,33 +241,33 @@ input_curl_select(struct input_curl *c)
/**
/**
* Mark a part of the buffer object as consumed.
* Mark a part of the buffer object as consumed.
*/
*/
static
void
static
struct
buffer
*
consume_buffer
(
struct
buffer
*
buffer
,
size_t
length
,
consume_buffer
(
struct
buffer
*
buffer
,
size_t
length
,
GQueue
*
rewind_buffers
)
struct
list_head
*
rewind_head
)
{
{
assert
(
buffer
!=
NULL
);
assert
(
buffer
!=
NULL
);
assert
(
buffer
->
consumed
<
buffer
->
size
);
assert
(
buffer
->
consumed
<
buffer
->
size
);
buffer
->
consumed
+=
length
;
buffer
->
consumed
+=
length
;
if
(
buffer
->
consumed
<
buffer
->
size
)
if
(
buffer
->
consumed
<
buffer
->
size
)
return
;
return
buffer
;
assert
(
buffer
->
consumed
==
buffer
->
size
);
assert
(
buffer
->
consumed
==
buffer
->
size
);
list_del
(
&
buffer
->
siblings
);
if
(
rewind_buffers
!=
NULL
)
if
(
rewind_head
!=
NULL
)
/* append this buffer to the rewind buffer list */
/* append this buffer to the rewind buffer list */
list_add_tail
(
&
buffer
->
siblings
,
rewind_head
);
g_queue_push_tail
(
rewind_buffers
,
buffer
);
else
else
g_free
(
buffer
);
g_free
(
buffer
);
return
NULL
;
}
}
static
size_t
static
size_t
read_from_buffer
(
struct
icy_metadata
*
icy_metadata
,
struct
buffer
*
buffer
,
read_from_buffer
(
struct
icy_metadata
*
icy_metadata
,
GQueue
*
buffers
,
void
*
dest0
,
size_t
length
,
void
*
dest0
,
size_t
length
,
struct
list_head
*
rewind_head
)
GQueue
*
rewind_buffers
)
{
{
struct
buffer
*
buffer
=
g_queue_pop_head
(
buffers
);
uint8_t
*
dest
=
dest0
;
uint8_t
*
dest
=
dest0
;
size_t
nbytes
=
0
;
size_t
nbytes
=
0
;
...
@@ -279,7 +284,7 @@ read_from_buffer(struct icy_metadata *icy_metadata, struct buffer *buffer,
...
@@ -279,7 +284,7 @@ read_from_buffer(struct icy_metadata *icy_metadata, struct buffer *buffer,
if
(
chunk
>
0
)
{
if
(
chunk
>
0
)
{
memcpy
(
dest
,
buffer
->
data
+
buffer
->
consumed
,
memcpy
(
dest
,
buffer
->
data
+
buffer
->
consumed
,
chunk
);
chunk
);
consume_buffer
(
buffer
,
chunk
,
rewind_head
);
buffer
=
consume_buffer
(
buffer
,
chunk
,
rewind_buffers
);
nbytes
+=
chunk
;
nbytes
+=
chunk
;
dest
+=
chunk
;
dest
+=
chunk
;
...
@@ -287,20 +292,27 @@ read_from_buffer(struct icy_metadata *icy_metadata, struct buffer *buffer,
...
@@ -287,20 +292,27 @@ read_from_buffer(struct icy_metadata *icy_metadata, struct buffer *buffer,
if
(
length
==
0
)
if
(
length
==
0
)
break
;
break
;
assert
(
buffer
!=
NULL
);
}
}
chunk
=
icy_meta
(
icy_metadata
,
buffer
->
data
+
buffer
->
consumed
,
chunk
=
icy_meta
(
icy_metadata
,
buffer
->
data
+
buffer
->
consumed
,
length
);
length
);
if
(
chunk
>
0
)
{
if
(
chunk
>
0
)
{
consume_buffer
(
buffer
,
chunk
,
rewind_head
);
buffer
=
consume_buffer
(
buffer
,
chunk
,
rewind_buffers
);
length
-=
chunk
;
length
-=
chunk
;
if
(
length
==
0
)
if
(
length
==
0
)
break
;
break
;
assert
(
buffer
!=
NULL
);
}
}
}
}
if
(
buffer
!=
NULL
)
g_queue_push_head
(
buffers
,
buffer
);
return
nbytes
;
return
nbytes
;
}
}
...
@@ -326,13 +338,34 @@ input_curl_read(struct input_stream *is, void *ptr, size_t size)
...
@@ -326,13 +338,34 @@ input_curl_read(struct input_stream *is, void *ptr, size_t size)
{
{
struct
input_curl
*
c
=
is
->
data
;
struct
input_curl
*
c
=
is
->
data
;
CURLMcode
mcode
=
CURLM_CALL_MULTI_PERFORM
;
CURLMcode
mcode
=
CURLM_CALL_MULTI_PERFORM
;
struct
list_head
*
rewind_head
;
GQueue
*
rewind_buffers
;
size_t
nbytes
=
0
;
size_t
nbytes
=
0
;
char
*
dest
=
ptr
;
char
*
dest
=
ptr
;
#ifndef NDEBUG
if
(
c
->
rewind
!=
NULL
&&
(
!
g_queue_is_empty
(
c
->
rewind
)
||
is
->
offset
==
0
))
{
off_t
offset
=
0
;
struct
buffer
*
buffer
;
for
(
GList
*
list
=
g_queue_peek_head_link
(
c
->
rewind
);
list
!=
NULL
;
list
=
g_list_next
(
list
))
{
buffer
=
list
->
data
;
offset
+=
buffer
->
consumed
;
assert
(
offset
<=
is
->
offset
);
}
buffer
=
g_queue_peek_head
(
c
->
buffers
);
if
(
buffer
!=
NULL
)
offset
+=
buffer
->
consumed
;
assert
(
offset
==
is
->
offset
);
}
#endif
/* fill the buffer */
/* fill the buffer */
while
(
!
c
->
eof
&&
list_empty
(
&
c
->
buffers
))
{
while
(
!
c
->
eof
&&
g_queue_is_empty
(
c
->
buffers
))
{
int
running_handles
;
int
running_handles
;
bool
bret
;
bool
bret
;
...
@@ -360,19 +393,19 @@ input_curl_read(struct input_stream *is, void *ptr, size_t size)
...
@@ -360,19 +393,19 @@ input_curl_read(struct input_stream *is, void *ptr, size_t size)
/* send buffer contents */
/* send buffer contents */
if
(
!
list_empty
(
&
c
->
rewind
)
||
is
->
offset
==
0
)
if
(
c
->
rewind
!=
NULL
&&
(
!
g_queue_is_empty
(
c
->
rewind
)
||
is
->
offset
==
0
))
/* at the beginning or already writing the rewind
/* at the beginning or already writing the rewind
buffer list */
buffer list */
rewind_
head
=
&
c
->
rewind
;
rewind_
buffers
=
c
->
rewind
;
else
else
/* we don't need the rewind buffers anymore */
/* we don't need the rewind buffers anymore */
rewind_
head
=
NULL
;
rewind_
buffers
=
NULL
;
while
(
size
>
0
&&
!
list_empty
(
&
c
->
buffers
))
{
while
(
size
>
0
&&
!
g_queue_is_empty
(
c
->
buffers
))
{
struct
buffer
*
buffer
=
(
struct
buffer
*
)
c
->
buffers
.
next
;
size_t
copy
=
read_from_buffer
(
&
c
->
icy_metadata
,
c
->
buffers
,
size_t
copy
=
read_from_buffer
(
&
c
->
icy_metadata
,
buffer
,
dest
+
nbytes
,
size
,
dest
+
nbytes
,
size
,
rewind_
head
);
rewind_
buffers
);
nbytes
+=
copy
;
nbytes
+=
copy
;
size
-=
copy
;
size
-=
copy
;
...
@@ -383,16 +416,31 @@ input_curl_read(struct input_stream *is, void *ptr, size_t size)
...
@@ -383,16 +416,31 @@ input_curl_read(struct input_stream *is, void *ptr, size_t size)
is
->
offset
+=
(
off_t
)
nbytes
;
is
->
offset
+=
(
off_t
)
nbytes
;
if
(
rewind_head
!=
NULL
&&
is
->
offset
>
max_rewind_size
)
{
#ifndef NDEBUG
/* drop the rewind buffer, it has grown too large */
if
(
rewind_buffers
!=
NULL
)
{
off_t
offset
=
0
;
struct
buffer
*
buffer
;
while
(
!
list_empty
(
&
c
->
rewind
))
{
for
(
GList
*
list
=
g_queue_peek_head_link
(
c
->
rewind
);
struct
buffer
*
buffer
=
list
!=
NULL
;
list
=
g_list_next
(
list
))
{
(
struct
buffer
*
)
c
->
rewind
.
next
;
buffer
=
list
->
data
;
list_del
(
&
buffer
->
siblings
);
offset
+=
buffer
->
consumed
;
assert
(
offset
<=
is
->
offset
);
}
g_free
(
buffer
);
buffer
=
g_queue_peek_head
(
c
->
buffers
);
if
(
buffer
!=
NULL
)
offset
+=
buffer
->
consumed
;
assert
(
offset
==
is
->
offset
);
}
}
#endif
if
(
rewind_buffers
!=
NULL
&&
is
->
offset
>
max_rewind_size
)
{
/* drop the rewind buffer, it has grown too large */
g_queue_foreach
(
c
->
rewind
,
buffer_free_callback
,
NULL
);
g_queue_clear
(
c
->
rewind
);
}
}
return
nbytes
;
return
nbytes
;
...
@@ -409,7 +457,7 @@ input_curl_eof(G_GNUC_UNUSED struct input_stream *is)
...
@@ -409,7 +457,7 @@ input_curl_eof(G_GNUC_UNUSED struct input_stream *is)
{
{
struct
input_curl
*
c
=
is
->
data
;
struct
input_curl
*
c
=
is
->
data
;
return
c
->
eof
&&
list_empty
(
&
c
->
buffers
);
return
c
->
eof
&&
g_queue_is_empty
(
c
->
buffers
);
}
}
static
int
static
int
...
@@ -424,7 +472,8 @@ input_curl_buffer(struct input_stream *is)
...
@@ -424,7 +472,8 @@ input_curl_buffer(struct input_stream *is)
do
{
do
{
mcode
=
curl_multi_perform
(
c
->
multi
,
&
running_handles
);
mcode
=
curl_multi_perform
(
c
->
multi
,
&
running_handles
);
}
while
(
mcode
==
CURLM_CALL_MULTI_PERFORM
&&
list_empty
(
&
c
->
buffers
));
}
while
(
mcode
==
CURLM_CALL_MULTI_PERFORM
&&
g_queue_is_empty
(
c
->
buffers
));
if
(
mcode
!=
CURLM_OK
&&
mcode
!=
CURLM_CALL_MULTI_PERFORM
)
{
if
(
mcode
!=
CURLM_OK
&&
mcode
!=
CURLM_CALL_MULTI_PERFORM
)
{
g_warning
(
"curl_multi_perform() failed: %s
\n
"
,
g_warning
(
"curl_multi_perform() failed: %s
\n
"
,
...
@@ -519,6 +568,15 @@ input_curl_headerfunction(void *ptr, size_t size, size_t nmemb, void *stream)
...
@@ -519,6 +568,15 @@ input_curl_headerfunction(void *ptr, size_t size, size_t nmemb, void *stream)
/* a stream with icy-metadata is not
/* a stream with icy-metadata is not
seekable */
seekable */
is
->
seekable
=
false
;
is
->
seekable
=
false
;
if
(
c
->
rewind
!=
NULL
)
{
/* rewinding with icy-metadata is too
hairy for me .. */
assert
(
g_queue_is_empty
(
c
->
rewind
));
g_queue_free
(
c
->
rewind
);
c
->
rewind
=
NULL
;
}
}
}
}
}
...
@@ -541,7 +599,7 @@ input_curl_writefunction(void *ptr, size_t size, size_t nmemb, void *stream)
...
@@ -541,7 +599,7 @@ input_curl_writefunction(void *ptr, size_t size, size_t nmemb, void *stream)
buffer
->
size
=
size
;
buffer
->
size
=
size
;
buffer
->
consumed
=
0
;
buffer
->
consumed
=
0
;
memcpy
(
buffer
->
data
,
ptr
,
size
);
memcpy
(
buffer
->
data
,
ptr
,
size
);
list_add_tail
(
&
buffer
->
siblings
,
&
c
->
buffers
);
g_queue_push_tail
(
c
->
buffers
,
buffer
);
c
->
buffered
=
true
;
c
->
buffered
=
true
;
is
->
ready
=
true
;
is
->
ready
=
true
;
...
@@ -619,18 +677,21 @@ input_curl_can_rewind(struct input_stream *is)
...
@@ -619,18 +677,21 @@ input_curl_can_rewind(struct input_stream *is)
struct
input_curl
*
c
=
is
->
data
;
struct
input_curl
*
c
=
is
->
data
;
struct
buffer
*
buffer
;
struct
buffer
*
buffer
;
if
(
!
list_empty
(
&
c
->
rewind
))
if
(
c
->
rewind
==
NULL
)
return
false
;
if
(
!
g_queue_is_empty
(
c
->
rewind
))
/* the rewind buffer hasn't been wiped yet */
/* the rewind buffer hasn't been wiped yet */
return
true
;
return
true
;
if
(
list_empty
(
&
c
->
buffers
))
if
(
g_queue_is_empty
(
c
->
buffers
))
/* there are no buffers at all - cheap rewind not
/* there are no buffers at all - cheap rewind not
possible */
possible */
return
false
;
return
false
;
/* rewind is possible if this is the very first buffer of the
/* rewind is possible if this is the very first buffer of the
resource */
resource */
buffer
=
(
struct
buffer
*
)
c
->
buffers
.
next
;
buffer
=
(
struct
buffer
*
)
g_queue_peek_head
(
c
->
buffers
)
;
return
(
off_t
)
buffer
->
consumed
==
is
->
offset
;
return
(
off_t
)
buffer
->
consumed
==
is
->
offset
;
}
}
...
@@ -638,35 +699,37 @@ static void
...
@@ -638,35 +699,37 @@ static void
input_curl_rewind
(
struct
input_stream
*
is
)
input_curl_rewind
(
struct
input_stream
*
is
)
{
{
struct
input_curl
*
c
=
is
->
data
;
struct
input_curl
*
c
=
is
->
data
;
struct
buffer
*
buffer
;
#ifndef NDEBUG
#ifndef NDEBUG
off_t
offset
=
0
;
off_t
offset
=
0
;
#endif
#endif
/* reset all rewind buffers */
assert
(
c
->
rewind
!=
NULL
);
/* rewind the current buffer */
list_for_each_entry
(
buffer
,
&
c
->
rewind
,
siblings
)
{
if
(
!
g_queue_is_empty
(
c
->
buffers
))
{
struct
buffer
*
buffer
=
(
struct
buffer
*
)
g_queue_peek_head
(
c
->
buffers
);
#ifndef NDEBUG
#ifndef NDEBUG
offset
+=
buffer
->
consumed
;
offset
+=
buffer
->
consumed
;
#endif
#endif
buffer
->
consumed
=
0
;
buffer
->
consumed
=
0
;
}
}
/* re
wind the current buffer
*/
/* re
set and move all rewind buffers back to the regular buffer list
*/
if
(
!
list_empty
(
&
c
->
buffers
))
{
while
(
!
g_queue_is_empty
(
c
->
rewind
))
{
buffer
=
(
struct
buffer
*
)
c
->
buffers
.
next
;
struct
buffer
*
buffer
=
(
struct
buffer
*
)
g_queue_pop_tail
(
c
->
rewind
);
#ifndef NDEBUG
#ifndef NDEBUG
offset
+=
buffer
->
consumed
;
offset
+=
buffer
->
consumed
;
#endif
#endif
buffer
->
consumed
=
0
;
buffer
->
consumed
=
0
;
g_queue_push_head
(
c
->
buffers
,
buffer
);
}
}
assert
(
offset
==
is
->
offset
);
assert
(
offset
==
is
->
offset
);
/* move all rewind buffers back to the regular buffer list */
list_splice_init
(
&
c
->
rewind
,
&
c
->
buffers
);
is
->
offset
=
0
;
is
->
offset
=
0
;
/* rewind the icy_metadata object */
/* rewind the icy_metadata object */
...
@@ -722,24 +785,30 @@ input_curl_seek(struct input_stream *is, off_t offset, int whence)
...
@@ -722,24 +785,30 @@ input_curl_seek(struct input_stream *is, off_t offset, int whence)
/* check if we can fast-forward the buffer */
/* check if we can fast-forward the buffer */
while
(
offset
>
is
->
offset
&&
!
list_empty
(
&
c
->
buffers
))
{
while
(
offset
>
is
->
offset
&&
!
g_queue_is_empty
(
c
->
buffers
))
{
struct
list_head
*
rewind_head
;
GQueue
*
rewind_buffers
;
struct
buffer
*
buffer
=
(
struct
buffer
*
)
c
->
buffers
.
next
;
struct
buffer
*
buffer
;
size_t
length
;
size_t
length
;
if
(
!
list_empty
(
&
c
->
rewind
)
||
is
->
offset
==
0
)
if
(
c
->
rewind
!=
NULL
&&
(
!
g_queue_is_empty
(
c
->
rewind
)
||
is
->
offset
==
0
))
/* at the beginning or already writing the rewind
/* at the beginning or already writing the rewind
buffer list */
buffer list */
rewind_
head
=
&
c
->
rewind
;
rewind_
buffers
=
c
->
rewind
;
else
else
/* we don't need the rewind buffers anymore */
/* we don't need the rewind buffers anymore */
rewind_head
=
NULL
;
rewind_buffers
=
NULL
;
buffer
=
(
struct
buffer
*
)
g_queue_pop_head
(
c
->
buffers
);
length
=
buffer
->
size
-
buffer
->
consumed
;
length
=
buffer
->
size
-
buffer
->
consumed
;
if
(
offset
-
is
->
offset
<
(
off_t
)
length
)
if
(
offset
-
is
->
offset
<
(
off_t
)
length
)
length
=
offset
-
is
->
offset
;
length
=
offset
-
is
->
offset
;
consume_buffer
(
buffer
,
length
,
rewind_head
);
buffer
=
consume_buffer
(
buffer
,
length
,
rewind_buffers
);
if
(
buffer
!=
NULL
)
g_queue_push_head
(
c
->
buffers
,
buffer
);
is
->
offset
+=
length
;
is
->
offset
+=
length
;
}
}
...
@@ -788,8 +857,8 @@ input_curl_open(struct input_stream *is, const char *url)
...
@@ -788,8 +857,8 @@ input_curl_open(struct input_stream *is, const char *url)
c
=
g_new0
(
struct
input_curl
,
1
);
c
=
g_new0
(
struct
input_curl
,
1
);
c
->
url
=
g_strdup
(
url
);
c
->
url
=
g_strdup
(
url
);
INIT_LIST_HEAD
(
&
c
->
buffers
);
c
->
buffers
=
g_queue_new
(
);
INIT_LIST_HEAD
(
&
c
->
rewind
);
c
->
rewind
=
g_queue_new
(
);
is
->
data
=
c
;
is
->
data
=
c
;
...
...
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