Skip to content
Projects
Groups
Snippets
Help
Loading...
Help
Support
Keyboard shortcuts
?
Submit feedback
Contribute to GitLab
Sign in
Toggle navigation
V
vlc
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
Commits
dbc59e3a
Commit
dbc59e3a
authored
Jun 14, 2006
by
Yoann Peronneau
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
* Tree playlist XSPF export
Kids, don't try this at home...
parent
e955c93c
Changes
3
Show whitespace changes
Inline
Side-by-side
Showing
3 changed files
with
146 additions
and
108 deletions
+146
-108
modules/gui/wxwidgets/dialogs/playlist.cpp
modules/gui/wxwidgets/dialogs/playlist.cpp
+8
-5
modules/misc/playlist/xspf.c
modules/misc/playlist/xspf.c
+136
-102
modules/misc/playlist/xspf.h
modules/misc/playlist/xspf.h
+2
-1
No files found.
modules/gui/wxwidgets/dialogs/playlist.cpp
View file @
dbc59e3a
...
@@ -932,7 +932,7 @@ void Playlist::OnSave( wxCommandEvent& WXUNUSED(event) )
...
@@ -932,7 +932,7 @@ void Playlist::OnSave( wxCommandEvent& WXUNUSED(event) )
char
*
psz_desc
;
char
*
psz_desc
;
char
*
psz_filter
;
char
*
psz_filter
;
char
*
psz_module
;
char
*
psz_module
;
}
formats
[]
=
{{
_
(
"M3U file"
),
"*.m3u"
,
"export-m3u"
},
}
formats
[]
=
{
//
{ _("M3U file"), "*.m3u", "export-m3u" },
{
_
(
"XSPF playlist"
),
"*.xspf"
,
"export-xspf"
}
{
_
(
"XSPF playlist"
),
"*.xspf"
,
"export-xspf"
}
};
};
...
@@ -959,10 +959,13 @@ void Playlist::OnSave( wxCommandEvent& WXUNUSED(event) )
...
@@ -959,10 +959,13 @@ void Playlist::OnSave( wxCommandEvent& WXUNUSED(event) )
{
{
if
(
dialog
.
GetPath
().
mb_str
()
)
if
(
dialog
.
GetPath
().
mb_str
()
)
{
{
abort
();
/* what root should we export? */
// playlist_Export( p_playlist, dialog.GetPath().mb_str(),
if
(
p_playlist
->
p_root_category
->
i_children
>
0
)
// /* ROOT */
{
// formats[dialog.GetFilterIndex()].psz_module );
playlist_Export
(
p_playlist
,
dialog
.
GetPath
().
mb_str
(),
p_playlist
->
p_root_category
->
pp_children
[
0
],
formats
[
dialog
.
GetFilterIndex
()].
psz_module
);
}
}
}
}
}
...
...
modules/misc/playlist/xspf.c
View file @
dbc59e3a
/******************************************************************************
/******************************************************************************
* Copyright (C) 2006 Daniel Stränger <vlc at schmaller dot de>
* xspf.c : XSPF playlist export functions
******************************************************************************
* Copyright (C) 2006 the VideoLAN team
* $Id$
*
* Authors: Daniel Stränger <vlc at schmaller dot de>
* Yoann Peronneau <yoann@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
...
@@ -39,87 +45,59 @@ int E_(xspf_export_playlist)( vlc_object_t *p_this )
...
@@ -39,87 +45,59 @@ int E_(xspf_export_playlist)( vlc_object_t *p_this )
const
playlist_export_t
*
p_export
=
const
playlist_export_t
*
p_export
=
(
playlist_export_t
*
)
p_playlist
->
p_private
;
(
playlist_export_t
*
)
p_playlist
->
p_private
;
int
i
;
int
i
;
char
*
psz
;
char
*
psz_temp
;
char
*
psz_temp
;
playlist_item_t
**
pp_items
=
NULL
;
playlist_item_t
**
pp_items
=
NULL
;
int
i_size
;
int
i_size
;
playlist_item_t
*
p_node
;
playlist_item_t
*
p_node
=
p_export
->
p_root
;
int
i_count
;
/* write XSPF XML header - since we don't use <extension>,
/* write XSPF XML header */
* we get by with version 0 */
fprintf
(
p_export
->
p_file
,
"<?xml version=
\"
1.0
\"
encoding=
\"
UTF-8
\"
?>
\n
"
);
fprintf
(
p_export
->
p_file
,
"<?xml version=
\"
1.0
\"
encoding=
\"
UTF-8
\"
?>
\n
"
);
fprintf
(
p_export
->
p_file
,
fprintf
(
p_export
->
p_file
,
"<playlist version=
\"
0
\"
xmlns=
\"
http://xspf.org/ns/0/
\"
>
\n
"
);
"<playlist version=
\"
1
\"
xmlns=
\"
http://xspf.org/ns/0/
\"
>
\n
"
);
if
(
!
p_node
)
return
VLC_SUCCESS
;
/* save tho whole playlist or only the current node */
#define p_item p_playlist->status.p_item
if
(
p_item
)
{
if
(
p_item
->
p_parent
->
p_input
->
i_type
==
ITEM_TYPE_PLAYLIST
)
{
/* set the current node and its children */
p_node
=
p_item
->
p_parent
;
pp_items
=
p_node
->
pp_children
;
pp_items
=
p_node
->
pp_children
;
i_size
=
p_node
->
i_children
;
i_size
=
p_node
->
i_children
;
#undef p_item
/* save name of the playlist node */
/* save name of the playlist node */
psz_temp
=
convert_xml_special_chars
(
p_node
->
p_input
->
psz_name
);
psz_temp
=
convert_xml_special_chars
(
p_node
->
p_input
->
psz_name
);
if
(
*
psz_temp
)
if
(
*
psz_temp
)
fprintf
(
p_export
->
p_file
,
"
\t
<title>%s</title>
\n
"
,
psz_temp
);
free
(
psz_temp
);
/* save the creator of the playlist node */
psz
=
p_node
->
p_input
->
p_meta
->
psz_artist
?
strdup
(
p_node
->
p_input
->
p_meta
->
psz_artist
)
:
strdup
(
""
);
if
(
psz
&&
!*
psz
)
{
{
free
(
psz
);
fprintf
(
p_export
->
p_file
,
"
\t
<title>%s</title>
\n
"
,
psz_temp
);
psz
=
NULL
;
}
}
if
(
!
psz
)
psz
=
p_node
->
p_input
->
p_meta
->
psz_author
?
strdup
(
p_node
->
p_input
->
p_meta
->
psz_author
)
:
strdup
(
""
);
psz_temp
=
convert_xml_special_chars
(
psz
);
if
(
psz
)
free
(
psz
);
if
(
*
psz_temp
)
fprintf
(
p_export
->
p_file
,
"
\t
<creator>%s</creator>
\n
"
,
psz_temp
);
free
(
psz_temp
);
free
(
psz_temp
);
/* save location of the playlist node */
/* save location of the playlist node */
psz
=
assertUTF8URI
(
p_export
->
psz_filename
);
psz_temp
=
assertUTF8URI
(
p_export
->
psz_filename
);
if
(
psz
&&
*
psz
)
if
(
psz_temp
&&
*
psz_temp
)
{
{
fprintf
(
p_export
->
p_file
,
"
\t
<location>%s</location>
\n
"
,
fprintf
(
p_export
->
p_file
,
"
\t
<location>%s</location>
\n
"
,
psz_temp
);
psz
);
free
(
psz_temp
);
free
(
psz
);
}
}
}
}
/* prepare all the playlist children for export */
/* export all items in a flat format */
if
(
!
pp_items
)
fprintf
(
p_export
->
p_file
,
"
\t
<trackList>
\n
"
);
i_count
=
0
;
for
(
i
=
0
;
i
<
p_node
->
i_children
;
i
++
)
{
{
pp_items
=
p_playlist
->
pp_items
;
xspf_export_item
(
p_node
->
pp_children
[
i
],
p_export
->
p_file
,
i_size
=
p_playlist
->
i_size
;
&
i_count
)
;
}
}
fprintf
(
p_export
->
p_file
,
"
\t
</trackList>
\n
"
);
/* export all items */
/* export the tree structure in <extension> */
fprintf
(
p_export
->
p_file
,
"
\t
<trackList>
\n
"
);
fprintf
(
p_export
->
p_file
,
"
\t
<extension>
\n
"
);
for
(
i
=
0
;
i
<
i_size
;
i
++
)
i_count
=
0
;
for
(
i
=
0
;
i
<
p_node
->
i_children
;
i
++
)
{
{
xspf_export_item
(
pp_items
[
i
],
p_export
->
p_file
);
xspf_extension_item
(
p_node
->
pp_children
[
i
],
p_export
->
p_file
,
&
i_count
);
}
}
fprintf
(
p_export
->
p_file
,
"
\t
</extension>
\n
"
);
/* close the header elements */
/* close the header elements */
fprintf
(
p_export
->
p_file
,
"
\t
</trackList>
\n
"
);
fprintf
(
p_export
->
p_file
,
"</playlist>
\n
"
);
fprintf
(
p_export
->
p_file
,
"</playlist>
\n
"
);
return
VLC_SUCCESS
;
return
VLC_SUCCESS
;
...
@@ -129,25 +107,23 @@ int E_(xspf_export_playlist)( vlc_object_t *p_this )
...
@@ -129,25 +107,23 @@ int E_(xspf_export_playlist)( vlc_object_t *p_this )
* \brief exports one item to file or traverse if item is a node
* \brief exports one item to file or traverse if item is a node
* \param p_item playlist item to export
* \param p_item playlist item to export
* \param p_file file to write xml-converted item to
* \param p_file file to write xml-converted item to
* \param p_i_count counter for track identifiers
*/
*/
static
void
xspf_export_item
(
playlist_item_t
*
p_item
,
FILE
*
p_file
)
static
void
xspf_export_item
(
playlist_item_t
*
p_item
,
FILE
*
p_file
,
int
*
p_i_count
)
{
{
int
i
;
/**< iterator for all children if the current item is a node */
char
*
psz
;
char
*
psz
;
char
*
psz_temp
;
char
*
psz_temp
;
if
(
!
p_item
)
if
(
!
p_item
)
return
;
return
;
/** \todo only "flat" playlists supported at this time.
* extend to save the tree structure.
*/
/* if we get a node here, we must traverse it */
/* if we get a node here, we must traverse it */
if
(
p_item
->
i_children
>
0
)
if
(
p_item
->
i_children
>
0
)
{
{
for
(
i
=
0
;
i
<
p_item
->
i_children
;
i
++
)
int
i
;
for
(
i
=
0
;
i
<
p_item
->
i_children
;
i
++
)
{
{
xspf_export_item
(
p_item
->
pp_children
[
i
],
p_file
);
xspf_export_item
(
p_item
->
pp_children
[
i
],
p_file
,
p_i_count
);
}
}
return
;
return
;
}
}
...
@@ -155,8 +131,12 @@ static void xspf_export_item( playlist_item_t *p_item, FILE *p_file )
...
@@ -155,8 +131,12 @@ static void xspf_export_item( playlist_item_t *p_item, FILE *p_file )
/* leaves can be written directly */
/* leaves can be written directly */
fprintf
(
p_file
,
"
\t\t
<track>
\n
"
);
fprintf
(
p_file
,
"
\t\t
<track>
\n
"
);
/* print identifier and increase the counter */
fprintf
(
p_file
,
"
\t\t\t
<identifier>%d</identifier>
\n
"
,
*
p_i_count
);
(
*
p_i_count
)
++
;
/* -> the location */
/* -> the location */
if
(
p_item
->
p_input
->
psz_uri
&&
*
p_item
->
p_input
->
psz_uri
)
if
(
p_item
->
p_input
->
psz_uri
&&
*
p_item
->
p_input
->
psz_uri
)
{
{
psz
=
assertUTF8URI
(
p_item
->
p_input
->
psz_uri
);
psz
=
assertUTF8URI
(
p_item
->
p_input
->
psz_uri
);
fprintf
(
p_file
,
"
\t\t\t
<location>%s</location>
\n
"
,
psz
);
fprintf
(
p_file
,
"
\t\t\t
<location>%s</location>
\n
"
,
psz
);
...
@@ -164,33 +144,42 @@ static void xspf_export_item( playlist_item_t *p_item, FILE *p_file )
...
@@ -164,33 +144,42 @@ static void xspf_export_item( playlist_item_t *p_item, FILE *p_file )
}
}
/* -> the name/title (only if different from uri)*/
/* -> the name/title (only if different from uri)*/
if
(
p_item
->
p_input
->
psz_name
&&
if
(
p_item
->
p_input
->
psz_name
&&
p_item
->
p_input
->
psz_uri
&&
p_item
->
p_input
->
psz_uri
&&
strcmp
(
p_item
->
p_input
->
psz_uri
,
p_item
->
p_input
->
psz_name
)
)
strcmp
(
p_item
->
p_input
->
psz_uri
,
p_item
->
p_input
->
psz_name
)
)
{
{
psz_temp
=
convert_xml_special_chars
(
p_item
->
p_input
->
psz_name
);
psz_temp
=
convert_xml_special_chars
(
p_item
->
p_input
->
psz_name
);
if
(
*
psz_temp
)
if
(
*
psz_temp
)
fprintf
(
p_file
,
"
\t\t\t
<title>%s</title>
\n
"
,
psz_temp
);
fprintf
(
p_file
,
"
\t\t\t
<title>%s</title>
\n
"
,
psz_temp
);
free
(
psz_temp
);
free
(
psz_temp
);
}
}
if
(
p_item
->
p_input
->
p_meta
==
NULL
)
{
goto
xspfexportitem_end
;
}
/* -> the artist/creator */
/* -> the artist/creator */
psz
=
p_item
->
p_input
->
p_meta
->
psz_artist
?
psz
=
p_item
->
p_input
->
p_meta
->
psz_artist
?
strdup
(
p_item
->
p_input
->
p_meta
->
psz_artist
)
:
strdup
(
p_item
->
p_input
->
p_meta
->
psz_artist
)
:
strdup
(
""
);
strdup
(
""
);
if
(
psz
&&
!*
psz
)
if
(
psz
&&
!*
psz
)
{
{
free
(
psz
);
free
(
psz
);
psz
=
NULL
;
psz
=
NULL
;
}
}
if
(
!
psz
)
if
(
!
psz
)
{
psz
=
p_item
->
p_input
->
p_meta
->
psz_author
?
psz
=
p_item
->
p_input
->
p_meta
->
psz_author
?
strdup
(
p_item
->
p_input
->
p_meta
->
psz_author
)
:
strdup
(
p_item
->
p_input
->
p_meta
->
psz_author
)
:
strdup
(
""
);
strdup
(
""
);
}
psz_temp
=
convert_xml_special_chars
(
psz
);
psz_temp
=
convert_xml_special_chars
(
psz
);
if
(
psz
)
free
(
psz
);
if
(
psz
)
free
(
psz
);
if
(
*
psz_temp
)
if
(
*
psz_temp
)
{
fprintf
(
p_file
,
"
\t\t\t
<creator>%s</creator>
\n
"
,
psz_temp
);
fprintf
(
p_file
,
"
\t\t\t
<creator>%s</creator>
\n
"
,
psz_temp
);
}
free
(
psz_temp
);
free
(
psz_temp
);
/* -> the album */
/* -> the album */
...
@@ -198,24 +187,29 @@ static void xspf_export_item( playlist_item_t *p_item, FILE *p_file )
...
@@ -198,24 +187,29 @@ static void xspf_export_item( playlist_item_t *p_item, FILE *p_file )
strdup
(
p_item
->
p_input
->
p_meta
->
psz_album
)
:
strdup
(
p_item
->
p_input
->
p_meta
->
psz_album
)
:
strdup
(
""
);
strdup
(
""
);
psz_temp
=
convert_xml_special_chars
(
psz
);
psz_temp
=
convert_xml_special_chars
(
psz
);
if
(
psz
)
free
(
psz
);
if
(
psz
)
free
(
psz
);
if
(
*
psz_temp
)
if
(
*
psz_temp
)
{
fprintf
(
p_file
,
"
\t\t\t
<album>%s</album>
\n
"
,
psz_temp
);
fprintf
(
p_file
,
"
\t\t\t
<album>%s</album>
\n
"
,
psz_temp
);
}
free
(
psz_temp
);
free
(
psz_temp
);
/* -> the track number */
/* -> the track number */
psz
=
p_item
->
p_input
->
p_meta
->
psz_tracknum
?
psz
=
p_item
->
p_input
->
p_meta
->
psz_tracknum
?
strdup
(
p_item
->
p_input
->
p_meta
->
psz_tracknum
)
:
strdup
(
p_item
->
p_input
->
p_meta
->
psz_tracknum
)
:
strdup
(
""
);
strdup
(
""
);
if
(
psz
)
if
(
psz
)
{
if
(
*
psz
)
{
{
if
(
*
psz
)
fprintf
(
p_file
,
"
\t\t\t
<trackNum>%i</trackNum>
\n
"
,
atoi
(
psz
)
);
fprintf
(
p_file
,
"
\t\t\t
<trackNum>%i</trackNum>
\n
"
,
atoi
(
psz
)
);
}
free
(
psz
);
free
(
psz
);
}
}
xspfexportitem_end:
/* -> the duration */
/* -> the duration */
if
(
p_item
->
p_input
->
i_duration
>
0
)
if
(
p_item
->
p_input
->
i_duration
>
0
)
{
{
fprintf
(
p_file
,
"
\t\t\t
<duration>%ld</duration>
\n
"
,
fprintf
(
p_file
,
"
\t\t\t
<duration>%ld</duration>
\n
"
,
(
long
)(
p_item
->
p_input
->
i_duration
/
1000
)
);
(
long
)(
p_item
->
p_input
->
i_duration
/
1000
)
);
...
@@ -226,6 +220,43 @@ static void xspf_export_item( playlist_item_t *p_item, FILE *p_file )
...
@@ -226,6 +220,43 @@ static void xspf_export_item( playlist_item_t *p_item, FILE *p_file )
return
;
return
;
}
}
/**
* \brief exports one item in extension to file and traverse if item is a node
* \param p_item playlist item to export
* \param p_file file to write xml-converted item to
* \param p_i_count counter for track identifiers
*/
static
void
xspf_extension_item
(
playlist_item_t
*
p_item
,
FILE
*
p_file
,
int
*
p_i_count
)
{
if
(
!
p_item
)
return
;
/* if we get a node here, we must traverse it */
if
(
p_item
->
i_children
>
0
)
{
int
i
;
fprintf
(
p_file
,
"
\t\t
<node>
\n
"
);
fprintf
(
p_file
,
"
\t\t\t
<title>%s</title>
\n
"
,
p_item
->
p_input
->
psz_name
);
for
(
i
=
0
;
i
<
p_item
->
i_children
;
i
++
)
{
xspf_extension_item
(
p_item
->
pp_children
[
i
],
p_file
,
p_i_count
);
}
fprintf
(
p_file
,
"
\t\t
</node>
\n
"
);
return
;
}
/* print leaf and increase the counter */
fprintf
(
p_file
,
"
\t\t\t
<item href=
\"
%d
\"
/>
\n
"
,
*
p_i_count
);
(
*
p_i_count
)
++
;
return
;
}
/**
/**
* \param psz_name the location of the media ressource (e.g. local file,
* \param psz_name the location of the media ressource (e.g. local file,
* device, network stream, etc.)
* device, network stream, etc.)
...
@@ -239,7 +270,7 @@ static char *assertUTF8URI( char *psz_name )
...
@@ -239,7 +270,7 @@ static char *assertUTF8URI( char *psz_name )
char
*
psz_s
=
NULL
,
*
psz_d
=
NULL
;
/**< src & dest pointers for URI conversion */
char
*
psz_s
=
NULL
,
*
psz_d
=
NULL
;
/**< src & dest pointers for URI conversion */
vlc_bool_t
b_name_is_uri
=
VLC_FALSE
;
vlc_bool_t
b_name_is_uri
=
VLC_FALSE
;
if
(
!
psz_name
||
!*
psz_name
)
if
(
!
psz_name
||
!*
psz_name
)
return
NULL
;
return
NULL
;
/* check that string is valid UTF-8 */
/* check that string is valid UTF-8 */
...
@@ -250,11 +281,11 @@ static char *assertUTF8URI( char *psz_name )
...
@@ -250,11 +281,11 @@ static char *assertUTF8URI( char *psz_name )
/* max. 3x for URI conversion (percent escaping) and
/* max. 3x for URI conversion (percent escaping) and
8 bytes for "file://" and NULL-termination */
8 bytes for "file://" and NULL-termination */
psz_ret
=
(
char
*
)
malloc
(
sizeof
(
char
)
*
strlen
(
psz_name
)
*
6
*
3
+
8
);
psz_ret
=
(
char
*
)
malloc
(
sizeof
(
char
)
*
strlen
(
psz_name
)
*
6
*
3
+
8
);
if
(
!
psz_ret
)
if
(
!
psz_ret
)
return
NULL
;
return
NULL
;
/** \todo check for a valid scheme part preceding the colon */
/** \todo check for a valid scheme part preceding the colon */
if
(
strchr
(
psz_s
,
':'
)
)
if
(
strchr
(
psz_s
,
':'
)
)
{
{
psz_d
=
psz_ret
;
psz_d
=
psz_ret
;
b_name_is_uri
=
VLC_TRUE
;
b_name_is_uri
=
VLC_TRUE
;
...
@@ -266,10 +297,10 @@ static char *assertUTF8URI( char *psz_name )
...
@@ -266,10 +297,10 @@ static char *assertUTF8URI( char *psz_name )
psz_d
=
psz_ret
+
7
;
psz_d
=
psz_ret
+
7
;
}
}
while
(
*
psz_s
)
while
(
*
psz_s
)
{
{
/* percent-encode all non-ASCII and the XML special characters and the percent sign itself */
/* percent-encode all non-ASCII and the XML special characters and the percent sign itself */
if
(
*
psz_s
&
B10000000
||
if
(
*
psz_s
&
B10000000
||
*
psz_s
==
'<'
||
*
psz_s
==
'<'
||
*
psz_s
==
'>'
||
*
psz_s
==
'>'
||
*
psz_s
==
'&'
||
*
psz_s
==
'&'
||
...
@@ -279,8 +310,11 @@ static char *assertUTF8URI( char *psz_name )
...
@@ -279,8 +310,11 @@ static char *assertUTF8URI( char *psz_name )
*
psz_d
++
=
'%'
;
*
psz_d
++
=
'%'
;
*
psz_d
++
=
hexchars
[(
*
psz_s
>>
4
)
&
B00001111
];
*
psz_d
++
=
hexchars
[(
*
psz_s
>>
4
)
&
B00001111
];
*
psz_d
++
=
hexchars
[
*
psz_s
&
B00001111
];
*
psz_d
++
=
hexchars
[
*
psz_s
&
B00001111
];
}
else
}
else
{
*
psz_d
++
=
*
psz_s
;
*
psz_d
++
=
*
psz_s
;
}
psz_s
++
;
psz_s
++
;
}
}
...
...
modules/misc/playlist/xspf.h
View file @
dbc59e3a
...
@@ -33,5 +33,6 @@ const char hexchars[16] = "0123456789ABCDEF";
...
@@ -33,5 +33,6 @@ const char hexchars[16] = "0123456789ABCDEF";
/* prototypes */
/* prototypes */
int
E_
(
xspf_export_playlist
)(
vlc_object_t
*
);
int
E_
(
xspf_export_playlist
)(
vlc_object_t
*
);
static
void
xspf_export_item
(
playlist_item_t
*
,
FILE
*
);
static
void
xspf_export_item
(
playlist_item_t
*
,
FILE
*
,
int
*
);
static
void
xspf_extension_item
(
playlist_item_t
*
,
FILE
*
,
int
*
);
static
char
*
assertUTF8URI
(
char
*
);
static
char
*
assertUTF8URI
(
char
*
);
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