Skip to content
Projects
Groups
Snippets
Help
Loading...
Help
Support
Keyboard shortcuts
?
Submit feedback
Contribute to GitLab
Sign in / Register
Toggle navigation
W
wendelin.core
Project overview
Project overview
Details
Activity
Releases
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Labels
Merge Requests
0
Merge Requests
0
Analytics
Analytics
Repository
Value Stream
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Commits
Open sidebar
Kirill Smelkov
wendelin.core
Commits
cf3511fb
Commit
cf3511fb
authored
Sep 30, 2021
by
Kirill Smelkov
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
.
parent
41045c0b
Changes
6
Show whitespace changes
Inline
Side-by-side
Showing
6 changed files
with
164 additions
and
53 deletions
+164
-53
wcfs/internal/xbtree/blib/keyrange.go
wcfs/internal/xbtree/blib/keyrange.go
+11
-0
wcfs/internal/xbtree/blib/rangemap.go.in
wcfs/internal/xbtree/blib/rangemap.go.in
+7
-3
wcfs/internal/xbtree/blib/rangemap_test.go
wcfs/internal/xbtree/blib/rangemap_test.go
+131
-43
wcfs/internal/xbtree/blib/rangeset_test.go
wcfs/internal/xbtree/blib/rangeset_test.go
+1
-1
wcfs/internal/xbtree/blib/zrangemap_str.go
wcfs/internal/xbtree/blib/zrangemap_str.go
+7
-3
wcfs/internal/xbtree/blib/zrangemap_void.go
wcfs/internal/xbtree/blib/zrangemap_void.go
+7
-3
No files found.
wcfs/internal/xbtree/blib/keyrange.go
View file @
cf3511fb
...
@@ -37,6 +37,17 @@ func (r *KeyRange) Has(k Key) bool {
...
@@ -37,6 +37,17 @@ func (r *KeyRange) Has(k Key) bool {
return
(
r
.
Lo
<=
k
&&
k
<=
r
.
Hi_
)
return
(
r
.
Lo
<=
k
&&
k
<=
r
.
Hi_
)
}
}
// Empty returns whether key range is empty.
func
(
r
*
KeyRange
)
Empty
()
bool
{
hi
:=
r
.
Hi_
if
hi
==
KeyMax
{
// [x,∞] cannot be empty because max x is ∞ and [∞,∞] has one element: ∞
return
false
}
hi
++
// no overflow
return
r
.
Lo
>=
hi
}
func
(
r
KeyRange
)
String
()
string
{
func
(
r
KeyRange
)
String
()
string
{
var
shi
string
var
shi
string
...
...
wcfs/internal/xbtree/blib/rangemap.go.in
View file @
cf3511fb
...
@@ -25,8 +25,8 @@ import (
...
@@ -25,8 +25,8 @@ import (
"sort"
"sort"
)
)
const
traceRangeMap
=
fals
e
const
traceRangeMap
=
tru
e
const
debugRangeMap
=
fals
e
const
debugRangeMap
=
tru
e
//
RangedMap
is
Key
->
VALUE
map
with
adjacent
keys
mapped
to
the
same
value
coalesced
into
Ranges
.
//
RangedMap
is
Key
->
VALUE
map
with
adjacent
keys
mapped
to
the
same
value
coalesced
into
Ranges
.
//
//
...
@@ -115,6 +115,10 @@ func (M *RangedMap) SetRange(r KeyRange, v VALUE) {
...
@@ -115,6 +115,10 @@ func (M *RangedMap) SetRange(r KeyRange, v VALUE) {
M
.
verify
()
M
.
verify
()
defer
M
.
verify
()
defer
M
.
verify
()
if
r
.
Empty
()
{
return
//
XXX
or
panic
?
}
//
clear
range
for
r
and
insert
new
entry
//
clear
range
for
r
and
insert
new
entry
//
TODO
optimize
for
same
-
value
/
set
case
(
just
merge
all
covered
//
TODO
optimize
for
same
-
value
/
set
case
(
just
merge
all
covered
//
entries
into
one
-
-
see
commented
AddRange
from
set
vvv
)
//
entries
into
one
-
-
see
commented
AddRange
from
set
vvv
)
...
@@ -367,7 +371,7 @@ func (M *RangedMap) verify() {
...
@@ -367,7 +371,7 @@ func (M *RangedMap) verify() {
for _, bad := range badv {
for _, bad := range badv {
emsg += fmt.Sprintf("- %s\n", bad)
emsg += fmt.Sprintf("- %s\n", bad)
}
}
emsg += fmt.Sprintf("\n
S
: %s\n", M)
emsg += fmt.Sprintf("\n
M
: %s\n", M)
panic(emsg)
panic(emsg)
}
}
}()
}()
...
...
wcfs/internal/xbtree/blib/rangemap_test.go
View file @
cf3511fb
...
@@ -38,14 +38,14 @@ const (
...
@@ -38,14 +38,14 @@ const (
func
TestRangedMap
(
t
*
testing
.
T
)
{
func
TestRangedMap
(
t
*
testing
.
T
)
{
type
testEntry
struct
{
type
testEntry
struct
{
A
*
RangedMap
M
*
RangedMap
B
RangedMapEntry
X
RangedMapEntry
Set
*
RangedMap
//
A.SetRange(B.keycov, B
.value)
Set
*
RangedMap
//
M.SetRange(X.keycov, X
.value)
Del
*
RangedMap
//
A.DelRange(B
.keycov)
Del
*
RangedMap
//
M.DelRange(X
.keycov)
Has
bool
//
A.HasRange(B
.keycov)
Has
bool
//
M.HasRange(X
.keycov)
}
}
E
:=
func
(
A
*
RangedMap
,
B
RangedMapEntry
,
S
,
D
*
RangedMap
,
H
bool
)
testEntry
{
E
:=
func
(
M
*
RangedMap
,
X
RangedMapEntry
,
S
,
D
*
RangedMap
,
H
bool
)
testEntry
{
return
testEntry
{
A
,
B
,
S
,
D
,
H
}
return
testEntry
{
M
,
X
,
S
,
D
,
H
}
}
}
// M is shorthand to create RangedMap, e.g. M(1,2,a, 3,4,b) will return {[1,2):a [3,4):b}.
// M is shorthand to create RangedMap, e.g. M(1,2,a, 3,4,b) will return {[1,2):a [3,4):b}.
...
@@ -54,7 +54,7 @@ func TestRangedMap(t *testing.T) {
...
@@ -54,7 +54,7 @@ func TestRangedMap(t *testing.T) {
if
l
%
3
!=
0
{
if
l
%
3
!=
0
{
panic
(
"non 3x number of arguments"
)
panic
(
"non 3x number of arguments"
)
}
}
// as
k
ey converts arg to Key
// as
K
ey converts arg to Key
asKey
:=
func
(
arg
interface
{})
Key
{
asKey
:=
func
(
arg
interface
{})
Key
{
// go through reflect to accept all int, int64, Key, ...
// go through reflect to accept all int, int64, Key, ...
return
Key
(
reflect
.
ValueOf
(
arg
)
.
Int
())
return
Key
(
reflect
.
ValueOf
(
arg
)
.
Int
())
...
@@ -78,8 +78,8 @@ func TestRangedMap(t *testing.T) {
...
@@ -78,8 +78,8 @@ func TestRangedMap(t *testing.T) {
return
M
return
M
}
}
//
K
creates RangedMapEntry{v, [lo,hi)}
//
X
creates RangedMapEntry{v, [lo,hi)}
K
:=
func
(
lo
,
hi
Key
,
v
string
)
RangedMapEntry
{
X
:=
func
(
lo
,
hi
Key
,
v
string
)
RangedMapEntry
{
hi_
:=
hi
hi_
:=
hi
if
hi_
!=
noo
{
if
hi_
!=
noo
{
hi_
--
hi_
--
...
@@ -95,78 +95,142 @@ func TestRangedMap(t *testing.T) {
...
@@ -95,78 +95,142 @@ func TestRangedMap(t *testing.T) {
testv
:=
[]
testEntry
{
testv
:=
[]
testEntry
{
// empty vs empty
E
(
E
(
M
(
1
,
2
,
a
,
2
,
3
,
b
),
// A
M
(),
// M
K
(
1
,
3
,
x
),
// B
X
(
0
,
0
,
x
),
// X
M
(),
// Set
M
(),
// Del
n
),
// Has
// empty vs !empty
E
(
M
(),
// M
X
(
1
,
2
,
x
),
// X
M
(
1
,
2
,
x
),
// Set
M
(),
// Del
n
),
// Has
// !empty vs empty
E
(
M
(
1
,
2
,
a
),
// M
X
(
0
,
0
,
x
),
// X
M
(
1
,
2
,
a
),
// Set
M
(
1
,
2
,
a
),
// Del
y
),
// Has XXX ok
// basic change
E
(
M
(
1
,
2
,
a
),
// M
X
(
1
,
2
,
x
),
// X
M
(
1
,
2
,
x
),
// Set
M
(),
// Del
y
),
// Has
// adjacent [1,3) [3,5)
E
(
M
(
1
,
3
,
a
),
// M
X
(
3
,
5
,
x
),
// X
M
(
1
,
3
,
a
,
3
,
5
,
x
),
// Set
M
(
1
,
3
,
a
),
// Del
n
),
// Has
// overlappin [1,3) [2,4)
E
(
M
(
1
,
3
,
a
),
// M
X
(
2
,
4
,
x
),
// X
M
(
1
,
2
,
a
,
2
,
4
,
x
),
// Set
M
(
1
,
2
,
a
),
// Del
n
),
// Has
// [1,7) vs [3,5) -> split
E
(
M
(
1
,
7
,
a
),
// M
X
(
3
,
5
,
x
),
// X
M
(
1
,
3
,
a
,
3
,
5
,
x
,
5
,
7
,
a
),
// Set
M
(
1
,
3
,
a
,
5
,
7
,
a
),
// Del
y
),
// Has
// several ranges vs [-∞,∞)
E
(
M
(
1
,
3
,
a
,
5
,
7
,
b
,
8
,
9
,
c
),
// M
X
(
noo
,
oo
,
x
),
// X
M
(
noo
,
oo
,
x
),
// Set
M
(),
// Del
n
),
// Has
E
(
M
(
1
,
2
,
a
,
2
,
3
,
b
),
// M
X
(
1
,
3
,
x
),
// X
M
(
1
,
3
,
x
),
// Set
M
(
1
,
3
,
x
),
// Set
M
(),
// Del
M
(),
// Del
y
),
// Has
y
),
// Has
// coalesce (same value, no overlap)
// coalesce (same value, no overlap)
E
(
E
(
M
(
1
,
2
,
a
,
4
,
5
,
a
),
//
A
M
(
1
,
2
,
a
,
4
,
5
,
a
),
//
M
K
(
2
,
4
,
a
),
// B
X
(
2
,
4
,
a
),
// X
M
(
1
,
5
,
a
),
// Set
M
(
1
,
5
,
a
),
// Set
M
(
1
,
2
,
a
,
4
,
5
,
a
),
// Del
M
(
1
,
2
,
a
,
4
,
5
,
a
),
// Del
n
),
// Has
n
),
// Has
// coalesce (same value, overlap)
// coalesce (same value, overlap)
E
(
E
(
M
(
1
,
4
,
a
,
5
,
8
,
a
),
//
A
M
(
1
,
4
,
a
,
5
,
8
,
a
),
//
M
K
(
2
,
6
,
a
),
// B
X
(
2
,
6
,
a
),
// X
M
(
1
,
8
,
a
),
// Set
M
(
1
,
8
,
a
),
// Set
M
(
1
,
2
,
a
,
6
,
8
,
a
),
// Del
M
(
1
,
2
,
a
,
6
,
8
,
a
),
// Del
n
),
// Has
n
),
// Has
// - shrink left/right (value !same) + new entry
// - shrink left/right (value !same) + new entry
E
(
E
(
M
(
1
,
4
,
a
),
//
A
M
(
1
,
4
,
a
),
//
M
K
(
2
,
6
,
x
),
// B
X
(
2
,
6
,
x
),
// X
M
(
1
,
2
,
a
,
2
,
6
,
x
),
// Set
M
(
1
,
2
,
a
,
2
,
6
,
x
),
// Set
M
(
1
,
2
,
a
),
// Del
M
(
1
,
2
,
a
),
// Del
n
),
// Has
n
),
// Has
E
(
E
(
M
(
5
,
8
,
b
),
//
A
M
(
5
,
8
,
b
),
//
M
K
(
2
,
6
,
x
),
// B
X
(
2
,
6
,
x
),
// X
M
(
2
,
6
,
x
,
6
,
8
,
b
),
// Set
M
(
2
,
6
,
x
,
6
,
8
,
b
),
// Set
M
(
6
,
8
,
b
),
// Del
M
(
6
,
8
,
b
),
// Del
n
),
// Has
n
),
// Has
E
(
E
(
M
(
1
,
4
,
a
,
5
,
8
,
b
),
//
A
M
(
1
,
4
,
a
,
5
,
8
,
b
),
//
M
K
(
2
,
6
,
x
),
// B
X
(
2
,
6
,
x
),
// X
M
(
1
,
2
,
a
,
2
,
6
,
x
,
6
,
8
,
b
),
// Set
M
(
1
,
2
,
a
,
2
,
6
,
x
,
6
,
8
,
b
),
// Set
M
(
1
,
2
,
a
,
6
,
8
,
b
),
// Del
M
(
1
,
2
,
a
,
6
,
8
,
b
),
// Del
n
),
// Has
n
),
// Has
}
}
for
_
,
tt
:=
range
testv
{
for
_
,
tt
:=
range
testv
{
A
:=
tt
.
A
M
:=
tt
.
M
B
:=
tt
.
B
X
:=
tt
.
X
r
:=
B
.
KeyRange
r
:=
X
.
KeyRange
v
:=
B
.
Value
v
:=
X
.
Value
assertMapHasRange
(
t
,
A
,
r
,
tt
.
Has
)
assertMapHasRange
(
t
,
M
,
r
,
tt
.
Has
)
Aset
:=
A
.
Clone
()
Mset
:=
M
.
Clone
()
Adel
:=
A
.
Clone
()
Mdel
:=
M
.
Clone
()
A
set
.
SetRange
(
r
,
v
)
M
set
.
SetRange
(
r
,
v
)
A
del
.
DelRange
(
r
)
M
del
.
DelRange
(
r
)
if
!
A
set
.
Equal
(
tt
.
Set
)
{
if
!
M
set
.
Equal
(
tt
.
Set
)
{
t
.
Errorf
(
"SetRange:
\n
A: %s
\n
e: %s
\n
->·: %s
\n
ok·: %s
\n
"
,
A
,
B
,
A
set
,
tt
.
Set
)
t
.
Errorf
(
"SetRange:
\n
M: %s
\n
e: %s
\n
->·: %s
\n
ok·: %s
\n
"
,
M
,
X
,
M
set
,
tt
.
Set
)
}
}
if
!
A
del
.
Equal
(
tt
.
Del
)
{
if
!
M
del
.
Equal
(
tt
.
Del
)
{
t
.
Errorf
(
"DelRange:
\n
A: %s
\n
r: %s
\n
->·: %s
\n
ok·: %s
\n
"
,
A
,
r
,
A
del
,
tt
.
Del
)
t
.
Errorf
(
"DelRange:
\n
M: %s
\n
r: %s
\n
->·: %s
\n
ok·: %s
\n
"
,
M
,
r
,
M
del
,
tt
.
Del
)
}
}
assertMapHasRange
(
t
,
A
set
,
r
,
true
)
assertMapHasRange
(
t
,
M
set
,
r
,
true
)
assertMapHasRange
(
t
,
A
del
,
r
,
false
)
assertMapHasRange
(
t
,
M
del
,
r
,
false
)
verifyGet
(
t
,
A
)
verifyGet
(
t
,
M
)
verifyGet
(
t
,
A
set
)
verifyGet
(
t
,
M
set
)
verifyGet
(
t
,
A
del
)
verifyGet
(
t
,
M
del
)
}
}
}
}
...
@@ -187,8 +251,11 @@ func verifyGet(t *testing.T, M *RangedMap) {
...
@@ -187,8 +251,11 @@ func verifyGet(t *testing.T, M *RangedMap) {
Mranges
=
append
(
Mranges
,
M
.
AllRanges
()
...
)
// copy just in case it changes on Get
Mranges
=
append
(
Mranges
,
M
.
AllRanges
()
...
)
// copy just in case it changes on Get
// verify "has-data"
// verify "has-data"
Z
:=
KeyRange
{
-
100
,
+
100
}
// models [-∞,∞)
for
_
,
e
:=
range
Mranges
{
for
_
,
e
:=
range
Mranges
{
for
k
:=
e
.
Lo
;
k
<=
e
.
Hi_
;
k
++
{
lo
:=
kmax
(
e
.
Lo
,
Z
.
Lo
)
hi_
:=
kmin
(
e
.
Hi_
,
Z
.
Hi_
)
for
k
:=
lo
;
k
<=
hi_
;
k
++
{
v
,
ok
:=
M
.
Get_
(
k
)
v
,
ok
:=
M
.
Get_
(
k
)
if
!
(
v
==
e
.
Value
&&
ok
)
{
if
!
(
v
==
e
.
Value
&&
ok
)
{
t
.
Errorf
(
"%s
\t
Get(%s):
\n
have: %q, %t
\n
want: %q, true"
,
t
.
Errorf
(
"%s
\t
Get(%s):
\n
have: %q, %t
\n
want: %q, true"
,
...
@@ -200,13 +267,15 @@ func verifyGet(t *testing.T, M *RangedMap) {
...
@@ -200,13 +267,15 @@ func verifyGet(t *testing.T, M *RangedMap) {
// verify "no-data"
// verify "no-data"
// NA = [-∞,∞) \ M
// NA = [-∞,∞) \ M
na
:=
RangedKeySet
{}
na
:=
RangedKeySet
{}
na
.
AddRange
(
KeyRange
{
-
10
,
+
10
})
// models -∞,∞
na
.
AddRange
(
Z
)
for
_
,
e
:=
range
Mranges
{
for
_
,
e
:=
range
Mranges
{
na
.
DelRange
(
e
.
KeyRange
)
na
.
DelRange
(
e
.
KeyRange
)
}
}
for
_
,
r
:=
range
na
.
AllRanges
()
{
for
_
,
r
:=
range
na
.
AllRanges
()
{
for
k
:=
r
.
Lo
;
k
<=
r
.
Hi_
;
k
++
{
lo
:=
kmax
(
r
.
Lo
,
Z
.
Lo
)
hi_
:=
kmin
(
r
.
Hi_
,
Z
.
Hi_
)
for
k
:=
lo
;
k
<=
hi_
;
k
++
{
v
,
ok
:=
M
.
Get_
(
k
)
v
,
ok
:=
M
.
Get_
(
k
)
if
!
(
v
==
""
&&
!
ok
)
{
if
!
(
v
==
""
&&
!
ok
)
{
t
.
Errorf
(
"%s
\t
Get(%s):
\n
have: %q, %t
\n
want: %q, false"
,
t
.
Errorf
(
"%s
\t
Get(%s):
\n
have: %q, %t
\n
want: %q, false"
,
...
@@ -215,3 +284,22 @@ func verifyGet(t *testing.T, M *RangedMap) {
...
@@ -215,3 +284,22 @@ func verifyGet(t *testing.T, M *RangedMap) {
}
}
}
}
}
}
// kmin returns min(a,b).
func
kmin
(
a
,
b
Key
)
Key
{
if
a
<
b
{
return
a
}
else
{
return
b
}
}
// kmax returns max(a,b).
func
kmax
(
a
,
b
Key
)
Key
{
if
a
>
b
{
return
a
}
else
{
return
b
}
}
wcfs/internal/xbtree/blib/rangeset_test.go
View file @
cf3511fb
...
@@ -110,7 +110,7 @@ func TestRangedKeySet(t *testing.T) {
...
@@ -110,7 +110,7 @@ func TestRangedKeySet(t *testing.T) {
S
(
1
,
7
),
S
(
1
,
7
),
S
(
1
,
3
,
5
,
7
)),
S
(
1
,
3
,
5
,
7
)),
// several ranges \ [-∞,
∞) -> ø
// several ranges \ [-∞,∞) -> ø
E
(
E
(
S
(
1
,
3
,
5
,
7
,
11
,
100
),
// A
S
(
1
,
3
,
5
,
7
,
11
,
100
),
// A
S
(
noo
,
oo
),
// B
S
(
noo
,
oo
),
// B
...
...
wcfs/internal/xbtree/blib/zrangemap_str.go
View file @
cf3511fb
...
@@ -27,8 +27,8 @@ import (
...
@@ -27,8 +27,8 @@ import (
"sort"
"sort"
)
)
const
trace_RangedMap_str
=
fals
e
const
trace_RangedMap_str
=
tru
e
const
debug_RangedMap_str
=
fals
e
const
debug_RangedMap_str
=
tru
e
// _RangedMap_str is Key->string map with adjacent keys mapped to the same value coalesced into Ranges.
// _RangedMap_str is Key->string map with adjacent keys mapped to the same value coalesced into Ranges.
//
//
...
@@ -117,6 +117,10 @@ func (M *_RangedMap_str) SetRange(r KeyRange, v string) {
...
@@ -117,6 +117,10 @@ func (M *_RangedMap_str) SetRange(r KeyRange, v string) {
M
.
verify
()
M
.
verify
()
defer
M
.
verify
()
defer
M
.
verify
()
if
r
.
Empty
()
{
return
// XXX or panic?
}
// clear range for r and insert new entry
// clear range for r and insert new entry
// TODO optimize for same-value/set case (just merge all covered
// TODO optimize for same-value/set case (just merge all covered
// entries into one - - see commented AddRange from set vvv)
// entries into one - - see commented AddRange from set vvv)
...
@@ -369,7 +373,7 @@ func (M *_RangedMap_str) verify() {
...
@@ -369,7 +373,7 @@ func (M *_RangedMap_str) verify() {
for
_
,
bad
:=
range
badv
{
for
_
,
bad
:=
range
badv
{
emsg
+=
fmt
.
Sprintf
(
"- %s
\n
"
,
bad
)
emsg
+=
fmt
.
Sprintf
(
"- %s
\n
"
,
bad
)
}
}
emsg
+=
fmt
.
Sprintf
(
"
\n
S
: %s
\n
"
,
M
)
emsg
+=
fmt
.
Sprintf
(
"
\n
M
: %s
\n
"
,
M
)
panic
(
emsg
)
panic
(
emsg
)
}
}
}()
}()
...
...
wcfs/internal/xbtree/blib/zrangemap_void.go
View file @
cf3511fb
...
@@ -27,8 +27,8 @@ import (
...
@@ -27,8 +27,8 @@ import (
"sort"
"sort"
)
)
const
trace_RangedMap_void
=
fals
e
const
trace_RangedMap_void
=
tru
e
const
debug_RangedMap_void
=
fals
e
const
debug_RangedMap_void
=
tru
e
// _RangedMap_void is Key->void map with adjacent keys mapped to the same value coalesced into Ranges.
// _RangedMap_void is Key->void map with adjacent keys mapped to the same value coalesced into Ranges.
//
//
...
@@ -117,6 +117,10 @@ func (M *_RangedMap_void) SetRange(r KeyRange, v void) {
...
@@ -117,6 +117,10 @@ func (M *_RangedMap_void) SetRange(r KeyRange, v void) {
M
.
verify
()
M
.
verify
()
defer
M
.
verify
()
defer
M
.
verify
()
if
r
.
Empty
()
{
return
// XXX or panic?
}
// clear range for r and insert new entry
// clear range for r and insert new entry
// TODO optimize for same-value/set case (just merge all covered
// TODO optimize for same-value/set case (just merge all covered
// entries into one - - see commented AddRange from set vvv)
// entries into one - - see commented AddRange from set vvv)
...
@@ -369,7 +373,7 @@ func (M *_RangedMap_void) verify() {
...
@@ -369,7 +373,7 @@ func (M *_RangedMap_void) verify() {
for
_
,
bad
:=
range
badv
{
for
_
,
bad
:=
range
badv
{
emsg
+=
fmt
.
Sprintf
(
"- %s
\n
"
,
bad
)
emsg
+=
fmt
.
Sprintf
(
"- %s
\n
"
,
bad
)
}
}
emsg
+=
fmt
.
Sprintf
(
"
\n
S
: %s
\n
"
,
M
)
emsg
+=
fmt
.
Sprintf
(
"
\n
M
: %s
\n
"
,
M
)
panic
(
emsg
)
panic
(
emsg
)
}
}
}()
}()
...
...
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