Kaydet (Commit) 3d5d6017 authored tarafından Ariel Constenla-Haile's avatar Ariel Constenla-Haile

i121935 - UCB: new "addProperty" and "removeProperty" commands

üst e7b7b284
......@@ -459,6 +459,65 @@ published service Content
</p>
</td>
</tr>
<tr>
<td>addProperty</td>
<td>void</td>
<td><type>PropertyCommandArgument</type> aCmdArg</td>
<td>
Adds a new properties to the content.
<p>
<member>PropertyCommandArgument::Property</member>
contains information about the property to be added.
<member>PropertyCommandArgument::DefaultValue</member>
may contain the default value for the property. Its type must
match the one specified in <member scope="com::sun::star::beans">Property::Type</member>.
</p>
<p>Note that the dynamic properties must be kept persistent. The
service <type>Store</type> (UCB persistence service) may be used to
implement this.</p>
<p><b>Important:</b> The implementation must at least support
adding properties of the following basic data types:</p>
<p>
<ul>
<li>boolean
<li>char
<li>byte
<li>string
<li>short
<li>long
<li>hyper
<li>float
<li>double
</ul>
</p>
<p>
Raises a <type scope="com::sun::star::beans">PropertyExistException</type>
if a property with the same name already exists;
<type scope="com::sun::star::beans">IllegalTypeException</type>
if the property has an unsupported type;
<type scope="com::sun::star::lang">IllegalArgumentException</type>
if the Name of the property is empty.</p>
<blockquote>
Note: This command replaces the deprecated interface method
<member scope="com::sun::star::beans">XPropertyContainer::addProperty</member>.
</blockquote>
</td>
</tr>
<tr>
<td>removeProperty</td>
<td>void</td>
<td>string PropertyName</td>
<td>Removes the properties from the content.
<p>Raises a <type scope="com::sun::star::beans">UnknownPropertyException</type>
if the property does not exist;
<type scope="com::sun::star::beans">NotRemoveableException</type>
if the property is not removable.</p>
<blockquote>
Note: This command replaces the deprecated interface method
<member scope="com::sun::star::beans">XPropertyContainer::removeProperty</member>.
</blockquote>
</td>
</tr>
</tbody>
</table>
......@@ -804,7 +863,7 @@ published service Content
<td>TargetURL</td>
<td>string</td>
<td>for contents that are links to other contents, contains the URL of
the target content</td>
the target content</td>
</tr>
<tr>
<td>TimeLimitStore</td>
......@@ -815,7 +874,7 @@ published service Content
<td>UserName</td>
<td>string</td>
<td>contains a user name. (e.g. the user name needed to access a
POP3-Account)</td>
POP3-Account)</td>
</tr>
<tr>
<td>VerificationMode</td>
......
/**************************************************************
*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you 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 __com_sun_star_ucb_PropertyCommandArgument_idl__
#define __com_sun_star_ucb_PropertyCommandArgument_idl__
#include <com/sun/star/beans/Property.idl>
module com { module sun { module star { module ucb {
/** The argument for the "addProperty" command.
@see XCommandProcessor
@since Apache OpenOffice 4.0
*/
struct PropertyCommandArgument
{
/** The property that the command has to add.
*/
com::sun::star::beans::Property Property;
/** The default value of the property.
*/
any DefaultValue;
};
}; }; }; };
#endif
......@@ -37,7 +37,7 @@ IDLFILES=\
AlreadyInitializedException.idl\
AnyCompareFactory.idl\
AuthenticationRequest.idl\
URLAuthenticationRequest.idl\
CHAOSProgressStart.idl\
CachedContentResultSet.idl\
CachedContentResultSetFactory.idl\
CachedContentResultSetStub.idl\
......@@ -46,7 +46,6 @@ IDLFILES=\
CachedDynamicResultSetFactory.idl\
CachedDynamicResultSetStub.idl\
CachedDynamicResultSetStubFactory.idl\
CHAOSProgressStart.idl\
CertificateValidationRequest.idl\
Command.idl\
CommandAbortedException.idl\
......@@ -83,6 +82,8 @@ IDLFILES=\
Error.idl\
ExpandContentProvider.idl\
ExportStreamInfo.idl\
FTPContent.idl\
FTPContentProvider.idl\
FetchError.idl\
FetchResult.idl\
FileContent.idl\
......@@ -91,10 +92,8 @@ IDLFILES=\
FolderList.idl\
FolderListCommand.idl\
FolderListEntry.idl\
FTPContent.idl\
FTPContentProvider.idl\
GlobalTransferCommandArgument.idl\
GIOContentProvider.idl\
GlobalTransferCommandArgument.idl\
GnomeVFSContentProvider.idl\
GnomeVFSDocumentContent.idl\
GnomeVFSFolderContent.idl\
......@@ -108,6 +107,7 @@ IDLFILES=\
HierarchyFolderContent.idl\
HierarchyLinkContent.idl\
HierarchyRootFolderContent.idl\
IOErrorCode.idl\
IllegalIdentifierException.idl\
InsertCommandArgument.idl\
InteractiveAppException.idl\
......@@ -117,8 +117,8 @@ IDLFILES=\
InteractiveFileIOException.idl\
InteractiveIOException.idl\
InteractiveLockingException.idl\
InteractiveLockingLockedException.idl\
InteractiveLockingLockExpiredException.idl\
InteractiveLockingLockedException.idl\
InteractiveLockingNotLockedException.idl\
InteractiveNetworkConnectException.idl\
InteractiveNetworkException.idl\
......@@ -128,12 +128,11 @@ IDLFILES=\
InteractiveNetworkResolveNameException.idl\
InteractiveNetworkWriteException.idl\
InteractiveWrongMediumException.idl\
IOErrorCode.idl\
Link.idl\
ListAction.idl\
ListActionType.idl\
ListenerAlreadySetException.idl\
ListEvent.idl\
ListenerAlreadySetException.idl\
Lock.idl\
LockDepth.idl\
LockEntry.idl\
......@@ -159,6 +158,7 @@ IDLFILES=\
PostCommandArgument2.idl\
Priority.idl\
PropertiesManager.idl\
PropertyCommandArgument.idl\
PropertySetRegistry.idl\
PropertyValueInfo.idl\
PropertyValueState.idl\
......@@ -191,10 +191,11 @@ IDLFILES=\
TransferInfo.idl\
TransferResult.idl\
TransientDocumentsContentProvider.idl\
TransientDocumentsRootContent.idl\
TransientDocumentsDocumentContent.idl\
TransientDocumentsFolderContent.idl\
TransientDocumentsRootContent.idl\
TransientDocumentsStreamContent.idl\
URLAuthenticationRequest.idl\
UniversalContentBroker.idl\
UnsupportedCommandException.idl\
UnsupportedDataSinkException.idl\
......@@ -262,7 +263,7 @@ IDLFILES=\
XSimpleFileAccess3.idl\
XSortedDynamicResultSetFactory.idl\
XSourceInitialization.idl\
XWebDAVCommandEnvironment.idl
XWebDAVCommandEnvironment.idl \
# ------------------------------------------------------------------
......
......@@ -26,6 +26,7 @@
#include <string.h>
#include "DAVProperties.hxx"
#include <rtl/ustrbuf.hxx>
using namespace http_dav_ucp;
......@@ -192,3 +193,42 @@ bool DAVProperties::isUCBDeadProperty( const SerfPropName & rName )
rName.nspace, "http://ucb.openoffice.org/dav/props/" )
== 0 ) );
}
bool DAVProperties::isUCBSpecialProperty(const rtl::OUString& rFullName, rtl::OUString& rParsedName)
{
sal_Int32 nLen = rFullName.getLength();
if ( nLen <= 0 ||
!rFullName.matchAsciiL( RTL_CONSTASCII_STRINGPARAM( "<prop:" ) ) ||
!rFullName.endsWithAsciiL( RTL_CONSTASCII_STRINGPARAM( "\">" ) ) )
return false;
sal_Int32 nStart = RTL_CONSTASCII_LENGTH( "<prop:" );
sal_Int32 nEnd = rFullName.indexOf( sal_Unicode( ' ' ), nStart );
if ( nEnd == -1 )
return false;
rtl::OUString sPropName = rFullName.copy( nStart, nEnd - nStart );
if ( !sPropName.getLength() )
return false;
// TODO skip whitespaces?
if ( !rFullName.matchAsciiL( RTL_CONSTASCII_STRINGPARAM( "xmlns:prop=\"" ), ++nEnd ) )
return false;
nStart = nEnd + RTL_CONSTASCII_LENGTH( "xmlns:prop=\"" );
nEnd = rFullName.indexOf( sal_Unicode( '"' ), nStart );
if ( nEnd != nLen - RTL_CONSTASCII_LENGTH( "\">" ) )
return false;
rtl::OUString sNamesp = rFullName.copy( nStart, nEnd - nStart );
if ( !( nLen = sNamesp.getLength() ) )
return false;
rtl::OUStringBuffer aBuff( sNamesp );
if ( sNamesp[nLen - 1] != '/' )
aBuff.append( sal_Unicode( '/' ) );
aBuff.append( sPropName );
rParsedName = aBuff.makeStringAndClear();
return rParsedName.getLength();
}
......@@ -50,6 +50,8 @@ struct DAVProperties
rtl::OUString & rFullName );
static bool isUCBDeadProperty( const SerfPropName & rName );
static bool isUCBSpecialProperty( const rtl::OUString & rFullName,
rtl::OUString & rParsedName );
};
} // namespace http_dav_ucp
......
......@@ -68,6 +68,7 @@
#include <com/sun/star/ucb/OpenCommandArgument2.hpp>
#include <com/sun/star/ucb/OpenMode.hpp>
#include <com/sun/star/ucb/PostCommandArgument2.hpp>
#include <com/sun/star/ucb/PropertyCommandArgument.hpp>
#include <com/sun/star/ucb/TransferInfo.hpp>
#include <com/sun/star/ucb/UnsupportedCommandException.hpp>
#include <com/sun/star/ucb/UnsupportedDataSinkException.hpp>
......@@ -769,6 +770,68 @@ uno::Any SAL_CALL Content::execute(
aRet = uno::makeAny( createNewContent( aArg ) );
}
else if ( aCommand.Name.equalsAsciiL( RTL_CONSTASCII_STRINGPARAM( "addProperty" )))
{
ucb::PropertyCommandArgument aPropArg;
if ( !( aCommand.Argument >>= aPropArg ))
{
ucbhelper::cancelCommandExecution(
uno::makeAny( lang::IllegalArgumentException(
rtl::OUString( RTL_CONSTASCII_USTRINGPARAM(
"Wrong argument type!" )),
static_cast< cppu::OWeakObject * >( this ),
-1 ) ),
Environment );
}
// TODO when/if XPropertyContainer is removed,
// the command execution can be canceled in addProperty
try
{
addProperty( aPropArg, Environment );
}
catch ( const beans::PropertyExistException &e )
{
ucbhelper::cancelCommandExecution( uno::makeAny( e ), Environment );
}
catch ( const beans::IllegalTypeException&e )
{
ucbhelper::cancelCommandExecution( uno::makeAny( e ), Environment );
}
catch ( const lang::IllegalArgumentException&e )
{
ucbhelper::cancelCommandExecution( uno::makeAny( e ), Environment );
}
}
else if ( aCommand.Name.equalsAsciiL( RTL_CONSTASCII_STRINGPARAM( "removeProperty" )))
{
rtl::OUString sPropName;
if ( !( aCommand.Argument >>= sPropName ) )
{
ucbhelper::cancelCommandExecution(
uno::makeAny( lang::IllegalArgumentException(
rtl::OUString( RTL_CONSTASCII_USTRINGPARAM(
"Wrong argument type!" )),
static_cast< cppu::OWeakObject * >( this ),
-1 ) ),
Environment );
}
// TODO when/if XPropertyContainer is removed,
// the command execution can be canceled in removeProperty
try
{
removeProperty( sPropName, Environment );
}
catch( const beans::UnknownPropertyException &e )
{
ucbhelper::cancelCommandExecution( uno::makeAny( e ), Environment );
}
catch( const beans::NotRemoveableException &e )
{
ucbhelper::cancelCommandExecution( uno::makeAny( e ), Environment );
}
}
else
{
//////////////////////////////////////////////////////////////////
......@@ -820,42 +883,53 @@ void SAL_CALL Content::abort( sal_Int32 /*CommandId*/ )
//
//=========================================================================
// virtual
void SAL_CALL Content::addProperty( const rtl::OUString& Name,
sal_Int16 Attributes,
const uno::Any& DefaultValue )
throw( beans::PropertyExistException,
beans::IllegalTypeException,
lang::IllegalArgumentException,
uno::RuntimeException )
void Content::addProperty( const com::sun::star::ucb::PropertyCommandArgument &aCmdArg,
const uno::Reference< ucb::XCommandEnvironment >& xEnv )
throw( beans::PropertyExistException,
beans::IllegalTypeException,
lang::IllegalArgumentException,
uno::RuntimeException )
{
// if ( m_bTransient )
// @@@ ???
const beans::Property aProperty = aCmdArg.Property;
const uno::Any aDefaultValue = aCmdArg.DefaultValue;
if ( !Name.getLength() )
throw lang::IllegalArgumentException();
// check property Name
if ( !aProperty.Name.getLength() )
throw lang::IllegalArgumentException(
rtl::OUString( RTL_CONSTASCII_USTRINGPARAM(
"\"addProperty\" with empty Property.Name")),
static_cast< ::cppu::OWeakObject * >( this ),
-1 );
// Check property type.
if ( !UCBDeadPropertyValue::supportsType( DefaultValue.getValueType() ) )
{
OSL_ENSURE( sal_False,
"Content::addProperty - Unsupported property type!" );
throw beans::IllegalTypeException();
}
if ( !UCBDeadPropertyValue::supportsType( aProperty.Type ) )
throw beans::IllegalTypeException(
rtl::OUString( RTL_CONSTASCII_USTRINGPARAM(
"\"addProperty\" unsupported Property.Type")),
static_cast< ::cppu::OWeakObject * >( this ) );
// check default value
if ( aDefaultValue.hasValue() && aDefaultValue.getValueType() != aProperty.Type )
throw beans::IllegalTypeException(
rtl::OUString( RTL_CONSTASCII_USTRINGPARAM(
"\"addProperty\" DefaultValue does not match Property.Type")),
static_cast< ::cppu::OWeakObject * >( this ) );
//////////////////////////////////////////////////////////////////////
// Make sure a property with the requested name does not already
// exist in dynamic and static(!) properties.
//////////////////////////////////////////////////////////////////////
// @@@ Need real command environment here, but where to get it from?
// XPropertyContainer interface should be replaced by
// XCommandProcessor commands!
uno::Reference< ucb::XCommandEnvironment > xEnv;
// Take into account special properties with custom namespace
// using <prop:the_propname xmlns:prop="the_namespace">
rtl::OUString aSpecialName;
bool bIsSpecial = DAVProperties::isUCBSpecialProperty( aProperty.Name, aSpecialName );
// Note: This requires network access!
if ( getPropertySetInfo( xEnv, sal_False /* don't cache data */ )
->hasPropertyByName( Name ) )
->hasPropertyByName( bIsSpecial ? aSpecialName : aProperty.Name ) )
{
// Property does already exist.
throw beans::PropertyExistException();
......@@ -865,7 +939,7 @@ void SAL_CALL Content::addProperty( const rtl::OUString& Name,
// Add a new dynamic property.
//////////////////////////////////////////////////////////////////////
ProppatchValue aValue( PROPSET, Name, DefaultValue );
ProppatchValue aValue( PROPSET, aProperty.Name, aDefaultValue );
std::vector< ProppatchValue > aProppatchValues;
aProppatchValues.push_back( aValue );
......@@ -887,7 +961,7 @@ void SAL_CALL Content::addProperty( const rtl::OUString& Name,
// Notify propertyset info change listeners.
beans::PropertySetInfoChangeEvent evt(
static_cast< cppu::OWeakObject * >( this ),
Name,
bIsSpecial ? aSpecialName : aProperty.Name,
-1, // No handle available
beans::PropertySetInfoChange::PROPERTY_INSERTED );
notifyPropertySetInfoChange( evt );
......@@ -899,8 +973,9 @@ void SAL_CALL Content::addProperty( const rtl::OUString& Name,
// Support for setting arbitrary dead properties is optional!
// Store property locally.
ContentImplHelper::addProperty(
Name, Attributes, DefaultValue );
ContentImplHelper::addProperty( bIsSpecial ? aSpecialName : aProperty.Name,
aProperty.Attributes,
aDefaultValue );
}
else
{
......@@ -917,9 +992,9 @@ void SAL_CALL Content::addProperty( const rtl::OUString& Name,
case NON_DAV:
// Store property locally.
ContentImplHelper::addProperty( Name,
Attributes,
DefaultValue );
ContentImplHelper::addProperty( bIsSpecial ? aSpecialName : aProperty.Name,
aProperty.Attributes,
aDefaultValue );
break;
default:
......@@ -946,25 +1021,19 @@ void SAL_CALL Content::addProperty( const rtl::OUString& Name,
}
}
//=========================================================================
// virtual
void SAL_CALL Content::removeProperty( const rtl::OUString& Name )
throw( beans::UnknownPropertyException,
beans::NotRemoveableException,
uno::RuntimeException )
void Content::removeProperty( const rtl::OUString& Name,
const uno::Reference< ucb::XCommandEnvironment >& xEnv )
throw( beans::UnknownPropertyException,
beans::NotRemoveableException,
uno::RuntimeException )
{
// @@@ Need real command environment here, but where to get it from?
// XPropertyContainer interface should be replaced by
// XCommandProcessor commands!
uno::Reference< ucb::XCommandEnvironment > xEnv;
#if 0
// @@@ REMOVEABLE z.Z. nicht richtig an der PropSetInfo gesetzt!!!
try
{
beans::Property aProp
= getPropertySetInfo( xEnv, sal_False /* don't cache data */ )
->getPropertyByName( Name );
= getPropertySetInfo( xEnv, sal_False /* don't cache data */ )
->getPropertyByName( Name );
if ( !( aProp.Attributes & beans::PropertyAttribute::REMOVEABLE ) )
{
......@@ -1027,20 +1096,20 @@ void SAL_CALL Content::removeProperty( const rtl::OUString& Name )
const ResourceType & rType = getResourceType( xEnv );
switch ( rType )
{
case UNKNOWN:
case DAV:
throw beans::UnknownPropertyException();
case NON_DAV:
// Try to remove property from local store.
ContentImplHelper::removeProperty( Name );
break;
default:
OSL_ENSURE( sal_False,
"Content::removeProperty - "
"Unsupported resource type!" );
break;
case UNKNOWN:
case DAV:
throw beans::UnknownPropertyException();
case NON_DAV:
// Try to remove property from local store.
ContentImplHelper::removeProperty( Name );
break;
default:
OSL_ENSURE( sal_False,
"Content::removeProperty - "
"Unsupported resource type!" );
break;
}
}
catch ( uno::Exception const & )
......@@ -1061,6 +1130,35 @@ void SAL_CALL Content::removeProperty( const rtl::OUString& Name )
}
}
// virtual
void SAL_CALL Content::addProperty( const rtl::OUString& Name,
sal_Int16 Attributes,
const uno::Any& DefaultValue )
throw( beans::PropertyExistException,
beans::IllegalTypeException,
lang::IllegalArgumentException,
uno::RuntimeException )
{
beans::Property aProperty;
aProperty.Name = Name;
aProperty.Type = DefaultValue.getValueType();
aProperty.Attributes = Attributes;
aProperty.Handle = -1;
addProperty( ucb::PropertyCommandArgument( aProperty, DefaultValue ),
uno::Reference< ucb::XCommandEnvironment >());
}
// virtual
void SAL_CALL Content::removeProperty( const rtl::OUString& Name )
throw( beans::UnknownPropertyException,
beans::NotRemoveableException,
uno::RuntimeException )
{
removeProperty( Name,
uno::Reference< ucb::XCommandEnvironment >() );
}
//=========================================================================
//
// XContentCreator methods.
......@@ -1779,11 +1877,14 @@ uno::Sequence< uno::Any > Content::setPropertyValues(
// Optional props.
//////////////////////////////////////////////////////////////
rtl::OUString aSpecialName;
bool bIsSpecial = DAVProperties::isUCBSpecialProperty( rName, aSpecialName );
if ( !xInfo.is() )
xInfo = getPropertySetInfo( xEnv,
sal_False /* don't cache data */ );
if ( !xInfo->hasPropertyByName( rName ) )
if ( !xInfo->hasPropertyByName( bIsSpecial ? aSpecialName : rName ) )
{
// Check, whether property exists. Skip otherwise.
// PROPPATCH::set would add the property automatically, which
......@@ -1832,7 +1933,7 @@ uno::Sequence< uno::Any > Content::setPropertyValues(
static_cast< cppu::OWeakObject * >( this ) );
}
if ( rName.equalsAsciiL(
RTL_CONSTASCII_STRINGPARAM( "CreatableContentsInfo" ) ) )
RTL_CONSTASCII_STRINGPARAM( "CreatableContentsInfo" ) ) )
{
// Read-only property!
aRet[ n ] <<= lang::IllegalAccessException(
......
......@@ -48,6 +48,7 @@ namespace com { namespace sun { namespace star { namespace sdbc {
namespace com { namespace sun { namespace star { namespace ucb {
struct OpenCommandArgument2;
struct PropertyCommandArgument;
struct PostCommandArgument2;
struct TransferInfo;
} } } }
......@@ -192,6 +193,21 @@ private:
const com::sun::star::uno::Reference<
com::sun::star::ucb::XCommandEnvironment >& Environment );
// XPropertyContainer replacement
void addProperty( const com::sun::star::ucb::PropertyCommandArgument &aCmdArg,
const com::sun::star::uno::Reference<
com::sun::star::ucb::XCommandEnvironment >& Environment )
throw( com::sun::star::beans::PropertyExistException,
com::sun::star::beans::IllegalTypeException,
com::sun::star::lang::IllegalArgumentException,
com::sun::star::uno::RuntimeException );
void removeProperty( const rtl::OUString& Name,
const com::sun::star::uno::Reference<
com::sun::star::ucb::XCommandEnvironment >& Environment )
throw( com::sun::star::beans::UnknownPropertyException,
com::sun::star::beans::NotRemoveableException,
com::sun::star::uno::RuntimeException );
public:
Content( const ::com::sun::star::uno::Reference<
::com::sun::star::lang::XMultiServiceFactory >& rxSMgr,
......
......@@ -38,6 +38,7 @@
#include <com/sun/star/ucb/OpenCommandArgument2.hpp>
#include <com/sun/star/ucb/InsertCommandArgument.hpp>
#include <com/sun/star/ucb/PostCommandArgument2.hpp>
#include <com/sun/star/ucb/PropertyCommandArgument.hpp>
#include <com/sun/star/ucb/TransferInfo.hpp>
#include <com/sun/star/uno/Sequence.hxx>
#include <com/sun/star/util/DateTime.hpp>
......@@ -529,7 +530,7 @@ uno::Sequence< ucb::CommandInfo > Content::getCommands(
{
osl::Guard< osl::Mutex > aGuard( m_aMutex );
uno::Sequence< ucb::CommandInfo > aCmdInfo( 8 );
uno::Sequence< ucb::CommandInfo > aCmdInfo( 10 );
///////////////////////////////////////////////////////////////
// Mandatory commands
......@@ -594,6 +595,18 @@ uno::Sequence< ucb::CommandInfo > Content::getCommands(
-1,
getCppuType( static_cast<
ucb::PostCommandArgument2 * >( 0 ) ) );
aCmdInfo[ 8 ] =
ucb::CommandInfo(
rtl::OUString( RTL_CONSTASCII_USTRINGPARAM( "addProperty" ) ),
-1,
getCppuType( static_cast<
ucb::PropertyCommandArgument * >( 0 ) ) );
aCmdInfo[ 9 ] =
ucb::CommandInfo(
rtl::OUString( RTL_CONSTASCII_USTRINGPARAM( "removeProperty" ) ),
-1,
getCppuType( static_cast<
rtl::OUString * >( 0 ) ) );
sal_Bool bFolder = sal_False;
......
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