Skip to content
GitLab
Menu
Projects
Groups
Snippets
Loading...
Help
Help
Support
Community forum
Keyboard shortcuts
?
Submit feedback
Sign in
Toggle navigation
Menu
Open sidebar
Instrument Control
NomadCommandSystem
Commits
e06c9ce4
Commit
e06c9ce4
authored
Apr 23, 2020
by
legoc
Browse files
Merge branch 'V4.0' of
https://code.ill.fr/instrument-control/nomad-command-system
into V4.0
parents
47942b75
8c3989d7
Changes
11
Expand all
Hide whitespace changes
Inline
Side-by-side
ChangeLog
View file @
e06c9ce4
4.0.7 23/04/2020
-----
* Merge ploty2 branch
4.0.6 22/04/2020
-----
* Dependencies to cameo updated.
...
...
pom.xml
View file @
e06c9ce4
...
...
@@ -2,7 +2,7 @@
<modelVersion>
4.0.0
</modelVersion>
<groupId>
fr.ill.ics
</groupId>
<artifactId>
nomadcommandsystem
</artifactId>
<version>
4.0.
6
</version>
<version>
4.0.
7
</version>
<name>
NomadCommandSystem
</name>
<description>
Java bridge for the communication with the Nomad server
</description>
<scm>
...
...
@@ -196,6 +196,11 @@
<arg
line=
"${protoFlags} ${protoDir}/SessionRequests.proto"
/>
</exec>
<echo
message=
"Generating DataPlotMessages.java"
/>
<exec
executable=
"protoc"
>
<arg
line=
"${protoFlags} ${protoDir}/DataPlotMessages.proto"
/>
</exec>
</target>
</configuration>
<goals>
...
...
src/main/java/fr/ill/ics/bridge/ChangeManager.java
View file @
e06c9ce4
...
...
@@ -26,7 +26,9 @@ import fr.ill.ics.nscclient.log.LogSubscriber;
import
fr.ill.ics.nscclient.notification.DataNotificationClient
;
import
fr.ill.ics.nscclient.notification.commandzone.CommandZoneEventClient
;
import
fr.ill.ics.nscclient.notification.commandzone.sync.CommandZoneSyncEventClient
;
import
fr.ill.ics.nscclient.ploty.PlotyInstance
;
import
fr.ill.ics.nscclient.survey.SurveySubscriberImpl
;
import
fr.ill.ics.util.ConfigManager
;
public
class
ChangeManager
{
...
...
@@ -50,6 +52,9 @@ public class ChangeManager {
DataNotificationClient
.
getInstance
().
readAndDispatch
();
CommandZoneEventClient
.
getInstance
().
readAndDispatch
();
CommandZoneSyncEventClient
.
getInstance
().
readAndDispatch
();
if
(
ConfigManager
.
getInstance
().
getPlotyVersion
()
==
2
)
{
PlotyInstance
.
getInstance
().
readAndDispatch
();
}
// Iterate the map of log subscribers.
Map
<
String
,
LogSubscriber
>
logSubscriberMap
=
LogSubscriber
.
getInstances
();
...
...
src/main/java/fr/ill/ics/core/property/DynamicProperty.java
View file @
e06c9ce4
...
...
@@ -43,6 +43,10 @@ public class DynamicProperty {
return
properties
;
}
public
int
getId
()
{
return
id
;
}
public
String
toString
()
{
String
result
=
"DynamicProperty "
+
id
+
" {\n"
;
...
...
src/main/java/fr/ill/ics/core/property/PropertyManager.java
View file @
e06c9ce4
...
...
@@ -740,6 +740,26 @@ public class PropertyManager {
throw
pnfe
;
}
/**
* Search dynamic property
*/
public
int
getDynamicPropertyParentId
(
Controller
controller
,
String
propertyName
)
throws
PropertyNotFoundException
{
if
(
dynamicPropertyMap
==
null
||
!
this
.
dynamicPropertyMap
.
containsKey
(((
Servant
)
controller
).
getId
()))
{
//problem with empty map
createDynamicPropertiesForController
((
Servant
)
controller
);
}
HashMap
<
String
,
DynamicProperty
>
map
=
dynamicPropertyMap
.
get
(((
Servant
)
controller
).
getId
());
if
(
map
!=
null
)
{
DynamicProperty
dynamicProperty
=
map
.
get
(
propertyName
);
if
(
dynamicProperty
!=
null
)
{
return
dynamicProperty
.
getId
();
}
}
PropertyNotFoundException
pnfe
=
new
PropertyNotFoundException
(
propertyName
,
controller
.
getType
(),
controller
.
getName
(),
getClass
().
getCanonicalName
(),
"getDynamicProperty"
);
throw
pnfe
;
}
/**
* Used to clone a property
* @param controllerType
...
...
src/main/java/fr/ill/ics/nscclient/ploty/PlotyInstance.java
0 → 100644
View file @
e06c9ce4
/*
* Nomad Instrument Control Software
*
* Copyright 2011 Institut Laue-Langevin
*
* Licensed under the EUPL, Version 1.1 only (the "License");
* You may not use this work except in compliance with the Licence.
* You may obtain a copy of the Licence at:
*
* http://joinup.ec.europa.eu/software/page/eupl
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the Licence is distributed on an "AS IS" basis,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the Licence for the specific language governing permissions and
* limitations under the Licence.
*/
package
fr.ill.ics.nscclient.ploty
;
import
java.io.IOException
;
import
java.nio.file.Files
;
import
java.nio.file.Paths
;
import
java.util.Iterator
;
import
java.util.LinkedHashSet
;
import
java.util.List
;
import
java.util.Set
;
import
fr.ill.ics.cameo.Application
;
import
fr.ill.ics.cameo.Application.Info
;
import
fr.ill.ics.cameo.ConnectionTimeout
;
import
fr.ill.ics.cameo.RequesterCreationException
;
import
fr.ill.ics.cameo.Server
;
import
fr.ill.ics.core.property.Property
;
import
fr.ill.ics.ploty.DataPlotMessages
;
import
fr.ill.ics.ploty.DataPlotMessages.Message
;
import
fr.ill.ics.ploty.DataPlotMessages.PlotPropertyDataMessage
;
import
fr.ill.ics.util.ConfigManager
;
/**
* Class providing access to the running Nomad server instances using Cameo.
*/
public
class
PlotyInstance
{
private
static
PlotyInstance
instance
;
private
Server
server
;
private
Application
.
Requester
plotRequester
;
private
Application
.
Subscriber
spySubcriber
;
private
Thread
subscriberThread
;
private
boolean
initOk
=
false
;
private
Set
<
PlotyNotification
>
pendingChanges
=
new
LinkedHashSet
<
PlotyNotification
>();
private
Set
<
SpyPlotChangeListener
>
spyPlotChangeListeners
=
new
LinkedHashSet
<
SpyPlotChangeListener
>();
private
static
final
String
PLOTY_NAME
=
"ploty2"
;
private
static
final
String
SPY_PLOT_DIRECTORY
=
"/offscreenImages/spy/"
;
private
static
final
String
MULTIPLOT_PLOT_DIRECTORY
=
"/offscreenImages/multiplot/"
;
public
static
PlotyInstance
getInstance
()
{
if
(
instance
==
null
)
{
instance
=
new
PlotyInstance
();
}
return
instance
;
}
public
boolean
init
()
{
try
{
Files
.
createDirectories
(
Paths
.
get
(
System
.
getenv
(
"HOME"
)
+
SPY_PLOT_DIRECTORY
));
Files
.
createDirectories
(
Paths
.
get
(
System
.
getenv
(
"HOME"
)
+
MULTIPLOT_PLOT_DIRECTORY
));
}
catch
(
IOException
e1
)
{
// TODO Auto-generated catch block
e1
.
printStackTrace
();
}
// Get the server endpoint.
String
serverEndpoint
=
ConfigManager
.
getInstance
().
getServerEndpoint
();
// Connect to the server.
server
=
new
Server
(
serverEndpoint
);
Application
.
Instance
ploty
=
null
;
if
(
ConfigManager
.
getInstance
().
getString
(
ConfigManager
.
CLIENT_TYPE_PROPERTY
).
contains
(
"main"
))
{
try
{
ploty
=
server
.
connect
(
PLOTY_NAME
);
}
catch
(
ConnectionTimeout
e
)
{
System
.
err
.
println
(
"Timeout while connecting "
+
PLOTY_NAME
);
return
false
;
}
}
else
{
List
<
Info
>
info
=
server
.
getApplicationInfos
(
PLOTY_NAME
);
if
(
info
.
isEmpty
()
==
false
)
{
server
.
killAllAndWaitFor
(
PLOTY_NAME
);
}
// start ploty2 on remote
try
{
ploty
=
server
.
start
(
PLOTY_NAME
);
}
catch
(
ConnectionTimeout
e
)
{
System
.
err
.
println
(
"Timeout while starting "
+
PLOTY_NAME
);
return
false
;
}
}
// Create the requester.
try
{
plotRequester
=
Application
.
Requester
.
create
(
ploty
,
"plot_requester"
);
}
catch
(
RequesterCreationException
e
)
{
System
.
err
.
println
(
"Problem to connect to the ploty plot_requester responder"
);
return
false
;
}
// Create the requester.
spySubcriber
=
Application
.
Subscriber
.
create
(
ploty
,
"spy_publisher"
);
// Start the thread.
subscriberThread
=
new
Thread
(
new
Runnable
()
{
@Override
public
void
run
()
{
// Receive data
while
(
true
)
{
// Blocking call.
byte
[][]
data
=
spySubcriber
.
receiveTwoParts
();
if
(
data
!=
null
)
{
try
{
if
(
DataPlotMessages
.
SpyMultiPlotMessage
.
parseFrom
(
data
[
0
]).
getType
()
==
fr
.
ill
.
ics
.
ploty
.
DataPlotMessages
.
SpyMultiPlotMessage
.
Type
.
SpyChange
)
{
notifySpyPlotChanged
(
DataPlotMessages
.
SpyMultiPlotMessage
.
parseFrom
(
data
[
0
]).
getName
());
}
}
catch
(
IOException
e
)
{
System
.
err
.
println
(
"Cannot parse notification data change message"
);
}
}
else
{
break
;
}
}
}
});
subscriberThread
.
start
();
initOk
=
true
;
return
initOk
;
}
public
void
unsubscribe
()
{
System
.
out
.
println
(
"Unsubscribing spy plot change..."
);
// Stop the subscriber.
spySubcriber
.
cancel
();
// Join the thread.
try
{
subscriberThread
.
join
();
}
catch
(
InterruptedException
e
)
{
e
.
printStackTrace
();
}
// Terminate the subscriber.
spySubcriber
.
terminate
();
if
(
ConfigManager
.
getInstance
().
getString
(
ConfigManager
.
CLIENT_TYPE_PROPERTY
).
contains
(
"remote"
))
{
server
.
killAllAndWaitFor
(
PLOTY_NAME
);
}
System
.
out
.
println
(
"Unsubscribed from the spy plot change"
);
}
public
void
restorePlots
()
{
Message
messageFirst
=
DataPlotMessages
.
Message
.
newBuilder
().
setType
(
DataPlotMessages
.
Message
.
Type
.
RestorePlots
).
build
();
plotRequester
.
send
(
messageFirst
.
toByteArray
());
}
public
void
closePlots
()
{
Message
messageFirst
=
DataPlotMessages
.
Message
.
newBuilder
().
setType
(
DataPlotMessages
.
Message
.
Type
.
ClosePlots
).
build
();
plotRequester
.
send
(
messageFirst
.
toByteArray
());
}
public
void
plot
(
String
plotkey
,
List
<
String
>
key
,
List
<
String
>
legend_key
,
List
<
String
>
color
,
List
<
Integer
>
datax_id
,
List
<
Integer
>
datay_id
,
List
<
Integer
>
dataz_id
,
List
<
Boolean
>
errorBars
,
String
windowTitle
,
String
plotTitle
,
String
xAxisTitle
,
String
yAxisTitle
,
Property
titleProperty
,
Property
xAxisTitleProperty
,
Property
yAxisTitleProperty
,
List
<
Integer
>
dataState_id
,
List
<
Integer
>
xSlice_id
,
List
<
Integer
>
ySlice_id
,
List
<
Integer
>
zSlice_id
,
List
<
Integer
>
wSlice_id
,
List
<
Integer
>
maxXSlice_id
,
List
<
Integer
>
maxYSlice_id
,
List
<
Integer
>
maxZSlice_id
,
List
<
Integer
>
maxWSlice_id
,
List
<
Integer
>
xPhysicalSize_id
,
List
<
Integer
>
yPhysicalSize_id
,
List
<
Integer
>
optimizationResultFound_id
,
List
<
Integer
>
optimizationResultValue_id
,
List
<
String
>
optimizationColor
,
List
<
Integer
>
optimizationFitFound_id
,
List
<
Integer
>
optimizationFitArray_id
,
List
<
String
>
optimizationFitColor
,
Integer
use_grid_id
,
Integer
use_log_id
,
Integer
color_map_id
,
Integer
min_color_limit_id
,
Integer
max_color_limit_id
,
Integer
nbRois_id
,
List
<
Integer
>
roiX0_parentId
,
List
<
Integer
>
roiY0_parentId
,
List
<
Integer
>
roiX1_parentId
,
List
<
Integer
>
roiY1_parentId
,
List
<
Integer
>
roiSum_parentId
,
Integer
roiSelectedPlot_id
,
Integer
roiSelectedRoi_id
,
Integer
roiSelectedX0_id
,
Integer
roiSelectedY0_id
,
Integer
roiSelectedX1_id
,
Integer
roiSelectedY1_id
,
Integer
roiCommandAdd_id
,
Integer
roiCommandRaz_id
,
Integer
countActivated_id
)
{
if
(
initOk
==
false
)
return
;
int
titleid
=
0
;
if
(
titleProperty
!=
null
)
{
titleid
=
titleProperty
.
getPropertyID
();
}
int
xaxistitleid
=
0
;
if
(
xAxisTitleProperty
!=
null
)
{
xaxistitleid
=
xAxisTitleProperty
.
getPropertyID
();
}
int
yaxistitleid
=
0
;
if
(
yAxisTitleProperty
!=
null
)
{
yaxistitleid
=
yAxisTitleProperty
.
getPropertyID
();
}
Message
messageFirst
=
DataPlotMessages
.
Message
.
newBuilder
().
setType
(
DataPlotMessages
.
Message
.
Type
.
PlotPropertyData
).
build
();
PlotPropertyDataMessage
messageSecond
=
DataPlotMessages
.
PlotPropertyDataMessage
.
newBuilder
().
setPlotkey
(
plotkey
).
addAllKeys
(
key
).
addAllLegendKeys
(
legend_key
).
addAllColors
(
color
).
addAllDataxIds
(
datax_id
).
addAllDatayIds
(
datay_id
).
addAllDatazIds
(
dataz_id
).
addAllPlottypeId
(
dataState_id
).
addAllXId
(
xSlice_id
).
addAllYId
(
ySlice_id
).
addAllChannelId
(
zSlice_id
).
addAllSliceId
(
wSlice_id
).
addAllMaxXId
(
maxXSlice_id
).
addAllMaxYId
(
maxYSlice_id
).
addAllMaxChannelId
(
maxZSlice_id
).
addAllMaxSliceId
(
maxWSlice_id
).
addAllXphysicalSizeId
(
xPhysicalSize_id
).
addAllYphysicalSizeId
(
yPhysicalSize_id
).
addAllErrorBars
(
errorBars
).
addAllOptimizationResultFoundId
(
optimizationResultFound_id
).
addAllOptimizationResultXId
(
optimizationResultValue_id
).
addAllOptimizationResultColor
(
optimizationColor
).
addAllOptimizationFitFoundId
(
optimizationFitFound_id
).
addAllOptimizationFitYId
(
optimizationFitArray_id
).
addAllOptimizationFitColor
(
optimizationFitColor
).
setUseGridId
(
use_grid_id
).
setUseLogId
(
use_log_id
).
setColorMapId
(
color_map_id
).
setMinColorLimitId
(
min_color_limit_id
).
setMaxColorLimitId
(
max_color_limit_id
).
setWindowTitle
(
windowTitle
).
setPlotTitle
(
plotTitle
).
setXaxisTitle
(
yAxisTitle
).
setYaxisTitle
(
yAxisTitle
).
setTitleId
(
titleid
).
setXaxisTitleId
(
xaxistitleid
).
setYaxisTitleId
(
yaxistitleid
).
setNbRoisId
(
nbRois_id
).
addAllX0ParentId
(
roiX0_parentId
).
addAllY0ParentId
(
roiY0_parentId
).
addAllX1ParentId
(
roiX1_parentId
).
addAllY1ParentId
(
roiY1_parentId
).
addAllSumParentId
(
roiSum_parentId
).
setSelectedPlotId
(
roiSelectedPlot_id
).
setSelectedRoiId
(
roiSelectedRoi_id
).
setSelectedX0Id
(
roiSelectedX0_id
).
setSelectedY0Id
(
roiSelectedY0_id
).
setSelectedX1Id
(
roiSelectedX1_id
).
setSelectedY1Id
(
roiSelectedY1_id
).
setCommandRoiAddId
(
roiCommandAdd_id
).
setCommandRoiRazId
(
roiCommandRaz_id
).
setCountActivatedId
(
countActivated_id
).
build
();
plotRequester
.
sendTwoParts
(
messageFirst
.
toByteArray
(),
messageSecond
.
toByteArray
());
}
private
void
updatePendingChanges
(
PlotyNotification
change
)
{
// We do not add the change at the end of the list if it is already there.
// We used to move it at the end but some priority inversion could lead to problems.
if
(!
pendingChanges
.
contains
(
change
))
{
pendingChanges
.
add
(
change
);
}
else
{
//System.out.println("Not adding " + change);
}
}
private
void
notifySpyPlotChanged
(
String
name
)
{
synchronized
(
pendingChanges
)
{
PlotyNotification
change
=
new
PlotyNotification
(
name
);
updatePendingChanges
(
change
);
}
}
public
void
addSpyPlotChangeListener
(
SpyPlotChangeListener
listener
)
{
synchronized
(
spyPlotChangeListeners
)
{
if
(!
spyPlotChangeListeners
.
contains
(
listener
))
{
spyPlotChangeListeners
.
add
(
listener
);
}
}
}
public
void
removeSpyPlotChangeListener
(
SpyPlotChangeListener
listener
)
{
synchronized
(
spyPlotChangeListeners
)
{
if
(
spyPlotChangeListeners
.
contains
(
listener
))
{
spyPlotChangeListeners
.
remove
(
listener
);
}
}
}
private
void
notifySpyPlotChangeListeners
(
String
name
)
{
synchronized
(
spyPlotChangeListeners
)
{
Iterator
<
SpyPlotChangeListener
>
it
=
spyPlotChangeListeners
.
iterator
();
while
(
it
.
hasNext
())
{
SpyPlotChangeListener
listener
=
it
.
next
();
if
(
listener
.
getImageKey
().
contentEquals
(
name
))
{
listener
.
reloadImage
();
}
}
}
}
public
void
readAndDispatch
()
{
// copy pending property changes to unblock any server calls
Set
<
PlotyNotification
>
changes
;
synchronized
(
pendingChanges
)
{
changes
=
new
LinkedHashSet
<
PlotyNotification
>(
pendingChanges
);
pendingChanges
.
clear
();
}
// notify property listeners
Iterator
<
PlotyNotification
>
c
=
changes
.
iterator
();
while
(
c
.
hasNext
())
{
PlotyNotification
data
=
c
.
next
();
notifySpyPlotChangeListeners
(
data
.
getName
());
}
}
}
src/main/java/fr/ill/ics/nscclient/ploty/PlotyNotification.java
0 → 100644
View file @
e06c9ce4
/*
* Nomad Instrument Control Software
*
* Copyright 2011 Institut Laue-Langevin
*
* Licensed under the EUPL, Version 1.1 only (the "License");
* You may not use this work except in compliance with the Licence.
* You may obtain a copy of the Licence at:
*
* http://joinup.ec.europa.eu/software/page/eupl
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the Licence is distributed on an "AS IS" basis,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the Licence for the specific language governing permissions and
* limitations under the Licence.
*/
package
fr.ill.ics.nscclient.ploty
;
public
class
PlotyNotification
{
String
name
;
public
PlotyNotification
(
String
aName
)
{
name
=
aName
;
}
String
getName
()
{
return
name
;
}
}
src/main/java/fr/ill/ics/nscclient/ploty/SpyPlotChangeListener.java
0 → 100644
View file @
e06c9ce4
/*
* Nomad Instrument Control Software
*
* Copyright 2011 Institut Laue-Langevin
*
* Licensed under the EUPL, Version 1.1 only (the "License");
* You may not use this work except in compliance with the Licence.
* You may obtain a copy of the Licence at:
*
* http://joinup.ec.europa.eu/software/page/eupl
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the Licence is distributed on an "AS IS" basis,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the Licence for the specific language governing permissions and
* limitations under the Licence.
*/
package
fr.ill.ics.nscclient.ploty
;
public
interface
SpyPlotChangeListener
{
public
void
reloadImage
();
public
String
getImageKey
();
}
\ No newline at end of file
src/main/java/fr/ill/ics/nscclient/sessionmanagement/ServerSessionManager.java
View file @
e06c9ce4
...
...
@@ -33,11 +33,13 @@ import fr.ill.ics.nscclient.dataprovider.ServantManagerAccessor;
import
fr.ill.ics.nscclient.log.LogSubscriber
;
import
fr.ill.ics.nscclient.notification.DataChangeSubscriber
;
import
fr.ill.ics.nscclient.notification.commandzone.ServerCommandZoneEventManager
;
import
fr.ill.ics.nscclient.ploty.PlotyInstance
;
import
fr.ill.ics.nscclient.servant.ConfigurationManager
;
import
fr.ill.ics.nscclient.servant.ConfigurationManager.LoadFailure
;
import
fr.ill.ics.nscclient.sessionmanagement.SessionManager.ClientAlreadyLaunchedException
;
import
fr.ill.ics.nscclient.survey.SurveySubscriberImpl
;
import
fr.ill.ics.nscclient.variable.VariableManagerAccessor
;
import
fr.ill.ics.util.ConfigManager
;
public
class
ServerSessionManager
{
...
...
@@ -149,6 +151,11 @@ public class ServerSessionManager {
LogSubscriber
.
getInstance
(
serverId
).
unsubscribe
();
SurveySubscriberImpl
.
getInstance
(
serverId
).
unsubscribe
();
DataChangeSubscriber
.
getInstance
(
serverId
).
unsubscribe
();
if
(
serverId
.
equals
(
"real"
))
{
if
(
ConfigManager
.
getInstance
().
getPlotyVersion
()
==
2
)
{
PlotyInstance
.
getInstance
().
unsubscribe
();
}
}
DataAccessor
.
getInstance
(
serverId
).
reset
();
ServantManagerAccessor
.
getInstance
(
serverId
).
reset
();
...
...
@@ -182,6 +189,16 @@ public class ServerSessionManager {
}
catch
(
Exception
e
)
{
System
.
out
.
println
(
"Cannot unsubscribe data change"
);
}
if
(
serverId
.
equals
(
"real"
))
{
if
(
ConfigManager
.
getInstance
().
getPlotyVersion
()
==
2
)
{
try
{
PlotyInstance
.
getInstance
().
unsubscribe
();
}
catch
(
Exception
e
)
{
System
.
out
.
println
(
"Cannot unsubscribe data change"
);
}
}
}
}
public
ServantManagerAccessor
getServantManagerAccessor
()
{
...
...
src/main/java/fr/ill/ics/ploty/DataPlotMessages.java
0 → 100644
View file @
e06c9ce4
This diff is collapsed.
Click to expand it.
src/main/java/fr/ill/ics/util/ConfigManager.java
View file @
e06c9ce4
...
...
@@ -615,7 +615,15 @@ public class ConfigManager {
}
return
false
;
}
public
int
getPlotyVersion
()
{
String
value
=
getStringOrNothingAtAll
(
"plotyVersion"
);
if
(
value
!=
null
)
{
return
Integer
.
valueOf
(
value
);
}
return
1
;
}
public
boolean
isRemoteClient
()
{
return
remoteClient
;
}
...
...
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