Commit 9b93ca1c authored by Tim Peters's avatar Tim Peters

Remove a large pile of gimmicks that were officially

deprecated in ZODB 3.4.  This should be fun ;-)
parent 78a8ae1d
...@@ -11,6 +11,49 @@ Zope3 development). These are the dates of the internal releases: ...@@ -11,6 +11,49 @@ Zope3 development). These are the dates of the internal releases:
- 3.6a2 06-Sep-2005 - 3.6a2 06-Sep-2005
- 3.6a1 04-Sep-2005 - 3.6a1 04-Sep-2005
Removal of Features Deprecated in ZODB 3.4
------------------------------------------
(3.6b2) ZODB 3.6 no longer contains features officially deprecated in the
ZODB 3.4 release. These include:
- ``get_transaction()``. Use ``transaction.get()`` instead.
``transaction.commit()`` is a shortcut spelling of
``transaction.get().commit()``, and ``transaction.abort()``
of ``transaction.get().abort()``. Note that importing ZODB no longer
installs ``get_transaction`` as a name in Python's ``__builtin__``
either.
- The ``begin()`` method of ``Transaction`` objects. Use the ``begin()``
method of a transaction manager instead. ``transaction.begin()`` is
a shortcut spelling to call the default transaction manager's ``begin()``
method.
- The ``dt`` argument to ``Connection.cacheMinimize()``.
- The ``Connection.cacheFullSweep()`` method. Use ``cacheMinimize()``
instead.
- The ``Connection.getTransaction()`` method. Pass a transaction manager
to ``DB.open()`` instead.
- The ``Connection.getLocalTransaction()`` method. Pass a transaction
manager to ``DB.open()`` instead.
- The ``cache_deactivate_after`` and ``version_cache_deactivate_after``
arguments to the ``DB`` constructor.
- The ``temporary``, ``force``, and ``waitflag`` arguments
to ``DB.open()``. ``DB.open()`` no longer blocks (there's no longer
a fixed limit on the number of open connections).
- The ``transaction`` and ``txn_mgr``arguments to ``DB.open()``. Use
the ``transaction_manager`` argument instead.
- The ``getCacheDeactivateAfter``, ``setCacheDeactivateAfter``,
``getVersionCacheDeactivateAfter`` and ``setVersionCacheDeactivateAfter``
methods of ``DB``.
Persistent Persistent
---------- ----------
......
...@@ -40,7 +40,6 @@ from ZODB import POSException ...@@ -40,7 +40,6 @@ from ZODB import POSException
from ZODB.POSException import InvalidObjectReference, ConnectionStateError from ZODB.POSException import InvalidObjectReference, ConnectionStateError
from ZODB.POSException import ConflictError, ReadConflictError from ZODB.POSException import ConflictError, ReadConflictError
from ZODB.serialize import ObjectWriter, ObjectReader, myhasattr from ZODB.serialize import ObjectWriter, ObjectReader, myhasattr
from ZODB.utils import DEPRECATED_ARGUMENT, deprecated36
from ZODB.utils import p64, u64, z64, oid_repr, positive_id from ZODB.utils import p64, u64, z64, oid_repr, positive_id
global_reset_counter = 0 global_reset_counter = 0
...@@ -207,10 +206,8 @@ class Connection(ExportImport, object): ...@@ -207,10 +206,8 @@ class Connection(ExportImport, object):
self._cache[oid] = obj self._cache[oid] = obj
return obj return obj
def cacheMinimize(self, dt=DEPRECATED_ARGUMENT): def cacheMinimize(self):
"""Deactivate all unmodified objects in the cache.""" """Deactivate all unmodified objects in the cache."""
if dt is not DEPRECATED_ARGUMENT:
deprecated36("cacheMinimize() dt= is ignored.")
self._cache.minimize() self._cache.minimize()
# TODO: we should test what happens when cacheGC is called mid-transaction. # TODO: we should test what happens when cacheGC is called mid-transaction.
...@@ -848,16 +845,11 @@ class Connection(ExportImport, object): ...@@ -848,16 +845,11 @@ class Connection(ExportImport, object):
""" """
assert obj._p_jar is self assert obj._p_jar is self
if obj._p_oid is None: if obj._p_oid is None:
# There is some old Zope code that assigns _p_jar
# directly. That is no longer allowed, but we need to
# provide support for old code that still does it.
# The actual complaint here is that an object without # The actual complaint here is that an object without
# an oid is being registered. I can't think of any way to # an oid is being registered. I can't think of any way to
# achieve that without assignment to _p_jar. If there is # achieve that without assignment to _p_jar. If there is
# a way, this will be a very confusing warning. # a way, this will be a very confusing exception.
deprecated36("Assigning to _p_jar is deprecated, and will be " raise ValueError("assigning to _p_jar is not supported")
"changed to raise an exception.")
elif obj._p_oid in self._added: elif obj._p_oid in self._added:
# It was registered before it was added to _added. # It was registered before it was added to _added.
return return
...@@ -1001,48 +993,7 @@ class Connection(ExportImport, object): ...@@ -1001,48 +993,7 @@ class Connection(ExportImport, object):
########################################################################## ##########################################################################
# DEPRECATED methods # DEPRECATED methods
def cacheFullSweep(self, dt=None): # None at present.
deprecated36("cacheFullSweep is deprecated. "
"Use cacheMinimize instead.")
if dt is None:
self._cache.full_sweep()
else:
self._cache.full_sweep(dt)
def getTransaction(self):
"""Get the current transaction for this connection.
:deprecated:
The transaction manager's get method works the same as this
method. You can pass a transaction manager (TM) to DB.open()
to control which TM the Connection uses.
"""
deprecated36("getTransaction() is deprecated. "
"Use the transaction_manager argument "
"to DB.open() instead, or access "
".transaction_manager directly on the Connection.")
return self.transaction_manager.get()
def setLocalTransaction(self):
"""Use a transaction bound to the connection rather than the thread.
:deprecated:
Returns the transaction manager used by the connection. You
can pass a transaction manager (TM) to DB.open() to control
which TM the Connection uses.
"""
deprecated36("setLocalTransaction() is deprecated. "
"Use the transaction_manager argument "
"to DB.open() instead.")
if self.transaction_manager is transaction.manager:
if self._synch:
self.transaction_manager.unregisterSynch(self)
self.transaction_manager = transaction.TransactionManager()
if self._synch:
self.transaction_manager.registerSynch(self)
return self.transaction_manager
# DEPRECATED methods # DEPRECATED methods
########################################################################## ##########################################################################
......
...@@ -25,7 +25,6 @@ from ZODB.utils import z64 ...@@ -25,7 +25,6 @@ from ZODB.utils import z64
from ZODB.Connection import Connection from ZODB.Connection import Connection
from ZODB.serialize import referencesf from ZODB.serialize import referencesf
from ZODB.utils import WeakSet from ZODB.utils import WeakSet
from ZODB.utils import DEPRECATED_ARGUMENT, deprecated36
from zope.interface import implements from zope.interface import implements
from ZODB.interfaces import IDatabase from ZODB.interfaces import IDatabase
...@@ -177,9 +176,6 @@ class DB(object): ...@@ -177,9 +176,6 @@ class DB(object):
cacheFullSweep, cacheLastGCTime, cacheMinimize, cacheSize, cacheFullSweep, cacheLastGCTime, cacheMinimize, cacheSize,
cacheDetailSize, getCacheSize, getVersionCacheSize, setCacheSize, cacheDetailSize, getCacheSize, getVersionCacheSize, setCacheSize,
setVersionCacheSize setVersionCacheSize
- `Deprecated Methods`: getCacheDeactivateAfter,
setCacheDeactivateAfter,
getVersionCacheDeactivateAfter, setVersionCacheDeactivateAfter
""" """
implements(IDatabase) implements(IDatabase)
...@@ -189,12 +185,10 @@ class DB(object): ...@@ -189,12 +185,10 @@ class DB(object):
def __init__(self, storage, def __init__(self, storage,
pool_size=7, pool_size=7,
cache_size=400, cache_size=400,
cache_deactivate_after=DEPRECATED_ARGUMENT,
version_pool_size=3, version_pool_size=3,
version_cache_size=100, version_cache_size=100,
database_name='unnamed', database_name='unnamed',
databases=None, databases=None,
version_cache_deactivate_after=DEPRECATED_ARGUMENT,
): ):
"""Create an object database. """Create an object database.
...@@ -206,8 +200,6 @@ class DB(object): ...@@ -206,8 +200,6 @@ class DB(object):
version) version)
- `version_cache_size`: target size of Connection object cache for - `version_cache_size`: target size of Connection object cache for
version connections version connections
- `cache_deactivate_after`: ignored
- `version_cache_deactivate_after`: ignored
""" """
# Allocate lock. # Allocate lock.
x = threading.RLock() x = threading.RLock()
...@@ -222,12 +214,6 @@ class DB(object): ...@@ -222,12 +214,6 @@ class DB(object):
self._version_pool_size = version_pool_size self._version_pool_size = version_pool_size
self._version_cache_size = version_cache_size self._version_cache_size = version_cache_size
# warn about use of deprecated arguments
if cache_deactivate_after is not DEPRECATED_ARGUMENT:
deprecated36("cache_deactivate_after has no effect")
if version_cache_deactivate_after is not DEPRECATED_ARGUMENT:
deprecated36("version_cache_deactivate_after has no effect")
self._miv_cache = {} self._miv_cache = {}
# Setup storage # Setup storage
...@@ -494,10 +480,7 @@ class DB(object): ...@@ -494,10 +480,7 @@ class DB(object):
def objectCount(self): def objectCount(self):
return len(self._storage) return len(self._storage)
def open(self, version='', def open(self, version='', mvcc=True,
transaction=DEPRECATED_ARGUMENT, temporary=DEPRECATED_ARGUMENT,
force=DEPRECATED_ARGUMENT, waitflag=DEPRECATED_ARGUMENT,
mvcc=True, txn_mgr=DEPRECATED_ARGUMENT,
transaction_manager=None, synch=True): transaction_manager=None, synch=True):
"""Return a database Connection for use by application code. """Return a database Connection for use by application code.
...@@ -518,29 +501,6 @@ class DB(object): ...@@ -518,29 +501,6 @@ class DB(object):
register for afterCompletion() calls. register for afterCompletion() calls.
""" """
if temporary is not DEPRECATED_ARGUMENT:
deprecated36("DB.open() temporary= ignored. "
"open() no longer blocks.")
if force is not DEPRECATED_ARGUMENT:
deprecated36("DB.open() force= ignored. "
"open() no longer blocks.")
if waitflag is not DEPRECATED_ARGUMENT:
deprecated36("DB.open() waitflag= ignored. "
"open() no longer blocks.")
if transaction is not DEPRECATED_ARGUMENT:
deprecated36("DB.open() transaction= ignored.")
if txn_mgr is not DEPRECATED_ARGUMENT:
deprecated36("use transaction_manager= instead of txn_mgr=")
if transaction_manager is None:
transaction_manager = txn_mgr
else:
raise ValueError("cannot specify both transaction_manager= "
"and txn_mgr=")
self._a() self._a()
try: try:
# pool <- the _ConnectionPool for this version # pool <- the _ConnectionPool for this version
...@@ -706,24 +666,6 @@ class DB(object): ...@@ -706,24 +666,6 @@ class DB(object):
def versionEmpty(self, version): def versionEmpty(self, version):
return self._storage.versionEmpty(version) return self._storage.versionEmpty(version)
# The following methods are deprecated and have no effect
def getCacheDeactivateAfter(self):
"""Deprecated"""
deprecated36("getCacheDeactivateAfter has no effect")
def getVersionCacheDeactivateAfter(self):
"""Deprecated"""
deprecated36("getVersionCacheDeactivateAfter has no effect")
def setCacheDeactivateAfter(self, v):
"""Deprecated"""
deprecated36("setCacheDeactivateAfter has no effect")
def setVersionCacheDeactivateAfter(self, v):
"""Deprecated"""
deprecated36("setVersionCacheDeactivateAfter has no effect")
class ResourceManager(object): class ResourceManager(object):
"""Transaction participation for a version or undo resource.""" """Transaction participation for a version or undo resource."""
......
...@@ -16,7 +16,6 @@ ...@@ -16,7 +16,6 @@
__version__ = "3.6.0b1" __version__ = "3.6.0b1"
import sys import sys
import __builtin__
from persistent import TimeStamp from persistent import TimeStamp
from persistent import list from persistent import list
...@@ -30,9 +29,3 @@ sys.modules['ZODB.PersistentList'] = sys.modules['persistent.list'] ...@@ -30,9 +29,3 @@ sys.modules['ZODB.PersistentList'] = sys.modules['persistent.list']
del mapping, list, sys del mapping, list, sys
from DB import DB from DB import DB
# TODO: get_transaction() scheduled to go away in ZODB 3.6.
from transaction import get_transaction
__builtin__.get_transaction = get_transaction
del __builtin__
...@@ -390,11 +390,11 @@ class UserMethodTests(unittest.TestCase): ...@@ -390,11 +390,11 @@ class UserMethodTests(unittest.TestCase):
""" """
def test_cache(self): def test_cache(self):
r"""doctest of cacheMinimize() and cacheFullSweep() methods. r"""doctest of cacheMinimize().
These tests are fairly minimal, just verifying that the Thus test us minimal, just verifying that the method can be called
methods can be called and have some effect. We need other and has some effect. We need other tests that verify the cache works
tests that verify the cache works as intended. as intended.
>>> db = databaseFromString("<zodb>\n<mappingstorage/>\n</zodb>") >>> db = databaseFromString("<zodb>\n<mappingstorage/>\n</zodb>")
>>> cn = db.open() >>> cn = db.open()
...@@ -403,71 +403,12 @@ class UserMethodTests(unittest.TestCase): ...@@ -403,71 +403,12 @@ class UserMethodTests(unittest.TestCase):
>>> r._p_state >>> r._p_state
-1 -1
The next couple of tests are involved because they have to
cater to backwards compatibility issues. The cacheMinimize()
method used to take an argument, but now ignores it.
cacheFullSweep() used to do something different than
cacheMinimize(), but it doesn't anymore. We want to verify
that these methods do something, but all cause deprecation
warnings. To do that, we need a warnings hook.
>>> hook = WarningsHook()
>>> hook.install()
More problems in case this test is run more than once: fool the
warnings module into delivering the warnings despite that they've
been seen before.
>>> import warnings
>>> warnings.filterwarnings("always", category=DeprecationWarning)
>>> r._p_activate() >>> r._p_activate()
>>> cn.cacheMinimize(12) >>> r._p_state # up to date
>>> r._p_state
-1
>>> len(hook.warnings)
1
>>> message, category, filename, lineno = hook.warnings[0]
>>> print message
This will be removed in ZODB 3.6:
cacheMinimize() dt= is ignored.
>>> category.__name__
'DeprecationWarning'
>>> hook.clear()
cacheFullSweep() is a doozy. It generates two deprecation
warnings, one from the Connection and one from the
cPickleCache. Maybe we should drop the cPickleCache warning,
but it's there for now. When passed an argument, it acts like
cacheGC(). When it isn't passed an argument it acts like
cacheMinimize().
>>> r._p_activate()
>>> cn.cacheFullSweep(12)
>>> r._p_state
0 0
>>> len(hook.warnings) >>> cn.cacheMinimize()
2 >>> r._p_state # ghost again
>>> message, category, filename, lineno = hook.warnings[0] -1
>>> print message
This will be removed in ZODB 3.6:
cacheFullSweep is deprecated. Use cacheMinimize instead.
>>> category.__name__
'DeprecationWarning'
>>> message, category, filename, lineno = hook.warnings[1]
>>> message
'No argument expected'
>>> category.__name__
'DeprecationWarning'
We have to uninstall the hook so that other warnings don't get lost.
>>> hook.uninstall()
Obscure: There is no API call for removing the filter we added, but
filters appears to be a public variable.
>>> del warnings.filters[0]
""" """
class InvalidationTests(unittest.TestCase): class InvalidationTests(unittest.TestCase):
......
...@@ -54,15 +54,6 @@ class DBTests(unittest.TestCase): ...@@ -54,15 +54,6 @@ class DBTests(unittest.TestCase):
# make sure the basic methods are callable # make sure the basic methods are callable
def testSets(self): def testSets(self):
# test set methods that have non-trivial implementations
warnings.filterwarnings("error", category=DeprecationWarning)
self.assertRaises(DeprecationWarning,
self.db.setCacheDeactivateAfter, 12)
self.assertRaises(DeprecationWarning,
self.db.setVersionCacheDeactivateAfter, 12)
# Obscure: There is no API call for removing the warning we just
# added, but filters appears to be a public variable.
del warnings.filters[0]
self.db.setCacheSize(15) self.db.setCacheSize(15)
self.db.setVersionCacheSize(15) self.db.setVersionCacheSize(15)
......
...@@ -213,58 +213,6 @@ class ZODBTests(unittest.TestCase): ...@@ -213,58 +213,6 @@ class ZODBTests(unittest.TestCase):
conn1.close() conn1.close()
conn2.close() conn2.close()
def checkLocalTransactions(self):
# Test of transactions that apply to only the connection,
# not the thread.
conn1 = self._db.open()
conn2 = self._db.open()
hook = WarningsHook()
hook.install()
try:
conn1.setLocalTransaction()
conn2.setLocalTransaction()
r1 = conn1.root()
r2 = conn2.root()
if r1.has_key('item'):
del r1['item']
conn1.getTransaction().commit()
r1.get('item')
r2.get('item')
r1['item'] = 1
conn1.getTransaction().commit()
self.assertEqual(r1['item'], 1)
# r2 has not seen a transaction boundary,
# so it should be unchanged.
self.assertEqual(r2.get('item'), None)
conn2.sync()
# Now r2 is updated.
self.assertEqual(r2['item'], 1)
# Now, for good measure, send an update in the other direction.
r2['item'] = 2
conn2.getTransaction().commit()
self.assertEqual(r1['item'], 1)
self.assertEqual(r2['item'], 2)
conn1.sync()
conn2.sync()
self.assertEqual(r1['item'], 2)
self.assertEqual(r2['item'], 2)
for msg, obj, filename, lineno in hook.warnings:
self.assert_(msg in [
"This will be removed in ZODB 3.6:\n"
"setLocalTransaction() is deprecated. "
"Use the transaction_manager argument "
"to DB.open() instead.",
"This will be removed in ZODB 3.6:\n"
"getTransaction() is deprecated. "
"Use the transaction_manager argument "
"to DB.open() instead, or access "
".transaction_manager directly on the Connection."])
finally:
conn1.close()
conn2.close()
hook.uninstall()
def checkReadConflict(self): def checkReadConflict(self):
self.obj = P() self.obj = P()
self.readConflict() self.readConflict()
...@@ -584,57 +532,8 @@ class ZODBTests(unittest.TestCase): ...@@ -584,57 +532,8 @@ class ZODBTests(unittest.TestCase):
# transaction, and, in fact, when this test was written, # transaction, and, in fact, when this test was written,
# Transaction.begin() didn't do anything (everything from here # Transaction.begin() didn't do anything (everything from here
# down failed). # down failed).
# Later (ZODB 3.6): Transaction.begin() no longer exists, so the
# Oh, bleech. Since Transaction.begin is also deprecated, we have # rest of this test was tossed.
# to goof around suppressing the deprecation warning.
import warnings
# First verify that Transaction.begin *is* deprecated, by turning
# the warning into an error.
warnings.filterwarnings("error", category=DeprecationWarning)
self.assertRaises(DeprecationWarning, transaction.get().begin)
del warnings.filters[0]
# Now ignore DeprecationWarnings for the duration. Use a
# try/finally block to ensure we reenable DeprecationWarnings
# no matter what.
warnings.filterwarnings("ignore", category=DeprecationWarning)
try:
cn = self._db.open()
rt = cn.root()
rt['a'] = 1
transaction.get().begin() # should abort adding 'a' to the root
rt = cn.root()
self.assertRaises(KeyError, rt.__getitem__, 'a')
# A longstanding bug: this didn't work if changes were only in
# subtransactions.
transaction.get().begin()
rt = cn.root()
rt['a'] = 2
transaction.get().commit(1)
transaction.get().begin()
rt = cn.root()
self.assertRaises(KeyError, rt.__getitem__, 'a')
# One more time, mixing "top level" and subtransaction changes.
transaction.get().begin()
rt = cn.root()
rt['a'] = 3
transaction.get().commit(1)
rt['b'] = 4
transaction.get().begin()
rt = cn.root()
self.assertRaises(KeyError, rt.__getitem__, 'a')
self.assertRaises(KeyError, rt.__getitem__, 'b')
cn.close()
finally:
del warnings.filters[0]
def checkFailingCommitSticks(self): def checkFailingCommitSticks(self):
# See also checkFailingSubtransactionCommitSticks. # See also checkFailingSubtransactionCommitSticks.
......
...@@ -37,9 +37,9 @@ MinimalMemoryStorage that implements MVCC support, but not much else. ...@@ -37,9 +37,9 @@ MinimalMemoryStorage that implements MVCC support, but not much else.
>>> from ZODB import DB >>> from ZODB import DB
>>> db = DB(MinimalMemoryStorage()) >>> db = DB(MinimalMemoryStorage())
We will use two different connections with the experimental We will use two different connections with different transaction managers
setLocalTransaction() method to make sure that the connections act to make sure that the connections act independently, even though they'll
independently, even though they'll be run from a single thread. be run from a single thread.
>>> import transaction >>> import transaction
>>> tm1 = transaction.TransactionManager() >>> tm1 = transaction.TransactionManager()
......
...@@ -37,7 +37,6 @@ __all__ = ['z64', ...@@ -37,7 +37,6 @@ __all__ = ['z64',
'readable_tid_repr', 'readable_tid_repr',
'WeakSet', 'WeakSet',
'DEPRECATED_ARGUMENT', 'DEPRECATED_ARGUMENT',
'deprecated36',
'deprecated37', 'deprecated37',
'deprecated38', 'deprecated38',
'get_pickle_metadata', 'get_pickle_metadata',
...@@ -52,13 +51,6 @@ __all__ = ['z64', ...@@ -52,13 +51,6 @@ __all__ = ['z64',
# dance. # dance.
DEPRECATED_ARGUMENT = object() DEPRECATED_ARGUMENT = object()
# Raise DeprecationWarning, noting that the deprecated thing will go
# away in ZODB 3.6. Point to the caller of our caller (i.e., at the
# code using the deprecated thing).
def deprecated36(msg):
warnings.warn("This will be removed in ZODB 3.6:\n%s" % msg,
DeprecationWarning, stacklevel=3)
# Raise DeprecationWarning, noting that the deprecated thing will go # Raise DeprecationWarning, noting that the deprecated thing will go
# away in ZODB 3.7. Point to the caller of our caller (i.e., at the # away in ZODB 3.7. Point to the caller of our caller (i.e., at the
# code using the deprecated thing). # code using the deprecated thing).
......
...@@ -25,10 +25,3 @@ begin = manager.begin ...@@ -25,10 +25,3 @@ begin = manager.begin
commit = manager.commit commit = manager.commit
abort = manager.abort abort = manager.abort
savepoint = manager.savepoint savepoint = manager.savepoint
def get_transaction():
from ZODB.utils import deprecated36
deprecated36(""" use transaction.get() instead of get_transaction().
transaction.commit() is a shortcut spelling of transaction.get().commit(),
and transaction.abort() of transaction.get().abort().""")
return get()
...@@ -30,7 +30,7 @@ registers its _p_jar attribute. TODO: explain adapter ...@@ -30,7 +30,7 @@ registers its _p_jar attribute. TODO: explain adapter
Subtransactions Subtransactions
--------------- ---------------
Note: Suntransactions are deprecated! Use savepoint/rollback instead. Note: Subtransactions are deprecated! Use savepoint/rollback instead.
A subtransaction applies the transaction notion recursively. It A subtransaction applies the transaction notion recursively. It
allows a set of modifications within a transaction to be committed or allows a set of modifications within a transaction to be committed or
...@@ -345,17 +345,6 @@ class Transaction(object): ...@@ -345,17 +345,6 @@ class Transaction(object):
assert id(obj) not in map(id, adapter.objects) assert id(obj) not in map(id, adapter.objects)
adapter.objects.append(obj) adapter.objects.append(obj)
def begin(self):
from ZODB.utils import deprecated36
deprecated36("Transaction.begin() should no longer be used; use "
"the begin() method of a transaction manager.")
if (self._resources or self._synchronizers):
self.abort()
# Else aborting wouldn't do anything, except if _manager is non-None,
# in which case it would do nothing besides uselessly free() this
# transaction.
def commit(self, subtransaction=_marker, deprecation_wng=True): def commit(self, subtransaction=_marker, deprecation_wng=True):
if subtransaction is _marker: if subtransaction is _marker:
subtransaction = 0 subtransaction = 0
......
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