Commit c498f783 authored by Jørgen Lind's avatar Jørgen Lind
Browse files

Update python apis

Add overloads allowing create and open to be called only with url. And
also add GlobalState functionality
parent 1db1107a
......@@ -26,6 +26,8 @@ pybind11_add_module(core MODULE SYSTEM
PyVolumeDataLayoutDescriptor.h
PyVolumeSampler.cpp
PyVolumeSampler.h
PyGlobalState.cpp
PyGlobalState.h
)
if (MSVC AND (CMAKE_CXX_COMPILER_ID STREQUAL "Clang"))
......
......@@ -46,6 +46,8 @@ PyGlobal::initModule(py::module& m)
OpenOptions_ConnectionType_.value("Http" , OpenOptions::ConnectionType::Http , OPENVDS_DOCSTRING(OpenOptions_ConnectionType_Http));
OpenOptions_ConnectionType_.value("VDSFile" , OpenOptions::ConnectionType::VDSFile , OPENVDS_DOCSTRING(OpenOptions_ConnectionType_VDSFile));
OpenOptions_ConnectionType_.value("InMemory" , OpenOptions::ConnectionType::InMemory , OPENVDS_DOCSTRING(OpenOptions_ConnectionType_InMemory));
OpenOptions_ConnectionType_.value("Other" , OpenOptions::ConnectionType::Other , OPENVDS_DOCSTRING(OpenOptions_ConnectionType_Other));
OpenOptions_ConnectionType_.value("ConnectionTypeCount" , OpenOptions::ConnectionType::ConnectionTypeCount, OPENVDS_DOCSTRING(OpenOptions_ConnectionType_ConnectionTypeCount));
// AWSOpenOptions
py::class_<AWSOpenOptions, OpenOptions, std::unique_ptr<AWSOpenOptions>>
......@@ -137,14 +139,17 @@ PyGlobal::initModule(py::module& m)
m.def("createOpenOptions" , static_cast<native::OpenOptions *(*)(native::StringWrapper, native::StringWrapper, native::Error &)>(&CreateOpenOptions), py::arg("url"), py::arg("connectionString"), py::arg("error"), OPENVDS_DOCSTRING(CreateOpenOptions));
m.def("open" , static_cast<native::VDSHandle(*)(native::StringWrapper, native::StringWrapper, native::Error &)>(&Open), py::arg("url"), py::arg("connectionString"), py::arg("error"), OPENVDS_DOCSTRING(Open));
m.def("open" , static_cast<native::VDSHandle(*)(const native::OpenOptions &, native::Error &)>(&Open), py::arg("options"), py::arg("error"), OPENVDS_DOCSTRING(Open_2));
m.def("open" , static_cast<native::VDSHandle(*)(native::IOManager *, native::Error &)>(&Open), py::arg("ioManager"), py::arg("error"), OPENVDS_DOCSTRING(Open_3));
m.def("open" , static_cast<native::VDSHandle(*)(native::StringWrapper, native::Error &)>(&Open), py::arg("url"), py::arg("error"), OPENVDS_DOCSTRING(Open_2));
m.def("open" , static_cast<native::VDSHandle(*)(const native::OpenOptions &, native::Error &)>(&Open), py::arg("options"), py::arg("error"), OPENVDS_DOCSTRING(Open_3));
m.def("open" , static_cast<native::VDSHandle(*)(native::IOManager *, native::Error &)>(&Open), py::arg("ioManager"), py::arg("error"), OPENVDS_DOCSTRING(Open_4));
m.def("create" , static_cast<native::VDSHandle(*)(native::StringWrapper, native::StringWrapper, const native::VolumeDataLayoutDescriptor &, VectorWrapper<native::VolumeDataAxisDescriptor>, VectorWrapper<native::VolumeDataChannelDescriptor>, const native::MetadataReadAccess &, native::Error &)>(&Create), py::arg("url"), py::arg("connectionString"), py::arg("layoutDescriptor"), py::arg("axisDescriptors"), py::arg("channelDescriptors"), py::arg("metadata"), py::arg("error"), OPENVDS_DOCSTRING(Create));
m.def("create" , static_cast<native::VDSHandle(*)(const native::OpenOptions &, const native::VolumeDataLayoutDescriptor &, VectorWrapper<native::VolumeDataAxisDescriptor>, VectorWrapper<native::VolumeDataChannelDescriptor>, const native::MetadataReadAccess &, native::Error &)>(&Create), py::arg("options"), py::arg("layoutDescriptor"), py::arg("axisDescriptors"), py::arg("channelDescriptors"), py::arg("metadata"), py::arg("error"), OPENVDS_DOCSTRING(Create_2));
m.def("create" , static_cast<native::VDSHandle(*)(native::IOManager *, const native::VolumeDataLayoutDescriptor &, VectorWrapper<native::VolumeDataAxisDescriptor>, VectorWrapper<native::VolumeDataChannelDescriptor>, const native::MetadataReadAccess &, native::Error &)>(&Create), py::arg("ioManager"), py::arg("layoutDescriptor"), py::arg("axisDescriptors"), py::arg("channelDescriptors"), py::arg("metadata"), py::arg("error"), OPENVDS_DOCSTRING(Create_3));
m.def("create" , static_cast<native::VDSHandle(*)(native::StringWrapper, const native::VolumeDataLayoutDescriptor &, VectorWrapper<native::VolumeDataAxisDescriptor>, VectorWrapper<native::VolumeDataChannelDescriptor>, const native::MetadataReadAccess &, native::Error &)>(&Create), py::arg("url"), py::arg("layoutDescriptor"), py::arg("axisDescriptors"), py::arg("channelDescriptors"), py::arg("metadata"), py::arg("error"), OPENVDS_DOCSTRING(Create_2));
m.def("create" , static_cast<native::VDSHandle(*)(const native::OpenOptions &, const native::VolumeDataLayoutDescriptor &, VectorWrapper<native::VolumeDataAxisDescriptor>, VectorWrapper<native::VolumeDataChannelDescriptor>, const native::MetadataReadAccess &, native::Error &)>(&Create), py::arg("options"), py::arg("layoutDescriptor"), py::arg("axisDescriptors"), py::arg("channelDescriptors"), py::arg("metadata"), py::arg("error"), OPENVDS_DOCSTRING(Create_3));
m.def("create" , static_cast<native::VDSHandle(*)(native::IOManager *, const native::VolumeDataLayoutDescriptor &, VectorWrapper<native::VolumeDataAxisDescriptor>, VectorWrapper<native::VolumeDataChannelDescriptor>, const native::MetadataReadAccess &, native::Error &)>(&Create), py::arg("ioManager"), py::arg("layoutDescriptor"), py::arg("axisDescriptors"), py::arg("channelDescriptors"), py::arg("metadata"), py::arg("error"), OPENVDS_DOCSTRING(Create_4));
m.def("getLayout" , static_cast<native::VolumeDataLayout *(*)(native::VDSHandle)>(&GetLayout), py::arg("handle"), OPENVDS_DOCSTRING(GetLayout));
m.def("getAccessManager" , static_cast<native::VolumeDataAccessManager *(*)(native::VDSHandle)>(&GetAccessManager), py::arg("handle"), OPENVDS_DOCSTRING(GetAccessManager));
m.def("close" , static_cast<void(*)(native::VDSHandle)>(&Close), py::arg("handle"), OPENVDS_DOCSTRING(Close));
m.def("getGlobalState" , static_cast<native::GlobalState *(*)()>(&GetGlobalState), OPENVDS_DOCSTRING(GetGlobalState));
//AUTOGEN-END
Error_.def(py::init<>());
Error_.def("__repr__", [](native::Error const& self){ std::string tmp = std::to_string(self.code); return std::string("Error(code=") + tmp + ", string='" + self.string + "')"; });
......@@ -182,6 +187,20 @@ PyGlobal::initModule(py::module& m)
}
return handle;
}, py::arg("url"), py::arg("connectionString"), OPENVDS_DOCSTRING(Open));
m.def("open" , [](const std::string &url, Error &error)
{
return native::Open(url, error);
}, py::arg("url"), py::arg("error"), OPENVDS_DOCSTRING(Open_2));
m.def("open" , [](const std::string &url)
{
native::Error err;
auto handle = native::Open(url, err);
if (err.code)
{
throw std::runtime_error(err.string);
}
return handle;
}, py::arg("url"), OPENVDS_DOCSTRING(Open_2));
m.def("open" , [](const native::OpenOptions &opt)
{
native::Error err;
......@@ -191,7 +210,7 @@ PyGlobal::initModule(py::module& m)
throw std::runtime_error(err.string);
}
return handle;
}, py::arg("options"), OPENVDS_DOCSTRING(Open_2));
}, py::arg("options"), OPENVDS_DOCSTRING(Open_3));
m.def("open" , [](native::IOManager *mgr)
{
native::Error err;
......@@ -201,7 +220,7 @@ PyGlobal::initModule(py::module& m)
throw std::runtime_error(err.string);
}
return handle;
}, py::arg("ioManager"), OPENVDS_DOCSTRING(Open_3));
}, py::arg("ioManager"), OPENVDS_DOCSTRING(Open_4));
m.def("create" , [](const std::string &url, const std::string &connectionString, const native::VolumeDataLayoutDescriptor &layout, std::vector<native::VolumeDataAxisDescriptor> axisdesc, std::vector<native::VolumeDataChannelDescriptor> channeldesc, const native::MetadataReadAccess &metadata, native::Error &error)
{
return native::Create(url, connectionString, layout , axisdesc, channeldesc, metadata, error);
......@@ -216,6 +235,20 @@ PyGlobal::initModule(py::module& m)
}
return handle;
}, py::arg("url"), py::arg("connectionString"), py::arg("layoutDescriptor"), py::arg("axisDescriptors"), py::arg("channelDescriptors"), py::arg("metadata"), OPENVDS_DOCSTRING(Create));
m.def("create" , [](const std::string &url, const native::VolumeDataLayoutDescriptor &layout, std::vector<native::VolumeDataAxisDescriptor> axisdesc, std::vector<native::VolumeDataChannelDescriptor> channeldesc, const native::MetadataReadAccess &metadata, native::Error &error)
{
return native::Create(url, layout , axisdesc, channeldesc, metadata, error);
}, py::arg("url"), py::arg("layoutDescriptor"), py::arg("axisDescriptors"), py::arg("channelDescriptors"), py::arg("metadata"), py::arg("error"), OPENVDS_DOCSTRING(Create_2));
m.def("create" , [](const std::string &url, const native::VolumeDataLayoutDescriptor &layout, std::vector<native::VolumeDataAxisDescriptor> axisdesc, std::vector<native::VolumeDataChannelDescriptor> channeldesc, const native::MetadataReadAccess &metadata)
{
native::Error err;
auto handle = native::Create(url, layout , axisdesc, channeldesc, metadata, err);
if (err.code)
{
throw std::runtime_error(err.string);
}
return handle;
}, py::arg("url"), py::arg("layoutDescriptor"), py::arg("axisDescriptors"), py::arg("channelDescriptors"), py::arg("metadata"), OPENVDS_DOCSTRING(Create_2));
m.def("create" , [](const native::OpenOptions &opt, const native::VolumeDataLayoutDescriptor &layout, std::vector<native::VolumeDataAxisDescriptor> axisdesc, std::vector<native::VolumeDataChannelDescriptor> channeldesc, const native::MetadataReadAccess &metadata)
{
native::Error err;
......@@ -225,7 +258,7 @@ PyGlobal::initModule(py::module& m)
throw std::runtime_error(err.string);
}
return handle;
}, py::arg("options"), py::arg("layoutDescriptor"), py::arg("axisDescriptors"), py::arg("channelDescriptors"), py::arg("metadata"), OPENVDS_DOCSTRING(Create_2));
}, py::arg("options"), py::arg("layoutDescriptor"), py::arg("axisDescriptors"), py::arg("channelDescriptors"), py::arg("metadata"), OPENVDS_DOCSTRING(Create_3));
m.def("create" , [](native::IOManager *mgr, const native::VolumeDataLayoutDescriptor &layout, std::vector<native::VolumeDataAxisDescriptor> axisdesc, std::vector<native::VolumeDataChannelDescriptor> channeldesc, const native::MetadataReadAccess &metadata)
{
native::Error err;
......@@ -235,6 +268,6 @@ PyGlobal::initModule(py::module& m)
throw std::runtime_error(err.string);
}
return handle;
}, py::arg("ioManager"), py::arg("layoutDescriptor"), py::arg("axisDescriptors"), py::arg("channelDescriptors"), py::arg("metadata"), OPENVDS_DOCSTRING(Create_3));
}, py::arg("ioManager"), py::arg("layoutDescriptor"), py::arg("axisDescriptors"), py::arg("channelDescriptors"), py::arg("metadata"), OPENVDS_DOCSTRING(Create_4));
}
......@@ -25,6 +25,7 @@
#include <OpenVDS/VolumeData.h>
#include <OpenVDS/VolumeDataAccess.h>
#include <OpenVDS/KnownMetadata.h>
#include <OpenVDS/GlobalState.h>
#include "generated_docstrings.h"
......
/****************************************************************************
** Copyright 2020 The Open Group
** Copyright 2020 Bluware, Inc.
**
** 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.
****************************************************************************/
#include "PyGlobalState.h"
using namespace native;
void
PyGlobalState::initModule(py::module& m)
{
//AUTOGEN-BEGIN
// GlobalState
py::class_<GlobalState, std::unique_ptr<GlobalState>>
GlobalState_(m,"GlobalState", OPENVDS_DOCSTRING(GlobalState));
GlobalState_.def("getBytesDownloaded" , static_cast<uint64_t(GlobalState::*)(OpenOptions::ConnectionType)>(&GlobalState::GetBytesDownloaded), py::arg("connectionType"), OPENVDS_DOCSTRING(GlobalState_GetBytesDownloaded));
GlobalState_.def("getChunksDownloaded" , static_cast<uint64_t(GlobalState::*)(OpenOptions::ConnectionType)>(&GlobalState::GetChunksDownloaded), py::arg("connectionType"), OPENVDS_DOCSTRING(GlobalState_GetChunksDownloaded));
GlobalState_.def("getBytesDecompressed" , static_cast<uint64_t(GlobalState::*)(OpenOptions::ConnectionType)>(&GlobalState::GetBytesDecompressed), py::arg("connectionType"), OPENVDS_DOCSTRING(GlobalState_GetBytesDecompressed));
GlobalState_.def("getChunksDecompressed" , static_cast<uint64_t(GlobalState::*)(OpenOptions::ConnectionType)>(&GlobalState::GetChunksDecompressed), py::arg("connectionType"), OPENVDS_DOCSTRING(GlobalState_GetChunksDecompressed));
//AUTOGEN-END
}
/****************************************************************************
** Copyright 2020 The Open Group
** Copyright 2020 Bluware, Inc.
**
** 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.
****************************************************************************/
#ifndef PYGLOBALSTATE_H_INCLUDED
#define PYGLOBALSTATE_H_INCLUDED
#include "PyGlobal.h"
class PyGlobalState
{
public:
static void initModule(py::module& m);
};
#endif
......@@ -32,6 +32,7 @@
#include "PyVolumeDataLayout.h"
#include "PyVolumeDataLayoutDescriptor.h"
#include "PyVolumeSampler.h"
#include "PyGlobalState.h"
PYBIND11_MODULE(core, m) {
......@@ -48,4 +49,5 @@ PYBIND11_MODULE(core, m) {
PyVolumeDataLayout::initModule(m);
PyVolumeDataLayoutDescriptor::initModule(m);
PyVolumeSampler::initModule(m);
PyGlobalState::initModule(m);
}
......@@ -230,6 +230,26 @@ Returns:
the VolumeDataAccessManager)doc";
static const char *__doc_OpenVDS_Create_2 =
R"doc(Create a new VDS This is a simple wrapper that uses an empty
connectionString
Parameters:
-----------
url :
The url scheme specific to each cloud provider Available schemes
are s3:// azure://
error :
If an error occured, the error code and message will be written to
this output parameter
Returns:
--------
The VDS handle that can be used to get the VolumeDataLayout and
the VolumeDataAccessManager)doc";
static const char *__doc_OpenVDS_Create_3 =
R"doc(Create a new VDS
Parameters:
......@@ -247,7 +267,7 @@ Returns:
The VDS handle that can be used to get the VolumeDataLayout and
the VolumeDataAccessManager)doc";
static const char *__doc_OpenVDS_Create_3 =
static const char *__doc_OpenVDS_Create_4 =
R"doc(<summary> Create a new VDS
</summary> <param name="ioManager"> The IOManager for the connection,
......@@ -453,6 +473,13 @@ static const char *__doc_OpenVDS_GetDataBlockDimensionality = R"doc()doc";
static const char *__doc_OpenVDS_GetDataBlockDimensionality_Dimensionality = R"doc()doc";
static const char *__doc_OpenVDS_GetGlobalState =
R"doc(Get the GlobalState interface
Returns:
--------
A pointer to the GlobalState interface)doc";
static const char *__doc_OpenVDS_GetLODSize =
R"doc(Get the number of voxels at a particular LOD from a voxel range
(ranges are exclusive).
......@@ -491,6 +518,65 @@ Returns:
--------
The VolumeDataLayout of the VDS)doc";
static const char *__doc_OpenVDS_GlobalState = R"doc(Object that contains global runtime data)doc";
static const char *__doc_OpenVDS_GlobalState_2 = R"doc()doc";
static const char *__doc_OpenVDS_GlobalState_GetBytesDecompressed =
R"doc(Get the global amount of decompressed bytes. This amount might be
smaller than the amount of downloaded bytes because of a small header
pr chunk. It can also be larger for non compressed data sets since
chunks can be cached.
Parameters:
-----------
connectionType :
$Returns:
--------
Amount of decompressed bytes served the process.)doc";
static const char *__doc_OpenVDS_GlobalState_GetBytesDownloaded =
R"doc(Get the global amount of downloaded bytes from a cloud vendor.
Parameters:
-----------
connectionType :
The counter to be retireved.
Returns:
--------
Global number of bytes downloaded from the connection. This does
not include any http header data.)doc";
static const char *__doc_OpenVDS_GlobalState_GetChunksDecompressed =
R"doc(Get the global count of decompressed chunks.
Parameters:
-----------
connectionType :
The counter to be retireved.
Returns:
--------
Number of chunks decompressed.)doc";
static const char *__doc_OpenVDS_GlobalState_GetChunksDownloaded =
R"doc(Get the global count of downloaded chunks.
Parameters:
-----------
connectionType :
The counter to be retireved.
Returns:
--------
Number of chunks downloaded.)doc";
static const char *__doc_OpenVDS_GoogleOpenOptions = R"doc(Options for opening a VDS in Google Cloud Storage)doc";
static const char *__doc_OpenVDS_GoogleOpenOptions_GoogleOpenOptions = R"doc()doc";
......@@ -1160,6 +1246,26 @@ Returns:
the VolumeDataAccessManager)doc";
static const char *__doc_OpenVDS_Open_2 =
R"doc(Open an existing VDS. This is a simple wrapper that uses an empty
connectionString
Parameters:
-----------
url :
The url scheme specific to each cloud provider Available schemes
are s3:// azure://
error :
If an error occured, the error code and message will be written to
this output parameter
Returns:
--------
The VDS handle that can be used to get the VolumeDataLayout and
the VolumeDataAccessManager)doc";
static const char *__doc_OpenVDS_Open_3 =
R"doc(Open an existing VDS
Parameters:
......@@ -1177,7 +1283,7 @@ Returns:
The VDS handle that can be used to get the VolumeDataLayout and
the VolumeDataAccessManager)doc";
static const char *__doc_OpenVDS_Open_3 =
static const char *__doc_OpenVDS_Open_4 =
R"doc(Open an existing VDS
Parameters:
......@@ -1206,12 +1312,16 @@ static const char *__doc_OpenVDS_OpenOptions_ConnectionType_Azure = R"doc()doc";
static const char *__doc_OpenVDS_OpenOptions_ConnectionType_AzurePresigned = R"doc()doc";
static const char *__doc_OpenVDS_OpenOptions_ConnectionType_ConnectionTypeCount = R"doc()doc";
static const char *__doc_OpenVDS_OpenOptions_ConnectionType_GoogleStorage = R"doc()doc";
static const char *__doc_OpenVDS_OpenOptions_ConnectionType_Http = R"doc()doc";
static const char *__doc_OpenVDS_OpenOptions_ConnectionType_InMemory = R"doc()doc";
static const char *__doc_OpenVDS_OpenOptions_ConnectionType_Other = R"doc()doc";
static const char *__doc_OpenVDS_OpenOptions_ConnectionType_VDSFile = R"doc()doc";
static const char *__doc_OpenVDS_OpenOptions_OpenOptions = R"doc()doc";
......
......@@ -375,7 +375,7 @@ def generate_function(node, all_, output, indent, parent_prefix, context):
output.append(line)
# Generate read-only property for get...() and is...() methods with no arguments
pname = getpyname(fnname[3:]) if fnname.startswith("get") else getpyname(fnname[2:]) if fnname.startswith("is") else ""
if pname and not argnames and not pname.isdigit() and not pname == "LODLevels":
if pname and not argnames and not pname.isdigit() and not pname == "LODLevels" and not pname == "globalState":
getter_code = """.def_property_readonly("{}", &{}, {});""".format(
pname,
getnativename(node, all_),
......@@ -616,4 +616,4 @@ if __name__ == '__main__':
generate_all(args)
except NoFilenamesError:
print("Usage:\n{} <clang options> [header files ...]".format(sys.argv[0]))
\ No newline at end of file
......@@ -313,6 +313,22 @@ OPENVDS_EXPORT OpenOptions* CreateOpenOptions(StringWrapper url, StringWrapper c
/// </returns>
OPENVDS_EXPORT VDSHandle Open(StringWrapper url, StringWrapper connectionString, Error& error);
/// <summary>
/// Open an existing VDS.
/// This is a simple wrapper that uses an empty connectionString
/// </summary>
/// <param name="url">
/// The url scheme specific to each cloud provider
/// Available schemes are s3:// azure://
/// </param>
/// <param name="error">
/// If an error occured, the error code and message will be written to this output parameter
/// </param>
/// <returns>
/// The VDS handle that can be used to get the VolumeDataLayout and the VolumeDataAccessManager
/// </returns>
inline VDSHandle Open(StringWrapper url, Error& error) { std::string connectionString; return Open(url, connectionString, error); }
/// <summary>
/// Open an existing VDS
/// </summary>
......@@ -360,6 +376,26 @@ OPENVDS_EXPORT VDSHandle Open(IOManager*ioManager, Error &error);
/// </returns>
OPENVDS_EXPORT VDSHandle Create(StringWrapper url, StringWrapper connectionString, VolumeDataLayoutDescriptor const& layoutDescriptor, VectorWrapper<VolumeDataAxisDescriptor> axisDescriptors, VectorWrapper<VolumeDataChannelDescriptor> channelDescriptors, MetadataReadAccess const& metadata, Error& error);
/// <summary>
/// Create a new VDS
/// This is a simple wrapper that uses an empty connectionString
/// </summary>
/// <param name="url">
/// The url scheme specific to each cloud provider
/// Available schemes are s3:// azure://
/// </param>
/// <param name="error">
/// If an error occured, the error code and message will be written to this output parameter
/// </param>
/// <returns>
/// The VDS handle that can be used to get the VolumeDataLayout and the VolumeDataAccessManager
/// </returns>
inline VDSHandle Create(StringWrapper url, VolumeDataLayoutDescriptor const& layoutDescriptor, VectorWrapper<VolumeDataAxisDescriptor> axisDescriptors, VectorWrapper<VolumeDataChannelDescriptor> channelDescriptors, MetadataReadAccess const& metadata, Error& error)
{
std::string connectionString;
return Create(url, connectionString, layoutDescriptor, axisDescriptors, channelDescriptors, metadata, error);
}
/// <summary>
/// Create a new VDS
/// </summary>
......
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment