Skip to content
Projects
Groups
Snippets
Help
Loading...
Help
Submit feedback
Contribute to GitLab
Sign in
Toggle navigation
K
kicad-source-mirror
Project
Project
Details
Activity
Releases
Cycle Analytics
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Charts
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Charts
Commits
Open sidebar
Elphel
kicad-source-mirror
Commits
8384d7e0
Commit
8384d7e0
authored
Dec 20, 2010
by
Dick Hollenbeck
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
implement some of the DIR_LIB_SOURCE Read*() functions
parent
80f21358
Changes
7
Hide whitespace changes
Inline
Side-by-side
Showing
7 changed files
with
292 additions
and
74 deletions
+292
-74
CMakeLists.txt
CMakeLists.txt
+1
-0
CMakeLists.txt
new/CMakeLists.txt
+3
-2
design.h
new/design.h
+0
-15
sch_dir_lib_source.cpp
new/sch_dir_lib_source.cpp
+221
-32
sch_dir_lib_source.h
new/sch_dir_lib_source.h
+26
-19
sch_lib.h
new/sch_lib.h
+6
-6
toolchain-mingw.cmake
new/toolchain-mingw.cmake
+35
-0
No files found.
CMakeLists.txt
View file @
8384d7e0
...
...
@@ -212,6 +212,7 @@ add_subdirectory(polygon)
add_subdirectory
(
polygon/kbool/src
)
add_subdirectory
(
potrace
)
add_subdirectory
(
bitmap2component
)
#add_subdirectory(new)
#############
# Resources #
...
...
new/CMakeLists.txt
View file @
8384d7e0
...
...
@@ -56,10 +56,11 @@ else(DOXYGEN_FOUND)
endif
()
include_directories
(
${
CMAKE_SOURCE_DIR
}
)
include_directories
(
${
CMAKE_
CURRENT_
SOURCE_DIR
}
)
add_executable
(
test_dir_lib_source sch_dir_lib_source.cpp
)
add_executable
(
test_dir_lib_source sch_dir_lib_source.cpp
${
PROJECT_SOURCE_DIR
}
/common/richio.cpp
)
#add_executable( test_dir_lib_source EXCLUDE_FROM_ALL sch_dir_lib_source.cpp )
target_link_libraries
(
test_dir_lib_source
${
wxWidgets_LIBRARIES
}
)
#target_link_libraries( test_dir_lib_source common ${wxWidgets_LIBRARIES} )
new/design.h
View file @
8384d7e0
...
...
@@ -352,21 +352,6 @@ Show architecture here.
*/
typedef
std
::
string
STRING
;
/**
* Type STRING_TOKS
* documents a container which holds a sequence of s-expressions suitable for parsing
* with DSNLEXER. This can either be a sequence of DSN_SYMBOLs or a sequence of
* fully parenthesis delimited s-expressions. There are 2 types: <ol>
* <li> R C R33 "quoted-name" J2
* <li> (part R ())(part C ())
* </ol>
* Notice that in the 1st example, there are 5 tokens in sequence, and in the
* 2nd example there are two top most s-expressions in sequence. So the counts
* in these are 5 and 2 respectively.
*/
typedef
std
::
dequeue
<
STRING
>
STRING_TOKS
;
typedef
std
::
dequeue
<
STRING
>
STRINGS
;
//typedef std::vector<wxString> WSTRINGS;
...
...
new/sch_dir_lib_source.cpp
View file @
8384d7e0
...
...
@@ -34,8 +34,13 @@
http://www.softagalleria.net/dirent.php
wx has these but they are based on wxString which can be wchar_t based and wx should
not be introduced at a level this low.
Part files: have the general form partname.part[.revN...]
Categories: are any subdirectories immediately below the sourceURI, one level only.
Part names: [category/]partname[/revN...]
*/
#include <sch_dir_lib_source.h>
using
namespace
SCH
;
...
...
@@ -60,7 +65,7 @@ using namespace std;
/**
* Class DIR_WRAP
* provides a destructor which
may be
invoked if an exception is thrown.
* provides a destructor which
is
invoked if an exception is thrown.
*/
class
DIR_WRAP
{
...
...
@@ -77,12 +82,13 @@ public:
DIR
*
operator
->
()
{
return
dir
;
}
DIR
*
operator
*
()
{
return
dir
;
}
operator
bool
()
{
return
dir
!=
0
;
}
};
/**
* Class FILE_WRAP
* provides a destructor which
may be
invoked if an exception is thrown.
* provides a destructor which
is
invoked if an exception is thrown.
*/
class
FILE_WRAP
{
...
...
@@ -119,24 +125,30 @@ static const char* strrstr( const char* haystack, const char* needle )
return
ret
;
}
static
const
char
*
endsWithRev
(
const
char
*
cp
,
const
char
*
limit
)
/**
* Function endsWithRev
* returns a pointer to the final string segment: "revN..." or NULL if none.
* @param start is the beginning of string segment to test, the partname or
* any middle portion of it.
* @param tail is a pointer to the terminating nul.
* @param separator is the separating byte, expected: '.' or '/', depending on context.
*/
static
const
char
*
endsWithRev
(
const
char
*
start
,
const
char
*
tail
,
char
separator
)
{
// find last instance of ".rev"
cp
=
strrstr
(
cp
,
".rev"
);
if
(
cp
)
{
const
char
*
rev
=
cp
+
1
;
cp
+=
sizeof
(
".rev"
)
-
1
;
bool
sawDigit
=
false
;
while
(
isdigit
(
*
cp
)
)
++
cp
;
if
(
cp
!=
limit
)
// there is garbage after "revN.."
rev
=
0
;
while
(
isdigit
(
*--
tail
)
&&
tail
>
start
)
{
sawDigit
=
true
;
}
return
rev
;
if
(
sawDigit
&&
tail
-
3
>=
start
&&
tail
[
-
3
]
==
separator
)
{
tail
-=
2
;
if
(
tail
[
0
]
==
'r'
&&
tail
[
1
]
==
'e'
&&
tail
[
2
]
==
'v'
)
{
return
tail
;
}
}
return
0
;
...
...
@@ -168,7 +180,7 @@ bool DIR_LIB_SOURCE::makePartFileName( const char* aEntry,
// if versioning, test for a trailing "revN.." type of string
if
(
useVersioning
)
{
const
char
*
rev
=
endsWithRev
(
cp
+
sizeof
(
".part"
)
-
1
,
limit
);
const
char
*
rev
=
endsWithRev
(
cp
+
sizeof
(
".part"
)
-
1
,
limit
,
'.'
);
if
(
rev
)
{
if
(
aCategory
.
size
()
)
...
...
@@ -193,10 +205,51 @@ static bool isCategoryName( const char* aName )
}
#define MAX_PART_FILE_SIZE (1*1024*1024) // sanity check
void
DIR_LIB_SOURCE
::
readSExpression
(
STRING
*
aResult
,
const
STRING
&
aFilename
)
throw
(
IO_ERROR
)
{
FILE_WRAP
fw
=
open
(
aFilename
.
c_str
(),
O_RDONLY
);
if
(
fw
==
-
1
)
{
STRING
msg
=
aFilename
;
msg
+=
" cannot be open()ed for reading"
;
throw
IO_ERROR
(
msg
.
c_str
()
);
}
DIR_LIB_SOURCE
::
DIR_LIB_SOURCE
(
const
STRING
&
aDirectoryPath
,
bool
doUseVersioning
)
throw
(
IO_ERROR
)
struct
stat
fs
;
fstat
(
fw
,
&
fs
);
// sanity check on file size
if
(
fs
.
st_size
>
(
1
*
1024
*
1024
)
)
{
STRING
msg
=
aFilename
;
msg
+=
" seems too big. ( > 1mbyte )"
;
throw
IO_ERROR
(
msg
.
c_str
()
);
}
// we reuse the same readBuffer, which is not thread safe, but the API
// is not expected to be thread safe.
readBuffer
.
resize
(
fs
.
st_size
);
size_t
count
=
read
(
fw
,
&
readBuffer
[
0
],
fs
.
st_size
);
if
(
count
!=
(
size_t
)
fs
.
st_size
)
{
STRING
msg
=
aFilename
;
msg
+=
" cannot be read"
;
throw
IO_ERROR
(
msg
.
c_str
()
);
}
// std::string chars are not gauranteed to be contiguous in
// future implementations of C++, so this is why we did not read into
// aResult directly.
aResult
->
assign
(
&
readBuffer
[
0
],
count
);
}
DIR_LIB_SOURCE
::
DIR_LIB_SOURCE
(
const
STRING
&
aDirectoryPath
,
bool
doUseVersioning
)
throw
(
IO_ERROR
)
:
readBuffer
(
512
)
{
useVersioning
=
doUseVersioning
;
sourceURI
=
aDirectoryPath
;
...
...
@@ -225,19 +278,121 @@ DIR_LIB_SOURCE::~DIR_LIB_SOURCE()
}
void
DIR_LIB_SOURCE
::
GetCategoricalPartNames
(
STRINGS
*
aResults
,
const
STRING
&
aCategory
)
throw
(
IO_ERROR
)
{
aResults
->
clear
();
if
(
aCategory
.
size
()
)
{
STRING
lower
=
aCategory
+
"/"
;
STRING
upper
=
aCategory
+
char
(
'/'
+
1
);
DIR_CACHE
::
const_iterator
limit
=
sweets
.
upper_bound
(
upper
);
for
(
DIR_CACHE
::
const_iterator
it
=
sweets
.
lower_bound
(
lower
);
it
!=
limit
;
++
it
)
{
const
char
*
start
=
it
->
first
.
c_str
();
size_t
len
=
it
->
first
.
size
();
if
(
!
endsWithRev
(
start
,
start
+
len
,
'/'
)
)
aResults
->
push_back
(
it
->
first
);
}
}
else
{
for
(
DIR_CACHE
::
const_iterator
it
=
sweets
.
begin
();
it
!=
sweets
.
end
();
++
it
)
{
const
char
*
start
=
it
->
first
.
c_str
();
size_t
len
=
it
->
first
.
size
();
if
(
!
endsWithRev
(
start
,
start
+
len
,
'/'
)
)
aResults
->
push_back
(
it
->
first
);
}
}
}
void
DIR_LIB_SOURCE
::
ReadPart
(
STRING
*
aResult
,
const
STRING
&
aPartName
,
const
STRING
&
aRev
)
throw
(
IO_ERROR
)
{
STRING
partname
=
aPartName
;
if
(
aRev
.
size
()
)
partname
+=
"/"
+
aRev
;
DIR_CACHE
::
iterator
it
=
sweets
.
find
(
partname
);
if
(
it
==
sweets
.
end
()
)
// part not found
{
partname
+=
" not found."
;
throw
IO_ERROR
(
partname
.
c_str
()
);
}
if
(
!
it
->
second
)
// if the sweet string is not loaded yet
{
STRING
filename
=
sourceURI
+
"/"
+
aPartName
+
".part"
;
if
(
aRev
.
size
()
)
{
filename
+=
"."
+
aRev
;
}
it
->
second
=
new
STRING
();
readSExpression
(
it
->
second
,
filename
);
}
*
aResult
=
*
it
->
second
;
}
void
DIR_LIB_SOURCE
::
ReadParts
(
STRINGS
*
aResults
,
const
STRINGS
&
aPartNames
)
throw
(
IO_ERROR
)
{
aResults
->
clear
();
for
(
STRINGS
::
const_iterator
n
=
aPartNames
.
begin
();
n
!=
aPartNames
.
end
();
++
n
)
{
aResults
->
push_back
(
STRING
()
);
ReadPart
(
&
aResults
->
back
(),
*
n
);
}
}
void
DIR_LIB_SOURCE
::
GetCategories
(
STRINGS
*
aResults
)
throw
(
IO_ERROR
)
{
*
aResults
=
categories
;
}
#if defined(DEBUG)
#include <richio.h>
void
DIR_LIB_SOURCE
::
Show
()
{
printf
(
"categories:
\n
"
);
printf
(
"
Show
categories:
\n
"
);
for
(
STRINGS
::
const_iterator
it
=
categories
.
begin
();
it
!=
categories
.
end
();
++
it
)
printf
(
" '%s'
\n
"
,
it
->
c_str
()
);
printf
(
"
\n
"
);
printf
(
"parts:
\n
"
);
printf
(
"
Show
parts:
\n
"
);
for
(
DIR_CACHE
::
const_iterator
it
=
sweets
.
begin
();
it
!=
sweets
.
end
();
++
it
)
{
printf
(
" '%s'
\n
"
,
it
->
first
.
c_str
()
);
if
(
it
->
second
)
{
STRING_LINE_READER
slr
(
*
it
->
second
,
wxString
(
wxConvertMB2WX
(
it
->
first
.
c_str
()
)
)
);
while
(
slr
.
ReadLine
()
)
{
printf
(
" %s"
,
(
char
*
)
slr
);
}
printf
(
"
\n
"
);
}
}
}
#endif
void
DIR_LIB_SOURCE
::
doOneDir
(
const
STRING
&
aCategory
)
throw
(
IO_ERROR
)
...
...
@@ -249,7 +404,7 @@ void DIR_LIB_SOURCE::doOneDir( const STRING& aCategory ) throw( IO_ERROR )
DIR_WRAP
dir
=
opendir
(
curDir
.
c_str
()
);
if
(
!
*
dir
)
if
(
!
dir
)
{
STRING
msg
=
strerror
(
errno
);
msg
+=
"; scanning directory "
+
curDir
;
...
...
@@ -257,10 +412,8 @@ void DIR_LIB_SOURCE::doOneDir( const STRING& aCategory ) throw( IO_ERROR )
}
struct
stat
fs
;
STRING
partName
;
STRING
fileName
;
dirent
*
entry
;
while
(
(
entry
=
readdir
(
*
dir
))
!=
NULL
)
...
...
@@ -270,8 +423,6 @@ void DIR_LIB_SOURCE::doOneDir( const STRING& aCategory ) throw( IO_ERROR )
fileName
=
curDir
+
"/"
+
entry
->
d_name
;
//D( printf("name: '%s'\n", fileName.c_str() );)
if
(
!
stat
(
fileName
.
c_str
(),
&
fs
)
)
{
if
(
S_ISREG
(
fs
.
st_mode
)
&&
makePartFileName
(
entry
->
d_name
,
aCategory
,
&
partName
)
)
...
...
@@ -286,15 +437,16 @@ void DIR_LIB_SOURCE::doOneDir( const STRING& aCategory ) throw( IO_ERROR )
*/
sweets
[
partName
]
=
NULL
;
// NULL for now, load the sweet later.
//D( printf("part: %s\n", partName.c_str() );)
}
else
if
(
S_ISDIR
(
fs
.
st_mode
)
&&
!
aCategory
.
size
()
&&
isCategoryName
(
entry
->
d_name
)
)
{
// only one level of recursion is used, controlled by the
// emptiness of aCategory.
//D( printf("category: %s\n", entry->d_name );)
categories
.
push_back
(
entry
->
d_name
);
// somebody needs to test Windows (mingw), make sure it can
// handle opendir() recursively
doOneDir
(
entry
->
d_name
);
}
else
...
...
@@ -306,15 +458,53 @@ void DIR_LIB_SOURCE::doOneDir( const STRING& aCategory ) throw( IO_ERROR )
}
#if
1 || defined( TEST_DIR_LIB_SOURCE
)
#if
(1 || defined( TEST_DIR_LIB_SOURCE )) && defined(DEBUG
)
int
main
(
int
argc
,
char
**
argv
)
{
STRINGS
partnames
;
STRINGS
sweets
;
try
{
DIR_LIB_SOURCE
uut
(
argv
[
1
]
?
argv
[
1
]
:
""
,
true
);
// initially, only the DIR_CACHE sweets and STRING categories are loaded:
uut
.
Show
();
uut
.
GetCategoricalPartNames
(
&
partnames
,
"Category"
);
printf
(
"GetCategoricalPartNames(Category):
\n
"
);
for
(
STRINGS
::
const_iterator
it
=
partnames
.
begin
();
it
!=
partnames
.
end
();
++
it
)
{
printf
(
" '%s'
\n
"
,
it
->
c_str
()
);
}
uut
.
ReadParts
(
&
sweets
,
partnames
);
// fetch the part names for ALL categories.
uut
.
GetCategoricalPartNames
(
&
partnames
);
printf
(
"GetCategoricalPartNames(ALL):
\n
"
);
for
(
STRINGS
::
const_iterator
it
=
partnames
.
begin
();
it
!=
partnames
.
end
();
++
it
)
{
printf
(
" '%s'
\n
"
,
it
->
c_str
()
);
}
uut
.
ReadParts
(
&
sweets
,
partnames
);
printf
(
"Sweets for ALL parts:
\n
"
);
STRINGS
::
const_iterator
pn
=
partnames
.
begin
();
for
(
STRINGS
::
const_iterator
it
=
sweets
.
begin
();
it
!=
sweets
.
end
();
++
it
,
++
pn
)
{
printf
(
" %s: %s"
,
pn
->
c_str
(),
it
->
c_str
()
);
}
}
catch
(
std
::
exception
&
ex
)
{
printf
(
"std::exception
\n
"
);
}
catch
(
IO_ERROR
ioe
)
...
...
@@ -327,4 +517,3 @@ int main( int argc, char** argv )
#endif
new/sch_dir_lib_source.h
View file @
8384d7e0
...
...
@@ -29,6 +29,7 @@
#include <sch_lib.h>
#include <map>
#include <vector>
/**
...
...
@@ -52,10 +53,13 @@ class DIR_LIB_SOURCE : public LIB_SOURCE
{
friend
class
LIBS
;
///< LIBS::GetLib() can construct one.
bool
useVersioning
;
///< use files with extension ".revNNN..", else not
bool
useVersioning
;
///< use files with extension ".revNNN..", else not
DIR_CACHE
sweets
;
///< @todo, don't really need to cache the sweets, only the partnames.
STRINGS
categories
;
std
::
vector
<
char
>
readBuffer
;
///< used by readSExpression()
DIR_CACHE
sweets
;
STRINGS
categories
;
/**
* Function isPartFileName
...
...
@@ -75,6 +79,13 @@ class DIR_LIB_SOURCE : public LIB_SOURCE
bool
makePartFileName
(
const
char
*
aEntry
,
const
STRING
&
aCategory
,
STRING
*
aPartName
);
/**
* Function readSExpression
* reads an s-expression into aResult. Candidate for virtual function later.
*/
void
readSExpression
(
STRING
*
aResult
,
const
STRING
&
aNameSpec
)
throw
(
IO_ERROR
);
/**
* Function doOneDir
* loads part names [and categories] from a directory given by
...
...
@@ -109,39 +120,35 @@ public:
//-----<LIB_SOURCE implementation functions >------------------------------
void
ReadPart
(
STRING
*
aResult
,
const
STRING
&
aPartName
,
const
STRING
&
aRev
=
StrEmpty
)
throw
(
IO_ERROR
)
{
}
throw
(
IO_ERROR
);
void
ReadParts
(
STRING_TOKS
*
aResults
,
const
STRINGS
&
aPartNames
)
throw
(
IO_ERROR
)
{
}
void
ReadParts
(
STRINGS
*
aResults
,
const
STRINGS
&
aPartNames
)
throw
(
IO_ERROR
);
void
GetCategories
(
STRING_TOKS
*
aResults
)
throw
(
IO_ERROR
)
{
}
void
GetCategories
(
STRINGS
*
aResults
)
throw
(
IO_ERROR
);
void
GetCategoricalPartNames
(
STRING_TOKS
*
aResults
,
const
STRING
&
aCategory
=
StrEmpty
)
throw
(
IO_ERROR
)
{
}
void
GetCategoricalPartNames
(
STRINGS
*
aResults
,
const
STRING
&
aCategory
=
StrEmpty
)
throw
(
IO_ERROR
);
void
GetRevisions
(
STRING
_TOK
S
*
aResults
,
const
STRING
&
aPartName
)
throw
(
IO_ERROR
)
void
GetRevisions
(
STRINGS
*
aResults
,
const
STRING
&
aPartName
)
throw
(
IO_ERROR
)
{
// @todo
}
void
FindParts
(
STRING
_TOK
S
*
aResults
,
const
STRING
&
aQuery
)
throw
(
IO_ERROR
)
void
FindParts
(
STRINGS
*
aResults
,
const
STRING
&
aQuery
)
throw
(
IO_ERROR
)
{
// @todo
}
//-----</LIB_SOURCE implementation functions >------------------------------
#if defined(DEBUG)
/**
* Function Show
* will output a debug dump of contents.
*/
void
Show
();
#endif
};
}
// namespace SCH
...
...
new/sch_lib.h
View file @
8384d7e0
...
...
@@ -40,7 +40,7 @@
typedef
std
::
string
STRING
;
typedef
std
::
deque
<
STRING
>
STRINGS
;
typedef
STRINGS
STRING
_TOK
S
;
typedef
STRINGS
STRINGS
;
extern
const
STRING
StrEmpty
;
...
...
@@ -93,14 +93,14 @@ protected: ///< derived classes must implement
* @param aPartNames is a list of part names, one name per list element.
* @param aResults receives the s-expressions
*/
virtual
void
ReadParts
(
STRING
_TOK
S
*
aResults
,
const
STRINGS
&
aPartNames
)
virtual
void
ReadParts
(
STRINGS
*
aResults
,
const
STRINGS
&
aPartNames
)
throw
(
IO_ERROR
)
=
0
;
/**
* Function GetCategories
* fetches all categories present in the library source into @a aResults
*/
virtual
void
GetCategories
(
STRING
_TOK
S
*
aResults
)
virtual
void
GetCategories
(
STRINGS
*
aResults
)
throw
(
IO_ERROR
)
=
0
;
/**
...
...
@@ -112,7 +112,7 @@ protected: ///< derived classes must implement
*
* @param aResults is a place to put the fetched result, one category per STRING.
*/
virtual
void
GetCategoricalPartNames
(
STRING
_TOK
S
*
aResults
,
const
STRING
&
aCategory
=
StrEmpty
)
virtual
void
GetCategoricalPartNames
(
STRINGS
*
aResults
,
const
STRING
&
aCategory
=
StrEmpty
)
throw
(
IO_ERROR
)
=
0
;
/**
...
...
@@ -120,7 +120,7 @@ protected: ///< derived classes must implement
* fetches all revisions for @a aPartName into @a aResults. Revisions are strings
* like "rev12", "rev279", and are library source agnostic. These
*/
virtual
void
GetRevisions
(
STRING
_TOK
S
*
aResults
,
const
STRING
&
aPartName
)
virtual
void
GetRevisions
(
STRINGS
*
aResults
,
const
STRING
&
aPartName
)
throw
(
IO_ERROR
)
=
0
;
/**
...
...
@@ -139,7 +139,7 @@ protected: ///< derived classes must implement
*
* @param aResults is a place to put the fetched part names, one part per STRING.
*/
virtual
void
FindParts
(
STRING
_TOK
S
*
aResults
,
const
STRING
&
aQuery
)
virtual
void
FindParts
(
STRINGS
*
aResults
,
const
STRING
&
aQuery
)
throw
(
IO_ERROR
)
=
0
;
//-----</abstract for implementors>--------------------------------------
...
...
new/toolchain-mingw.cmake
0 → 100644
View file @
8384d7e0
# This is a CMake toolchain file for ARM:
# http://vtk.org/Wiki/CMake_Cross_Compiling
# usage
# cmake -DCMAKE_TOOLCHAIN_FILE=../../toolchain-mingw.cmake ..
# It is here to assist Dick with verifying compilation of /new stuff with mingw (under linux)
set
(
CMAKE_SYSTEM_NAME Linux
)
# Specific to Dick's machine, again for testing only:
include_directories
(
/svn/wxWidgets/include
)
#-----<configuration>-----------------------------------------------
# configure only the lines within this <configure> block, typically
# specify the cross compiler
set
(
CMAKE_C_COMPILER i586-mingw32msvc-gcc
)
set
(
CMAKE_CXX_COMPILER i586-mingw32msvc-g++
)
# where is the target environment
set
(
CMAKE_FIND_ROOT_PATH /usr/i586-mingw32msvc
)
#-----</configuration>-----------------------------------------------
# search for programs in the build host directories
set
(
CMAKE_FIND_ROOT_PATH_MODE_PROGRAM NEVER
)
# for libraries and headers in the target directories
set
(
CMAKE_FIND_ROOT_PATH_MODE_LIBRARY ONLY
)
set
(
CMAKE_FIND_ROOT_PATH_MODE_INCLUDE ONLY
)
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