Skip to content
GitLab
Menu
Projects
Groups
Snippets
Loading...
Help
Help
Support
Community forum
Keyboard shortcuts
?
Submit feedback
Contribute to GitLab
Sign in / Register
Toggle navigation
Menu
Open sidebar
orangecat
ollama
Commits
e40145a3
"vscode:/vscode.git/clone" did not exist on "c888d6dc21bfc58386e80cb9c2b9823394fb1093"
Commit
e40145a3
authored
May 21, 2024
by
Michael Yang
Browse files
lint
parent
c895a7d1
Changes
31
Hide whitespace changes
Inline
Side-by-side
Showing
20 changed files
with
52 additions
and
54 deletions
+52
-54
.golangci.yaml
.golangci.yaml
+6
-0
api/types_test.go
api/types_test.go
+4
-4
app/lifecycle/paths.go
app/lifecycle/paths.go
+0
-1
app/lifecycle/server.go
app/lifecycle/server.go
+1
-2
app/lifecycle/updater.go
app/lifecycle/updater.go
+3
-3
app/store/store.go
app/store/store.go
+0
-1
cmd/cmd.go
cmd/cmd.go
+0
-2
cmd/interactive_test.go
cmd/interactive_test.go
+5
-4
convert/convert.go
convert/convert.go
+1
-1
convert/torch.go
convert/torch.go
+0
-1
format/format_test.go
format/format_test.go
+0
-1
gpu/assets.go
gpu/assets.go
+1
-1
gpu/gpu_test.go
gpu/gpu_test.go
+3
-2
llm/gguf.go
llm/gguf.go
+4
-4
llm/memory.go
llm/memory.go
+1
-1
llm/server.go
llm/server.go
+2
-3
openai/openai.go
openai/openai.go
+0
-1
parser/parser_test.go
parser/parser_test.go
+15
-16
progress/progress.go
progress/progress.go
+2
-2
readline/buffer.go
readline/buffer.go
+4
-4
No files found.
.golangci.yaml
View file @
e40145a3
...
...
@@ -12,8 +12,14 @@ linters:
# FIXME: for some reason this errors on windows
# - gofmt
# - goimports
-
intrange
-
misspell
-
nilerr
-
nolintlint
-
nosprintfhostport
-
testifylint
-
unconvert
-
unused
-
usestdlibvars
-
wastedassign
-
whitespace
api/types_test.go
View file @
e40145a3
...
...
@@ -72,13 +72,13 @@ func TestDurationMarshalUnmarshal(t *testing.T) {
},
{
"positive duration"
,
time
.
Duration
(
42
*
time
.
Second
)
,
time
.
Duration
(
42
*
time
.
Second
)
,
42
*
time
.
Second
,
42
*
time
.
Second
,
},
{
"another positive duration"
,
time
.
Duration
(
42
*
time
.
Minute
)
,
time
.
Duration
(
42
*
time
.
Minute
)
,
42
*
time
.
Minute
,
42
*
time
.
Minute
,
},
{
"zero duration"
,
...
...
app/lifecycle/paths.go
View file @
e40145a3
...
...
@@ -69,7 +69,6 @@ func init() {
slog
.
Error
(
fmt
.
Sprintf
(
"create ollama dir %s: %v"
,
AppDataDir
,
err
))
}
}
}
else
if
runtime
.
GOOS
==
"darwin"
{
// TODO
AppName
+=
".app"
...
...
app/lifecycle/server.go
View file @
e40145a3
...
...
@@ -15,7 +15,7 @@ import (
)
func
getCLIFullPath
(
command
string
)
string
{
cmdPath
:=
""
var
cmdPath
string
appExe
,
err
:=
os
.
Executable
()
if
err
==
nil
{
cmdPath
=
filepath
.
Join
(
filepath
.
Dir
(
appExe
),
command
)
...
...
@@ -65,7 +65,6 @@ func start(ctx context.Context, command string) (*exec.Cmd, error) {
if
err
!=
nil
{
if
!
errors
.
Is
(
err
,
os
.
ErrNotExist
)
{
return
nil
,
fmt
.
Errorf
(
"stat ollama server log dir %s: %v"
,
logDir
,
err
)
}
if
err
:=
os
.
MkdirAll
(
logDir
,
0
o755
);
err
!=
nil
{
...
...
app/lifecycle/updater.go
View file @
e40145a3
...
...
@@ -78,7 +78,7 @@ func IsNewReleaseAvailable(ctx context.Context) (bool, UpdateResponse) {
}
defer
resp
.
Body
.
Close
()
if
resp
.
StatusCode
==
204
{
if
resp
.
StatusCode
==
http
.
StatusNoContent
{
slog
.
Debug
(
"check update response 204 (current version is up to date)"
)
return
false
,
updateResp
}
...
...
@@ -87,7 +87,7 @@ func IsNewReleaseAvailable(ctx context.Context) (bool, UpdateResponse) {
slog
.
Warn
(
fmt
.
Sprintf
(
"failed to read body response: %s"
,
err
))
}
if
resp
.
StatusCode
!=
200
{
if
resp
.
StatusCode
!=
http
.
StatusOK
{
slog
.
Info
(
fmt
.
Sprintf
(
"check update error %d - %.96s"
,
resp
.
StatusCode
,
string
(
body
)))
return
false
,
updateResp
}
...
...
@@ -114,7 +114,7 @@ func DownloadNewRelease(ctx context.Context, updateResp UpdateResponse) error {
if
err
!=
nil
{
return
fmt
.
Errorf
(
"error checking update: %w"
,
err
)
}
if
resp
.
StatusCode
!=
200
{
if
resp
.
StatusCode
!=
http
.
StatusOK
{
return
fmt
.
Errorf
(
"unexpected status attempting to download update %d"
,
resp
.
StatusCode
)
}
resp
.
Body
.
Close
()
...
...
app/store/store.go
View file @
e40145a3
...
...
@@ -29,7 +29,6 @@ func GetID() string {
initStore
()
}
return
store
.
ID
}
func
GetFirstTimeRun
()
bool
{
...
...
cmd/cmd.go
View file @
e40145a3
...
...
@@ -746,7 +746,6 @@ func displayResponse(content string, wordWrap bool, state *displayResponseState)
if
wordWrap
&&
termWidth
>=
10
{
for
_
,
ch
:=
range
content
{
if
state
.
lineLength
+
1
>
termWidth
-
5
{
if
runewidth
.
StringWidth
(
state
.
wordBuffer
)
>
termWidth
-
10
{
fmt
.
Printf
(
"%s%c"
,
state
.
wordBuffer
,
ch
)
state
.
wordBuffer
=
""
...
...
@@ -1044,7 +1043,6 @@ func waitForServer(ctx context.Context, client *api.Client) error {
}
}
}
}
func
checkServerHeartbeat
(
cmd
*
cobra
.
Command
,
_
[]
string
)
error
{
...
...
cmd/interactive_test.go
View file @
e40145a3
...
...
@@ -6,6 +6,7 @@ import (
"text/template"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"github.com/ollama/ollama/api"
)
...
...
@@ -85,11 +86,11 @@ MESSAGE assistant """Yes it is true, I am half horse, half shark."""
`
tmpl
,
err
:=
template
.
New
(
""
)
.
Parse
(
expectedModelfile
)
assert
.
Nil
(
t
,
err
)
require
.
NoError
(
t
,
err
)
var
buf
bytes
.
Buffer
err
=
tmpl
.
Execute
(
&
buf
,
opts
)
assert
.
Nil
(
t
,
err
)
require
.
NoError
(
t
,
err
)
assert
.
Equal
(
t
,
buf
.
String
(),
mf
)
opts
.
ParentModel
=
"horseshark"
...
...
@@ -107,10 +108,10 @@ MESSAGE assistant """Yes it is true, I am half horse, half shark."""
`
tmpl
,
err
=
template
.
New
(
""
)
.
Parse
(
expectedModelfile
)
assert
.
Nil
(
t
,
err
)
require
.
NoError
(
t
,
err
)
var
parentBuf
bytes
.
Buffer
err
=
tmpl
.
Execute
(
&
parentBuf
,
opts
)
assert
.
Nil
(
t
,
err
)
require
.
NoError
(
t
,
err
)
assert
.
Equal
(
t
,
parentBuf
.
String
(),
mf
)
}
convert/convert.go
View file @
e40145a3
...
...
@@ -189,7 +189,7 @@ func LoadSentencePieceTokens(dirpath string, params *Params) (*Vocab, error) {
if
params
.
VocabSize
>
len
(
v
.
Tokens
)
{
missingTokens
:=
params
.
VocabSize
-
len
(
v
.
Tokens
)
slog
.
Warn
(
fmt
.
Sprintf
(
"vocab is missing %d tokens"
,
missingTokens
))
for
cnt
:=
0
;
cnt
<
missingTokens
;
cnt
++
{
for
cnt
:=
range
missingTokens
{
v
.
Tokens
=
append
(
v
.
Tokens
,
fmt
.
Sprintf
(
"<dummy%05d>"
,
cnt
+
1
))
v
.
Scores
=
append
(
v
.
Scores
,
-
1
)
v
.
Types
=
append
(
v
.
Types
,
tokenTypeUserDefined
)
...
...
convert/torch.go
View file @
e40145a3
...
...
@@ -104,7 +104,6 @@ func (tf *TorchFormat) GetTensors(dirpath string, params *Params) ([]llm.Tensor,
}
return
tensors
,
nil
}
func
getAltParams
(
dirpath
string
)
(
*
Params
,
error
)
{
...
...
format/format_test.go
View file @
e40145a3
...
...
@@ -5,7 +5,6 @@ import (
)
func
TestHumanNumber
(
t
*
testing
.
T
)
{
type
testCase
struct
{
input
uint64
expected
string
...
...
gpu/assets.go
View file @
e40145a3
...
...
@@ -80,7 +80,7 @@ func cleanupTmpDirs() {
if
err
==
nil
{
pid
,
err
:=
strconv
.
Atoi
(
string
(
raw
))
if
err
==
nil
{
if
proc
,
err
:=
os
.
FindProcess
(
int
(
pid
)
)
;
err
==
nil
&&
!
errors
.
Is
(
proc
.
Signal
(
syscall
.
Signal
(
0
)),
os
.
ErrProcessDone
)
{
if
proc
,
err
:=
os
.
FindProcess
(
pid
);
err
==
nil
&&
!
errors
.
Is
(
proc
.
Signal
(
syscall
.
Signal
(
0
)),
os
.
ErrProcessDone
)
{
// Another running ollama, ignore this tmpdir
continue
}
...
...
gpu/gpu_test.go
View file @
e40145a3
...
...
@@ -5,11 +5,12 @@ import (
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
func
TestBasicGetGPUInfo
(
t
*
testing
.
T
)
{
info
:=
GetGPUInfo
()
assert
.
Greater
(
t
,
len
(
info
)
,
0
)
assert
.
NotEmpty
(
t
,
len
(
info
))
assert
.
Contains
(
t
,
"cuda rocm cpu metal"
,
info
[
0
]
.
Library
)
if
info
[
0
]
.
Library
!=
"cpu"
{
assert
.
Greater
(
t
,
info
[
0
]
.
TotalMemory
,
uint64
(
0
))
...
...
@@ -19,7 +20,7 @@ func TestBasicGetGPUInfo(t *testing.T) {
func
TestCPUMemInfo
(
t
*
testing
.
T
)
{
info
,
err
:=
GetCPUMem
()
assert
.
NoError
(
t
,
err
)
require
.
NoError
(
t
,
err
)
switch
runtime
.
GOOS
{
case
"darwin"
:
t
.
Skip
(
"CPU memory not populated on darwin"
)
...
...
llm/gguf.go
View file @
e40145a3
...
...
@@ -592,8 +592,8 @@ func (llm *gguf) Encode(ws io.WriteSeeker, kv KV, tensors []Tensor) error {
return
err
}
dims
:=
0
for
cnt
:=
0
;
cnt
<
len
(
tensor
.
Shape
)
;
cnt
++
{
var
dims
int
for
cnt
:=
range
len
(
tensor
.
Shape
)
{
if
tensor
.
Shape
[
cnt
]
>
0
{
dims
++
}
...
...
@@ -603,8 +603,8 @@ func (llm *gguf) Encode(ws io.WriteSeeker, kv KV, tensors []Tensor) error {
return
err
}
for
i
:=
0
;
i
<
dims
;
i
++
{
if
err
:=
binary
.
Write
(
ws
,
llm
.
ByteOrder
,
uint64
(
tensor
.
Shape
[
dims
-
1
-
i
])
)
;
err
!=
nil
{
for
i
:=
range
dims
{
if
err
:=
binary
.
Write
(
ws
,
llm
.
ByteOrder
,
tensor
.
Shape
[
dims
-
1
-
i
]);
err
!=
nil
{
return
err
}
}
...
...
llm/memory.go
View file @
e40145a3
...
...
@@ -103,7 +103,7 @@ func EstimateGPULayers(gpus []gpu.GpuInfo, ggml *GGML, projectors []string, opts
}
var
layerCount
int
for
i
:=
0
;
i
<
int
(
ggml
.
KV
()
.
BlockCount
())
;
i
++
{
for
i
:=
range
int
(
ggml
.
KV
()
.
BlockCount
())
{
if
blk
,
ok
:=
layers
[
fmt
.
Sprintf
(
"blk.%d"
,
i
)];
ok
{
memoryLayer
:=
blk
.
size
()
...
...
llm/server.go
View file @
e40145a3
...
...
@@ -85,7 +85,6 @@ func NewLlamaServer(gpus gpu.GpuInfoList, model string, ggml *GGML, adapters, pr
var
systemMemory
uint64
gpuCount
:=
len
(
gpus
)
if
(
len
(
gpus
)
==
1
&&
gpus
[
0
]
.
Library
==
"cpu"
)
||
opts
.
NumGPU
==
0
{
// TODO evaluate system memory to see if we should block the load, or force an unload of another CPU runner
cpuRunner
=
serverForCpu
()
...
...
@@ -233,7 +232,7 @@ func NewLlamaServer(gpus gpu.GpuInfoList, model string, ggml *GGML, adapters, pr
params
=
append
(
params
,
"--parallel"
,
fmt
.
Sprintf
(
"%d"
,
numParallel
))
for
i
:=
0
;
i
<
len
(
servers
)
;
i
++
{
for
i
:=
range
len
(
servers
)
{
dir
:=
availableServers
[
servers
[
i
]]
if
dir
==
""
{
// Shouldn't happen
...
...
@@ -316,7 +315,7 @@ func NewLlamaServer(gpus gpu.GpuInfoList, model string, ggml *GGML, adapters, pr
s
.
cmd
.
Stdout
=
os
.
Stdout
s
.
cmd
.
Stderr
=
s
.
status
visibleDevicesEnv
,
visibleDevicesEnvVal
:=
gpu
.
GpuInfoList
(
gpus
)
.
GetVisibleDevicesEnv
()
visibleDevicesEnv
,
visibleDevicesEnvVal
:=
gpu
s
.
GetVisibleDevicesEnv
()
pathEnvVal
:=
strings
.
Join
(
libraryPaths
,
string
(
filepath
.
ListSeparator
))
// Update or add the path and visible devices variable with our adjusted version
...
...
openai/openai.go
View file @
e40145a3
...
...
@@ -245,7 +245,6 @@ func (w *writer) writeResponse(data []byte) (int, error) {
d
,
err
:=
json
.
Marshal
(
toChunk
(
w
.
id
,
chatResponse
))
if
err
!=
nil
{
return
0
,
err
}
w
.
ResponseWriter
.
Header
()
.
Set
(
"Content-Type"
,
"text/event-stream"
)
...
...
parser/parser_test.go
View file @
e40145a3
...
...
@@ -10,6 +10,7 @@ import (
"unicode/utf16"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
func
TestParseFileFile
(
t
*
testing
.
T
)
{
...
...
@@ -25,7 +26,7 @@ TEMPLATE template1
reader
:=
strings
.
NewReader
(
input
)
modelfile
,
err
:=
ParseFile
(
reader
)
assert
.
NoError
(
t
,
err
)
require
.
NoError
(
t
,
err
)
expectedCommands
:=
[]
Command
{
{
Name
:
"model"
,
Args
:
"model1"
},
...
...
@@ -88,7 +89,7 @@ func TestParseFileFrom(t *testing.T) {
for
_
,
c
:=
range
cases
{
t
.
Run
(
""
,
func
(
t
*
testing
.
T
)
{
modelfile
,
err
:=
ParseFile
(
strings
.
NewReader
(
c
.
input
))
assert
.
ErrorIs
(
t
,
err
,
c
.
err
)
require
.
ErrorIs
(
t
,
err
,
c
.
err
)
if
modelfile
!=
nil
{
assert
.
Equal
(
t
,
c
.
expected
,
modelfile
.
Commands
)
}
...
...
@@ -105,7 +106,7 @@ PARAMETER param1
reader
:=
strings
.
NewReader
(
input
)
_
,
err
:=
ParseFile
(
reader
)
assert
.
ErrorIs
(
t
,
err
,
io
.
ErrUnexpectedEOF
)
require
.
ErrorIs
(
t
,
err
,
io
.
ErrUnexpectedEOF
)
}
func
TestParseFileBadCommand
(
t
*
testing
.
T
)
{
...
...
@@ -114,8 +115,7 @@ FROM foo
BADCOMMAND param1 value1
`
_
,
err
:=
ParseFile
(
strings
.
NewReader
(
input
))
assert
.
ErrorIs
(
t
,
err
,
errInvalidCommand
)
require
.
ErrorIs
(
t
,
err
,
errInvalidCommand
)
}
func
TestParseFileMessages
(
t
*
testing
.
T
)
{
...
...
@@ -201,7 +201,7 @@ MESSAGE system`,
for
_
,
c
:=
range
cases
{
t
.
Run
(
""
,
func
(
t
*
testing
.
T
)
{
modelfile
,
err
:=
ParseFile
(
strings
.
NewReader
(
c
.
input
))
assert
.
ErrorIs
(
t
,
err
,
c
.
err
)
require
.
ErrorIs
(
t
,
err
,
c
.
err
)
if
modelfile
!=
nil
{
assert
.
Equal
(
t
,
c
.
expected
,
modelfile
.
Commands
)
}
...
...
@@ -355,7 +355,7 @@ TEMPLATE """
for
_
,
c
:=
range
cases
{
t
.
Run
(
""
,
func
(
t
*
testing
.
T
)
{
modelfile
,
err
:=
ParseFile
(
strings
.
NewReader
(
c
.
multiline
))
assert
.
ErrorIs
(
t
,
err
,
c
.
err
)
require
.
ErrorIs
(
t
,
err
,
c
.
err
)
if
modelfile
!=
nil
{
assert
.
Equal
(
t
,
c
.
expected
,
modelfile
.
Commands
)
}
...
...
@@ -413,7 +413,7 @@ func TestParseFileParameters(t *testing.T) {
fmt
.
Fprintln
(
&
b
,
"FROM foo"
)
fmt
.
Fprintln
(
&
b
,
"PARAMETER"
,
k
)
modelfile
,
err
:=
ParseFile
(
&
b
)
assert
.
NoError
(
t
,
err
)
require
.
NoError
(
t
,
err
)
assert
.
Equal
(
t
,
[]
Command
{
{
Name
:
"model"
,
Args
:
"foo"
},
...
...
@@ -442,7 +442,7 @@ FROM foo
for
_
,
c
:=
range
cases
{
t
.
Run
(
""
,
func
(
t
*
testing
.
T
)
{
modelfile
,
err
:=
ParseFile
(
strings
.
NewReader
(
c
.
input
))
assert
.
NoError
(
t
,
err
)
require
.
NoError
(
t
,
err
)
assert
.
Equal
(
t
,
c
.
expected
,
modelfile
.
Commands
)
})
}
...
...
@@ -501,15 +501,14 @@ SYSTEM ""
for
_
,
c
:=
range
cases
{
t
.
Run
(
""
,
func
(
t
*
testing
.
T
)
{
modelfile
,
err
:=
ParseFile
(
strings
.
NewReader
(
c
))
assert
.
NoError
(
t
,
err
)
require
.
NoError
(
t
,
err
)
modelfile2
,
err
:=
ParseFile
(
strings
.
NewReader
(
modelfile
.
String
()))
assert
.
NoError
(
t
,
err
)
require
.
NoError
(
t
,
err
)
assert
.
Equal
(
t
,
modelfile
,
modelfile2
)
})
}
}
func
TestParseFileUTF16ParseFile
(
t
*
testing
.
T
)
{
...
...
@@ -522,10 +521,10 @@ SYSTEM You are a utf16 file.
utf16File
:=
utf16
.
Encode
(
append
([]
rune
{
'\ufffe'
},
[]
rune
(
data
)
...
))
buf
:=
new
(
bytes
.
Buffer
)
err
:=
binary
.
Write
(
buf
,
binary
.
LittleEndian
,
utf16File
)
assert
.
NoError
(
t
,
err
)
require
.
NoError
(
t
,
err
)
actual
,
err
:=
ParseFile
(
buf
)
assert
.
NoError
(
t
,
err
)
require
.
NoError
(
t
,
err
)
expected
:=
[]
Command
{
{
Name
:
"model"
,
Args
:
"bob"
},
...
...
@@ -539,9 +538,9 @@ SYSTEM You are a utf16 file.
// simulate a utf16 be file
buf
=
new
(
bytes
.
Buffer
)
err
=
binary
.
Write
(
buf
,
binary
.
BigEndian
,
utf16File
)
assert
.
NoError
(
t
,
err
)
require
.
NoError
(
t
,
err
)
actual
,
err
=
ParseFile
(
buf
)
assert
.
NoError
(
t
,
err
)
require
.
NoError
(
t
,
err
)
assert
.
Equal
(
t
,
expected
,
actual
.
Commands
)
}
progress/progress.go
View file @
e40145a3
...
...
@@ -59,7 +59,7 @@ func (p *Progress) StopAndClear() bool {
stopped
:=
p
.
stop
()
if
stopped
{
// clear all progress lines
for
i
:=
0
;
i
<
p
.
pos
;
i
++
{
for
i
:=
range
p
.
pos
{
if
i
>
0
{
fmt
.
Fprint
(
p
.
w
,
"
\0
33[A"
)
}
...
...
@@ -85,7 +85,7 @@ func (p *Progress) render() {
defer
fmt
.
Fprint
(
p
.
w
,
"
\0
33[?25h"
)
// clear already rendered progress lines
for
i
:=
0
;
i
<
p
.
pos
;
i
++
{
for
i
:=
range
p
.
pos
{
if
i
>
0
{
fmt
.
Fprint
(
p
.
w
,
"
\0
33[A"
)
}
...
...
readline/buffer.go
View file @
e40145a3
...
...
@@ -154,7 +154,7 @@ func (b *Buffer) MoveToStart() {
if
b
.
Pos
>
0
{
currLine
:=
b
.
DisplayPos
/
b
.
LineWidth
if
currLine
>
0
{
for
cnt
:=
0
;
cnt
<
currLine
;
cnt
++
{
for
range
currLine
{
fmt
.
Print
(
CursorUp
)
}
}
...
...
@@ -169,7 +169,7 @@ func (b *Buffer) MoveToEnd() {
currLine
:=
b
.
DisplayPos
/
b
.
LineWidth
totalLines
:=
b
.
DisplaySize
()
/
b
.
LineWidth
if
currLine
<
totalLines
{
for
cnt
:=
0
;
cnt
<
totalLines
-
currLine
;
cnt
++
{
for
range
totalLines
-
currLine
{
fmt
.
Print
(
CursorDown
)
}
remainder
:=
b
.
DisplaySize
()
%
b
.
LineWidth
...
...
@@ -451,7 +451,7 @@ func (b *Buffer) DeleteBefore() {
func
(
b
*
Buffer
)
DeleteRemaining
()
{
if
b
.
DisplaySize
()
>
0
&&
b
.
Pos
<
b
.
DisplaySize
()
{
charsToDel
:=
b
.
Buf
.
Size
()
-
b
.
Pos
for
cnt
:=
0
;
cnt
<
charsToDel
;
cnt
++
{
for
range
charsToDel
{
b
.
Delete
()
}
}
...
...
@@ -495,7 +495,7 @@ func (b *Buffer) ClearScreen() {
if
currPos
>
0
{
targetLine
:=
currPos
/
b
.
LineWidth
if
targetLine
>
0
{
for
cnt
:=
0
;
cnt
<
targetLine
;
cnt
++
{
for
range
targetLine
{
fmt
.
Print
(
CursorDown
)
}
}
...
...
Prev
1
2
Next
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