Skip to content
Snippets Groups Projects
Commit 387f7c84 authored by cramakri's avatar cramakri
Browse files

LMS-1887 Development of the download replica command.

SVN: 18858
parent 8b3451dc
No related branches found
No related tags found
No related merge requests found
Showing with 360 additions and 47 deletions
......@@ -16,6 +16,7 @@
package ch.systemsx.cisd.cina.client.util.cli;
import java.io.File;
import java.util.ArrayList;
import ch.systemsx.cisd.args4j.Option;
......@@ -33,8 +34,8 @@ public class CommandGetReplica extends
{
static class CommandGetReplicaArguments extends GlobalArguments
{
@Option(name = "o", longName = "output", usage = "Output folder")
private String outputFolder = "";
@Option(name = "o", longName = "output", usage = "Path for output")
private String output = "";
public ArrayList<String> getReplicaIdentifiers()
{
......@@ -49,9 +50,9 @@ public class CommandGetReplica extends
return replicaIds;
}
public String getOutputFolder()
public String getOutput()
{
return outputFolder;
return output;
}
@Override
......@@ -87,23 +88,32 @@ public class CommandGetReplica extends
@Override
protected ResultCode doExecute(ICinaUtilities component)
{
// Create the output directory
File outputDir = getOutputDir();
outputDir.mkdirs();
// Find all datasets connected to this sample
for (String sampleCode : arguments.getReplicaIdentifiers())
{
executeForSampleCode(component, sampleCode);
ReplicaDownloader downloader =
new ReplicaDownloader(component, sampleCode, outputDir);
downloader.download();
}
return ResultCode.OK;
}
protected void executeForSampleCode(ICinaUtilities component, String sampleCode)
private File getOutputDir()
{
// Find all datasets connected to this sample
component.listDataSetsForSampleCode(sampleCode);
// List<DataSet> dataSets = component.listDataSetsForSampleCode(sampleCode);
// Download the raw-data dataset
// Download the ...
// List<Experiment> results =
// component.listVisibleExperiments(arguments.getReplicaIdentifier());
File outputDir;
if (arguments.getOutput().length() > 0)
{
// create the directory specified by output
outputDir = new File(arguments.getOutput());
} else
{
outputDir = new File(".");
}
return outputDir;
}
}
......
/*
* Copyright 2010 ETH Zuerich, CISD
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package ch.systemsx.cisd.cina.client.util.cli;
import java.io.File;
import java.util.List;
import ch.systemsx.cisd.cina.client.util.v1.ICinaUtilities;
import ch.systemsx.cisd.cina.shared.constants.BundleStructureConstants;
import ch.systemsx.cisd.cina.shared.constants.CinaConstants;
import ch.systemsx.cisd.openbis.dss.client.api.v1.FileInfoDssDownloader;
import ch.systemsx.cisd.openbis.dss.client.api.v1.IDataSetDss;
import ch.systemsx.cisd.openbis.dss.generic.shared.api.v1.FileInfoDssDTO;
import ch.systemsx.cisd.openbis.generic.shared.api.v1.dto.DataSet;
/**
* Utility class for downloading replicas.
*
* @author Chandrasekhar Ramakrishnan
*/
class ReplicaDownloader
{
private final ICinaUtilities component;
private final String replicaCode;
private final File outputDir;
ReplicaDownloader(ICinaUtilities component, String code, File outputDir)
{
this.component = component;
this.replicaCode = code;
this.outputDir = outputDir;
}
private static class DownloaderListener implements
FileInfoDssDownloader.FileInfoDssDownloaderListener
{
public void willDownload(FileInfoDssDTO fileInfo)
{
System.out.println("downloading " + fileInfo.getPathInDataSet());
}
public void willCreateDirectory(FileInfoDssDTO fileInfo)
{
System.out.println("mkdir " + fileInfo.getPathInDataSet());
}
public void didFinish()
{
System.out.println("Finished.");
}
}
protected void download()
{
// Find all datasets connected to this sample
List<DataSet> dataSets = component.listDataSetsForSampleCode(replicaCode);
DataSet mostRecentMetadata = null;
for (DataSet dataSet : dataSets)
{
String typeCode = dataSet.getDataSetTypeCode();
if (typeCode.equals(CinaConstants.RAW_IMAGES_DATA_SET_TYPE_CODE))
{
// Download the raw images
downloadDataSet(dataSet, BundleStructureConstants.RAW_IMAGES_FOLDER_NAME);
}
if (typeCode.equals(CinaConstants.METADATA_DATA_SET_TYPE_CODE))
{
if (null == mostRecentMetadata)
{
mostRecentMetadata = dataSet;
} else if (mostRecentMetadata.getRegistrationDate().compareTo(
dataSet.getRegistrationDate()) < 0)
{
// This element is newer than the current value
mostRecentMetadata = dataSet;
}
}
}
// Download the most recent metadata data set
if (null != mostRecentMetadata)
{
downloadDataSet(mostRecentMetadata, BundleStructureConstants.METADATA_FOLDER_NAME);
}
}
private void downloadDataSet(DataSet dataSet, String subfolderName)
{
IDataSetDss dataSetDss = component.getDataSet(dataSet.getCode());
FileInfoDssDTO[] fileInfos = dataSetDss.listFiles("/", true);
FileInfoDssDownloader downloader =
new FileInfoDssDownloader(dataSetDss, fileInfos,
new File(outputDir, subfolderName), new DownloaderListener());
downloader.downloadFiles();
}
}
......@@ -20,6 +20,7 @@ import java.util.List;
import ch.systemsx.cisd.common.exceptions.EnvironmentFailureException;
import ch.systemsx.cisd.common.exceptions.UserFailureException;
import ch.systemsx.cisd.openbis.dss.client.api.v1.IDataSetDss;
import ch.systemsx.cisd.openbis.generic.shared.api.v1.dto.DataSet;
import ch.systemsx.cisd.openbis.generic.shared.api.v1.dto.Experiment;
import ch.systemsx.cisd.openbis.generic.shared.api.v1.dto.Sample;
......@@ -90,6 +91,18 @@ public interface ICinaUtilities
public List<DataSet> listDataSetsForSampleCode(String sampleCode) throws IllegalStateException,
EnvironmentFailureException, UserFailureException;
/**
* Return
*
* @param dataSetCode The code of the data set to download
* @throws IllegalStateException Thrown if the user has not yet been authenticated.
* @throws EnvironmentFailureException Thrown in cases where it is not possible to connect to
* the server or if there are multiple samples with the given code.
* @throws UserFailureException Thrown if no sample exists with the specified code.
*/
public IDataSetDss getDataSet(String dataSetCode) throws IllegalStateException,
EnvironmentFailureException, UserFailureException;
/**
* Logs the current user out.
*/
......
......@@ -26,6 +26,10 @@ import ch.systemsx.cisd.common.api.client.ServiceFinder;
import ch.systemsx.cisd.common.exceptions.AuthorizationFailureException;
import ch.systemsx.cisd.common.exceptions.EnvironmentFailureException;
import ch.systemsx.cisd.common.exceptions.UserFailureException;
import ch.systemsx.cisd.openbis.dss.client.api.v1.IDataSetDss;
import ch.systemsx.cisd.openbis.dss.client.api.v1.IDssComponent;
import ch.systemsx.cisd.openbis.dss.client.api.v1.impl.DssComponent;
import ch.systemsx.cisd.openbis.dss.client.api.v1.impl.DssServiceRpcFactory;
import ch.systemsx.cisd.openbis.generic.shared.IETLLIMSService;
import ch.systemsx.cisd.openbis.generic.shared.OpenBisServiceFactory;
import ch.systemsx.cisd.openbis.generic.shared.ResourceNames;
......@@ -94,6 +98,12 @@ public class CinaUtilitiesFacade implements ICinaUtilities
return new OpenBisServiceFactory(openBISURL, ResourceNames.ETL_SERVICE_URL).createService();
}
private static IDssComponent createDssComponent(IETLLIMSService openbisService,
String sessionTokenOrNull)
{
return new DssComponent(openbisService, new DssServiceRpcFactory(), sessionTokenOrNull);
}
/** The interface for accessing the remote services. */
private final IGeneralInformationService generalInformationService;
......@@ -128,9 +138,27 @@ public class CinaUtilitiesFacade implements ICinaUtilities
* @param sessionTokenOrNull A session token, if the user has already logged in, or null
* otherwise.
*/
protected CinaUtilitiesFacade(IGeneralInformationService generalInformationService,
private CinaUtilitiesFacade(IGeneralInformationService generalInformationService,
IETLLIMSService openbisService, String sessionTokenOrNull)
{
this(generalInformationService, openbisService, createDssComponent(openbisService,
sessionTokenOrNull), sessionTokenOrNull);
}
/**
* Internal constructor, also used for testing.
*
* @param generalInformationService A proxy to the openBIS application server's general
* information service
* @param openbisService A proxy to the openBIS application server's ETLLIMS Service
* @param dssComponent A dss component facade for interacting with dss services
* @param sessionTokenOrNull A session token, if the user has already logged in, or null
* otherwise.
*/
protected CinaUtilitiesFacade(IGeneralInformationService generalInformationService,
IETLLIMSService openbisService, IDssComponent dssComponent, String sessionTokenOrNull)
{
this.generalInformationService = generalInformationService;
this.openbisService = openbisService;
......@@ -140,11 +168,29 @@ public class CinaUtilitiesFacade implements ICinaUtilities
} else
{
this.state =
new AuthenticatedState(generalInformationService, openbisService,
new AuthenticatedState(generalInformationService, openbisService, dssComponent,
sessionTokenOrNull);
}
}
/**
* FOR TESTING ONLY <br>
* This method makes it possible to hand in a mocked dssComponent -- it should only be used for
* testing and it therefore marked deprecated.
*
* @deprecated
*/
@Deprecated
void loginForTesting(String user, String password, IDssComponent dssComponent)
throws AuthorizationFailureException, EnvironmentFailureException
{
// login and transition to the authenticated state
state.login(user, password);
state =
new AuthenticatedState(generalInformationService, openbisService, dssComponent,
state.getSessionToken());
}
/**
* Authenticates the <code>user</code> with given <code>password</code>.
*
......@@ -159,6 +205,7 @@ public class CinaUtilitiesFacade implements ICinaUtilities
state.login(user, password);
state =
new AuthenticatedState(generalInformationService, openbisService,
createDssComponent(openbisService, state.getSessionToken()),
state.getSessionToken());
}
......@@ -197,6 +244,12 @@ public class CinaUtilitiesFacade implements ICinaUtilities
{
return state.listDataSetsForSampleCode(sampleCode);
}
public IDataSetDss getDataSet(String dataSetCode) throws IllegalStateException,
EnvironmentFailureException, UserFailureException
{
return state.getDataSet(dataSetCode);
}
}
/**
......@@ -245,6 +298,12 @@ abstract class AbstractCinaFacadeState implements ICinaUtilities
throw new IllegalStateException("Please log in");
}
public IDataSetDss getDataSet(String dataSetCode) throws IllegalStateException,
EnvironmentFailureException, UserFailureException
{
throw new IllegalStateException("Please log in");
}
/**
* Authenticates the <code>user</code> with given <code>password</code>.
*
......@@ -317,17 +376,20 @@ class AuthenticatedState extends AbstractCinaFacadeState
private final IETLLIMSService openbisService;
private final IDssComponent dssComponent;
private final int generalInformationServiceMinorVersion;
/**
* @param service
*/
AuthenticatedState(IGeneralInformationService service, IETLLIMSService openbisService,
String sessionToken)
IDssComponent dssComponent, String sessionToken)
{
super(service);
this.sessionToken = sessionToken;
this.openbisService = openbisService;
this.dssComponent = dssComponent;
this.generalInformationServiceMinorVersion = service.getMinorVersion();
}
......@@ -428,4 +490,11 @@ class AuthenticatedState extends AbstractCinaFacadeState
return service.listDataSets(sessionToken, samples);
}
@Override
public IDataSetDss getDataSet(String dataSetCode) throws IllegalStateException,
EnvironmentFailureException, UserFailureException
{
return dssComponent.getDataSet(dataSetCode);
}
}
\ No newline at end of file
......@@ -16,22 +16,32 @@
package ch.systemsx.cisd.cina.client.util.cli;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.util.ArrayList;
import java.util.GregorianCalendar;
import org.jmock.Expectations;
import org.jmock.Mockery;
import org.testng.AssertJUnit;
import org.testng.annotations.AfterMethod;
import org.testng.annotations.BeforeMethod;
import org.testng.annotations.Test;
import ch.systemsx.cisd.base.tests.AbstractFileSystemTestCase;
import ch.systemsx.cisd.cina.client.util.v1.ICinaUtilities;
import ch.systemsx.cisd.cina.shared.constants.CinaConstants;
import ch.systemsx.cisd.common.exceptions.EnvironmentFailureException;
import ch.systemsx.cisd.openbis.dss.client.api.cli.ICommand;
import ch.systemsx.cisd.openbis.dss.client.api.cli.ResultCode;
import ch.systemsx.cisd.openbis.dss.client.api.v1.IDataSetDss;
import ch.systemsx.cisd.openbis.dss.client.api.v1.IDssComponent;
import ch.systemsx.cisd.openbis.dss.generic.shared.api.v1.FileInfoDssBuilder;
import ch.systemsx.cisd.openbis.dss.generic.shared.api.v1.FileInfoDssDTO;
import ch.systemsx.cisd.openbis.generic.shared.IETLLIMSService;
import ch.systemsx.cisd.openbis.generic.shared.api.v1.IGeneralInformationService;
import ch.systemsx.cisd.openbis.generic.shared.api.v1.dto.DataSet;
import ch.systemsx.cisd.openbis.generic.shared.api.v1.dto.DataSet.DataSetInitializer;
import ch.systemsx.cisd.openbis.generic.shared.api.v1.dto.Sample;
import ch.systemsx.cisd.openbis.generic.shared.api.v1.dto.Sample.SampleInitializer;
import ch.systemsx.cisd.openbis.generic.shared.api.v1.dto.SearchCriteria;
......@@ -41,7 +51,7 @@ import ch.systemsx.cisd.openbis.generic.shared.api.v1.dto.SearchCriteria.MatchCl
/**
* @author Chandrasekhar Ramakrishnan
*/
public class CommandGetReplicaTest extends AssertJUnit
public class CommandGetReplicaTest extends AbstractFileSystemTestCase
{
private final class MockCommandGetReplica extends CommandGetReplica
{
......@@ -50,7 +60,7 @@ public class CommandGetReplicaTest extends AssertJUnit
{
facade =
ch.systemsx.cisd.cina.client.util.v1.impl.CinaUtilitiesFacadeTest.createFacade(
service, openbisService, USER_ID, PASSWORD);
service, openbisService, dssComponent, USER_ID, PASSWORD);
return facade;
}
}
......@@ -69,12 +79,20 @@ public class CommandGetReplicaTest extends AssertJUnit
private IETLLIMSService openbisService;
private IDssComponent dssComponent;
private IDataSetDss dataSetDss;
@Override
@BeforeMethod
public void setUp()
public void setUp() throws IOException
{
super.setUp();
context = new Mockery();
service = context.mock(IGeneralInformationService.class);
openbisService = context.mock(IETLLIMSService.class);
dssComponent = context.mock(IDssComponent.class);
dataSetDss = context.mock(IDataSetDss.class);
}
@AfterMethod
......@@ -111,68 +129,138 @@ public class CommandGetReplicaTest extends AssertJUnit
MatchClauseAttribute.CODE, sampleCode));
ArrayList<Sample> samples = new ArrayList<Sample>();
SampleInitializer initializer = new SampleInitializer();
initializer.setCode(sampleCode);
initializer.setId((long) 1);
initializer.setIdentifier("SPACE/" + sampleCode);
initializer.setPermId("PERM-ID");
initializer.setSampleTypeCode("SAMPLE-TYPE");
initializer.setSampleTypeId((long) 1);
Sample sample = new Sample(initializer);
samples.add(sample);
SampleInitializer sampInitializer = new SampleInitializer();
sampInitializer.setCode(sampleCode);
sampInitializer.setId((long) 1);
sampInitializer.setIdentifier("SPACE/" + sampleCode);
sampInitializer.setPermId("PERM-ID");
sampInitializer.setSampleTypeCode("SAMPLE-TYPE");
sampInitializer.setSampleTypeId((long) 1);
samples.add(new Sample(sampInitializer));
one(service).searchForSamples(SESSION_TOKEN, searchCriteria);
will(returnValue(samples));
ArrayList<DataSet> dataSets = new ArrayList<DataSet>();
DataSetInitializer dsInitializer = new DataSetInitializer();
dsInitializer.setCode(sampleCode + "-RAW-IMAGES");
dsInitializer.setDataSetTypeCode(CinaConstants.RAW_IMAGES_DATA_SET_TYPE_CODE);
dsInitializer.setRegistrationDate(new GregorianCalendar(2010, 0, 1).getTime());
dataSets.add(new DataSet(dsInitializer));
dsInitializer = new DataSetInitializer();
dsInitializer.setCode(sampleCode + "-METADATA-OLD");
dsInitializer.setDataSetTypeCode(CinaConstants.METADATA_DATA_SET_TYPE_CODE);
dsInitializer.setRegistrationDate(new GregorianCalendar(2010, 0, 1).getTime());
dataSets.add(new DataSet(dsInitializer));
dsInitializer = new DataSetInitializer();
dsInitializer.setCode(sampleCode + "-METADATA-NEW");
dsInitializer.setDataSetTypeCode(CinaConstants.METADATA_DATA_SET_TYPE_CODE);
dsInitializer.setRegistrationDate(new GregorianCalendar(2010, 1, 1).getTime());
dataSets.add(new DataSet(dsInitializer));
one(service).listDataSets(SESSION_TOKEN, samples);
will(returnValue(dataSets));
}
});
}
@Test
public void testCodePath()
private void setupDownloadDataSetExpectations(final String sampleCode) throws IOException
{
setupAuthenticationExpectations();
setupListDataSetsExpectations("REPLICA-ID");
final File parent = new File("sourceTest/java/ch/systemsx/cisd/cina/client/util/cli/");
ArrayList<FileInfoDssDTO> rawImagesInfos =
getFileInfosForPath(new File(parent, "RawImages"));
final FileInfoDssDTO[] rawImagesInfosArray =
(rawImagesInfos.size() > 0) ? rawImagesInfos
.toArray(new FileInfoDssDTO[rawImagesInfos.size()]) : new FileInfoDssDTO[0];
ICommand command = new MockCommandGetReplica();
ArrayList<FileInfoDssDTO> metadataInfos = getFileInfosForPath(new File(parent, "Metadata"));
ResultCode exitCode = command.execute(new String[]
{ "-s", "url", "-u", USER_ID, "-p", PASSWORD, "REPLICA-ID" });
final FileInfoDssDTO[] metadataInfosArray =
(metadataInfos.size() > 0) ? metadataInfos.toArray(new FileInfoDssDTO[metadataInfos
.size()]) : new FileInfoDssDTO[0];
assertEquals(ResultCode.OK, exitCode);
context.assertIsSatisfied();
context.checking(new Expectations()
{
{
one(dssComponent).getDataSet(sampleCode + "-RAW-IMAGES");
will(returnValue(dataSetDss));
one(dataSetDss).listFiles("/", true);
will(returnValue(rawImagesInfosArray));
one(dataSetDss).getFile("/ReplicaRawImages/Image.txt");
will(returnValue(new FileInputStream(new File(parent,
"RawImages/ReplicaRawImages/Image.txt"))));
one(dssComponent).getDataSet(sampleCode + "-METADATA-NEW");
will(returnValue(dataSetDss));
one(dataSetDss).listFiles("/", true);
will(returnValue(metadataInfosArray));
one(dataSetDss).getFile("/ReplicaMetadata/Metadata.txt");
will(returnValue(new FileInputStream(new File(parent,
"Metadata/ReplicaMetadata/Metadata.txt"))));
}
});
}
private ArrayList<FileInfoDssDTO> getFileInfosForPath(File file) throws IOException
{
ArrayList<FileInfoDssDTO> fileInfos = new ArrayList<FileInfoDssDTO>();
if (false == file.exists())
{
return fileInfos;
}
String path = file.getCanonicalPath();
if (false == file.isDirectory())
{
path = file.getParentFile().getCanonicalPath();
}
FileInfoDssBuilder builder = new FileInfoDssBuilder(path, path);
builder.appendFileInfosForFile(file, fileInfos, true);
return fileInfos;
}
@Test
public void testOutputFolder()
public void testCodePath() throws IOException
{
setupAuthenticationExpectations();
setupListDataSetsExpectations("REPLICA-ID");
setupDownloadDataSetExpectations("REPLICA-ID");
ICommand command = new MockCommandGetReplica();
ResultCode exitCode = command.execute(new String[]
{ "-s", "url", "-u", USER_ID, "-p", PASSWORD, "-o", "Foo.bundle/", "REPLICA-ID" });
File outputFolder = new File(workingDirectory, "Foo.bundle/");
ResultCode exitCode =
command.execute(new String[]
{ "-s", "url", "-u", USER_ID, "-p", PASSWORD, "-o", outputFolder.getPath(),
"REPLICA-ID" });
assertEquals(ResultCode.OK, exitCode);
context.assertIsSatisfied();
}
@Test
public void testMultipleReplicas()
public void testMultipleReplicas() throws IOException
{
setupAuthenticationExpectations();
setupListDataSetsExpectations("REPLICA-ID1");
setupListDataSetsExpectations("REPLICA-ID2");
setupDownloadDataSetExpectations("REPLICA-ID1");
setupDownloadDataSetExpectations("REPLICA-ID2");
ICommand command = new MockCommandGetReplica();
ResultCode exitCode = command.execute(new String[]
{ "-s", "url", "-u", USER_ID, "-p", PASSWORD, "REPLICA-ID1", "REPLICA-ID2" });
File outputFolder = new File(workingDirectory, "Foo.bundle/");
ResultCode exitCode =
command.execute(new String[]
{ "-s", "url", "-u", USER_ID, "-p", PASSWORD, "-o", outputFolder.getPath(),
"REPLICA-ID1", "REPLICA-ID2" });
assertEquals(ResultCode.OK, exitCode);
context.assertIsSatisfied();
......
This is replica metadata.
\ No newline at end of file
This is an image.
\ No newline at end of file
......@@ -26,6 +26,7 @@ import org.testng.annotations.AfterMethod;
import org.testng.annotations.BeforeMethod;
import org.testng.annotations.Test;
import ch.systemsx.cisd.openbis.dss.client.api.v1.IDssComponent;
import ch.systemsx.cisd.openbis.generic.shared.IETLLIMSService;
import ch.systemsx.cisd.openbis.generic.shared.api.v1.IGeneralInformationService;
import ch.systemsx.cisd.openbis.generic.shared.api.v1.dto.Experiment;
......@@ -182,7 +183,7 @@ public class CinaUtilitiesFacadeTest extends AssertJUnit
public static CinaUtilitiesFacade createFacade(IGeneralInformationService service,
IETLLIMSService openbisService)
{
CinaUtilitiesFacade facade = new CinaUtilitiesFacade(service, openbisService, null);
CinaUtilitiesFacade facade = new CinaUtilitiesFacade(service, openbisService, null, null);
return facade;
}
......@@ -192,8 +193,22 @@ public class CinaUtilitiesFacadeTest extends AssertJUnit
public static CinaUtilitiesFacade createFacade(IGeneralInformationService service,
IETLLIMSService openbisService, String userId, String password)
{
CinaUtilitiesFacade facade = new CinaUtilitiesFacade(service, openbisService, null);
CinaUtilitiesFacade facade = new CinaUtilitiesFacade(service, openbisService, null, null);
facade.login(userId, password);
return facade;
}
/**
* Utility method to create a CinaUtilitiesFacade object with a mocked dssComponent for testing.
*/
@SuppressWarnings("deprecation")
public static CinaUtilitiesFacade createFacade(IGeneralInformationService service,
IETLLIMSService openbisService, IDssComponent dssComponent, String userId,
String password)
{
CinaUtilitiesFacade facade = new CinaUtilitiesFacade(service, openbisService, null, null);
// The loginForTesting method is marked deprecated to discourage accidental use.
facade.loginForTesting(userId, password, dssComponent);
return facade;
}
}
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment