Skip to content
Projects
Groups
Snippets
Help
This project
Loading...
Sign in / Register
Toggle navigation
Y
yii2
Project
Overview
Details
Activity
Cycle Analytics
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Charts
Issues
0
Issues
0
List
Board
Labels
Milestones
Merge Requests
0
Merge Requests
0
CI / CD
CI / CD
Pipelines
Jobs
Schedules
Charts
Wiki
Wiki
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Charts
Create a new issue
Jobs
Commits
Issue Boards
Open sidebar
PSDI Army
yii2
Commits
733dbbfb
Commit
733dbbfb
authored
Jun 25, 2013
by
resurtm
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
Mutex WIP.
parent
b98a1236
Hide whitespace changes
Inline
Side-by-side
Showing
6 changed files
with
43 additions
and
215 deletions
+43
-215
DbMutex.php
extensions/mutex/yii/mutex/DbMutex.php
+2
-12
FileMutex.php
extensions/mutex/yii/mutex/FileMutex.php
+15
-28
MssqlMutex.php
extensions/mutex/yii/mutex/MssqlMutex.php
+7
-7
Mutex.php
extensions/mutex/yii/mutex/Mutex.php
+14
-59
MysqlMutex.php
extensions/mutex/yii/mutex/MysqlMutex.php
+5
-20
Mutex.php
extensions/mutex/yii/mutex/cache/Mutex.php
+0
-89
No files found.
extensions/mutex/yii/mutex/
db/
Mutex.php
→
extensions/mutex/yii/mutex/
Db
Mutex.php
View file @
733dbbfb
...
@@ -5,7 +5,7 @@
...
@@ -5,7 +5,7 @@
* @license http://www.yiiframework.com/license/
* @license http://www.yiiframework.com/license/
*/
*/
namespace
yii\mutex
\db
;
namespace
yii\mutex
;
use
Yii
;
use
Yii
;
use
yii\db\Connection
;
use
yii\db\Connection
;
...
@@ -15,7 +15,7 @@ use yii\base\InvalidConfigException;
...
@@ -15,7 +15,7 @@ use yii\base\InvalidConfigException;
* @author resurtm <resurtm@gmail.com>
* @author resurtm <resurtm@gmail.com>
* @since 2.0
* @since 2.0
*/
*/
abstract
class
Mutex
extends
\yii\mutex\
Mutex
abstract
class
DbMutex
extends
Mutex
{
{
/**
/**
* @var Connection|string the DB connection object or the application component ID of the DB connection.
* @var Connection|string the DB connection object or the application component ID of the DB connection.
...
@@ -38,14 +38,4 @@ abstract class Mutex extends \yii\mutex\Mutex
...
@@ -38,14 +38,4 @@ abstract class Mutex extends \yii\mutex\Mutex
throw
new
InvalidConfigException
(
'Mutex::db must be either a DB connection instance or the application component ID of a DB connection.'
);
throw
new
InvalidConfigException
(
'Mutex::db must be either a DB connection instance or the application component ID of a DB connection.'
);
}
}
}
}
/**
* This method should be extended by concrete mutex implementations. Returns whether current mutex
* implementation can be used in a distributed environment.
* @return boolean whether current mutex implementation can be used in a distributed environment.
*/
public
function
getIsDistributed
()
{
return
true
;
}
}
}
extensions/mutex/yii/mutex/
unix/
Mutex.php
→
extensions/mutex/yii/mutex/
File
Mutex.php
View file @
733dbbfb
...
@@ -5,7 +5,7 @@
...
@@ -5,7 +5,7 @@
* @license http://www.yiiframework.com/license/
* @license http://www.yiiframework.com/license/
*/
*/
namespace
yii\mutex
\unix
;
namespace
yii\mutex
;
use
Yii
;
use
Yii
;
use
yii\base\InvalidConfigException
;
use
yii\base\InvalidConfigException
;
...
@@ -14,9 +14,14 @@ use yii\base\InvalidConfigException;
...
@@ -14,9 +14,14 @@ use yii\base\InvalidConfigException;
* @author resurtm <resurtm@gmail.com>
* @author resurtm <resurtm@gmail.com>
* @since 2.0
* @since 2.0
*/
*/
class
Mutex
extends
\yii\mutex\
Mutex
class
FileMutex
extends
Mutex
{
{
/**
/**
* @var string the directory to store mutex files. You may use path alias here.
* If not set, it will use the "mutex" subdirectory under the application runtime path.
*/
public
$mutexPath
=
'@runtime/mutex'
;
/**
* @var resource[] stores all opened lock files. Keys are lock names and values are file handles.
* @var resource[] stores all opened lock files. Keys are lock names and values are file handles.
*/
*/
private
$_files
=
array
();
private
$_files
=
array
();
...
@@ -30,7 +35,11 @@ class Mutex extends \yii\mutex\Mutex
...
@@ -30,7 +35,11 @@ class Mutex extends \yii\mutex\Mutex
public
function
init
()
public
function
init
()
{
{
if
(
stripos
(
php_uname
(
's'
),
'win'
)
===
0
)
{
if
(
stripos
(
php_uname
(
's'
),
'win'
)
===
0
)
{
throw
new
InvalidConfigException
(
''
);
throw
new
InvalidConfigException
(
'FileMutex does not have MS Windows operating system support.'
);
}
$this
->
mutexPath
=
Yii
::
getAlias
(
$this
->
mutexPath
);
if
(
!
is_dir
(
$this
->
mutexPath
))
{
mkdir
(
$this
->
mutexPath
,
0777
,
true
);
}
}
}
}
...
@@ -40,9 +49,9 @@ class Mutex extends \yii\mutex\Mutex
...
@@ -40,9 +49,9 @@ class Mutex extends \yii\mutex\Mutex
* @param integer $timeout to wait for lock to become released.
* @param integer $timeout to wait for lock to become released.
* @return boolean acquiring result.
* @return boolean acquiring result.
*/
*/
protected
function
acquire
(
$name
,
$timeout
=
0
)
protected
function
acquire
Lock
(
$name
,
$timeout
=
0
)
{
{
$file
=
fopen
(
Yii
::
$app
->
getRuntimePath
()
.
'/mutex.
'
.
md5
(
$name
)
.
'.lock'
,
'w+'
);
$file
=
fopen
(
$this
->
mutexPath
.
'/
'
.
md5
(
$name
)
.
'.lock'
,
'w+'
);
if
(
$file
===
false
)
{
if
(
$file
===
false
)
{
return
false
;
return
false
;
}
}
...
@@ -64,7 +73,7 @@ class Mutex extends \yii\mutex\Mutex
...
@@ -64,7 +73,7 @@ class Mutex extends \yii\mutex\Mutex
* @param string $name of the lock to be released.
* @param string $name of the lock to be released.
* @return boolean release result.
* @return boolean release result.
*/
*/
protected
function
release
(
$name
)
protected
function
release
Lock
(
$name
)
{
{
if
(
!
isset
(
$this
->
_files
[
$name
])
||
!
flock
(
$this
->
_files
[
$name
],
LOCK_UN
))
{
if
(
!
isset
(
$this
->
_files
[
$name
])
||
!
flock
(
$this
->
_files
[
$name
],
LOCK_UN
))
{
return
false
;
return
false
;
...
@@ -74,26 +83,4 @@ class Mutex extends \yii\mutex\Mutex
...
@@ -74,26 +83,4 @@ class Mutex extends \yii\mutex\Mutex
return
true
;
return
true
;
}
}
}
}
/**
* This method may optionally be extended by concrete mutex implementations. Checks whether lock has been
* already acquired by given name.
* @param string $name of the lock to be released.
* @return null|boolean whether lock has been already acquired. Returns `null` in case this feature
* is not supported by concrete mutex implementation.
*/
protected
function
getIsAcquired
(
$name
)
{
return
false
;
}
/**
* This method should be extended by concrete mutex implementations. Returns whether current mutex
* implementation can be used in a distributed environment.
* @return boolean whether current mutex implementation can be used in a distributed environment.
*/
public
function
getIsDistributed
()
{
return
false
;
}
}
}
extensions/mutex/yii/mutex/
db/mssql/
Mutex.php
→
extensions/mutex/yii/mutex/
Mssql
Mutex.php
View file @
733dbbfb
...
@@ -5,7 +5,7 @@
...
@@ -5,7 +5,7 @@
* @license http://www.yiiframework.com/license/
* @license http://www.yiiframework.com/license/
*/
*/
namespace
yii\mutex
\db\mssql
;
namespace
yii\mutex
;
use
Yii
;
use
Yii
;
use
yii\base\InvalidConfigException
;
use
yii\base\InvalidConfigException
;
...
@@ -14,7 +14,7 @@ use yii\base\InvalidConfigException;
...
@@ -14,7 +14,7 @@ use yii\base\InvalidConfigException;
* @author resurtm <resurtm@gmail.com>
* @author resurtm <resurtm@gmail.com>
* @since 2.0
* @since 2.0
*/
*/
class
M
utex
extends
\yii\mutex\db\
Mutex
class
M
ssqlMutex
extends
Mutex
{
{
/**
/**
* Initializes Microsoft SQL Server specific mutex component implementation.
* Initializes Microsoft SQL Server specific mutex component implementation.
...
@@ -25,7 +25,7 @@ class Mutex extends \yii\mutex\db\Mutex
...
@@ -25,7 +25,7 @@ class Mutex extends \yii\mutex\db\Mutex
parent
::
init
();
parent
::
init
();
$driverName
=
$this
->
db
->
driverName
;
$driverName
=
$this
->
db
->
driverName
;
if
(
$driverName
!==
'sqlsrv'
&&
$driverName
!==
'dblib'
&&
$driverName
!==
'mssql'
)
{
if
(
$driverName
!==
'sqlsrv'
&&
$driverName
!==
'dblib'
&&
$driverName
!==
'mssql'
)
{
throw
new
InvalidConfigException
(
''
);
throw
new
InvalidConfigException
(
'
In order to use MssqlMutex connection must be configured to use MS SQL Server database.
'
);
}
}
}
}
...
@@ -34,10 +34,10 @@ class Mutex extends \yii\mutex\db\Mutex
...
@@ -34,10 +34,10 @@ class Mutex extends \yii\mutex\db\Mutex
* @param string $name of the lock to be acquired.
* @param string $name of the lock to be acquired.
* @param integer $timeout to wait for lock to become released.
* @param integer $timeout to wait for lock to become released.
* @return boolean acquiring result.
* @return boolean acquiring result.
* @throws \BadMethodCallException
* @throws \BadMethodCallException
not implemented yet.
* @see http://msdn.microsoft.com/en-us/library/ms189823.aspx
* @see http://msdn.microsoft.com/en-us/library/ms189823.aspx
*/
*/
protected
function
acquire
(
$name
,
$timeout
=
0
)
protected
function
acquire
Lock
(
$name
,
$timeout
=
0
)
{
{
throw
new
\BadMethodCallException
(
'Not implemented yet.'
);
throw
new
\BadMethodCallException
(
'Not implemented yet.'
);
}
}
...
@@ -46,10 +46,10 @@ class Mutex extends \yii\mutex\db\Mutex
...
@@ -46,10 +46,10 @@ class Mutex extends \yii\mutex\db\Mutex
* This method should be extended by concrete mutex implementations. Releases lock by given name.
* This method should be extended by concrete mutex implementations. Releases lock by given name.
* @param string $name of the lock to be released.
* @param string $name of the lock to be released.
* @return boolean release result.
* @return boolean release result.
* @throws \BadMethodCallException
* @throws \BadMethodCallException
not implemented yet.
* @see http://msdn.microsoft.com/en-us/library/ms178602.aspx
* @see http://msdn.microsoft.com/en-us/library/ms178602.aspx
*/
*/
protected
function
release
(
$name
)
protected
function
release
Lock
(
$name
)
{
{
throw
new
\BadMethodCallException
(
'Not implemented yet.'
);
throw
new
\BadMethodCallException
(
'Not implemented yet.'
);
}
}
...
...
extensions/mutex/yii/mutex/Mutex.php
View file @
733dbbfb
...
@@ -18,7 +18,8 @@ abstract class Mutex extends Component
...
@@ -18,7 +18,8 @@ abstract class Mutex extends Component
{
{
/**
/**
* @var boolean whether all locks acquired in this process (i.e. local locks) must be released automagically
* @var boolean whether all locks acquired in this process (i.e. local locks) must be released automagically
* before finishing script execution. Defaults to true. Setting this property to true
* before finishing script execution. Defaults to true. Setting this property to true means that all locks
* acquire in this process must be released in any case (regardless any kind of errors or exceptions).
*/
*/
public
$autoRelease
=
true
;
public
$autoRelease
=
true
;
/**
/**
...
@@ -33,14 +34,13 @@ abstract class Mutex extends Component
...
@@ -33,14 +34,13 @@ abstract class Mutex extends Component
public
function
init
()
public
function
init
()
{
{
if
(
$this
->
autoRelease
)
{
if
(
$this
->
autoRelease
)
{
$references
=
new
stdClass
();
$mutex
=
$this
;
$references
->
mutex
=
$this
;
$locks
=
&
$this
->
_locks
;
$references
->
locks
=
&
$this
->
_locks
;
register_shutdown_function
(
function
()
use
(
$mutex
,
&
$locks
)
{
register_shutdown_function
(
function
(
$refs
)
{
foreach
(
$locks
as
$lock
)
{
foreach
(
$refs
->
locks
as
$lock
)
{
$mutex
->
release
(
$lock
);
$refs
->
mutex
->
release
(
$lock
);
}
}
}
,
$references
);
});
}
}
}
}
...
@@ -50,9 +50,9 @@ abstract class Mutex extends Component
...
@@ -50,9 +50,9 @@ abstract class Mutex extends Component
* false immediately in case lock was already acquired.
* false immediately in case lock was already acquired.
* @return boolean lock acquiring result.
* @return boolean lock acquiring result.
*/
*/
public
function
acquire
Lock
(
$name
,
$timeout
=
0
)
public
function
acquire
(
$name
,
$timeout
=
0
)
{
{
if
(
$this
->
acquire
(
$name
,
$timeout
))
{
if
(
$this
->
acquire
Lock
(
$name
,
$timeout
))
{
$this
->
_locks
[]
=
$name
;
$this
->
_locks
[]
=
$name
;
return
true
;
return
true
;
}
else
{
}
else
{
...
@@ -65,9 +65,9 @@ abstract class Mutex extends Component
...
@@ -65,9 +65,9 @@ abstract class Mutex extends Component
* @param string $name of the lock to be released. This lock must be already created.
* @param string $name of the lock to be released. This lock must be already created.
* @return boolean lock release result: false in case named lock was not found..
* @return boolean lock release result: false in case named lock was not found..
*/
*/
public
function
release
Lock
(
$name
)
public
function
release
(
$name
)
{
{
if
(
$this
->
release
(
$name
))
{
if
(
$this
->
release
Lock
(
$name
))
{
$index
=
array_search
(
$name
,
$this
->
_locks
);
$index
=
array_search
(
$name
,
$this
->
_locks
);
if
(
$index
!==
false
)
{
if
(
$index
!==
false
)
{
unset
(
$this
->
_locks
[
$index
]);
unset
(
$this
->
_locks
[
$index
]);
...
@@ -79,62 +79,17 @@ abstract class Mutex extends Component
...
@@ -79,62 +79,17 @@ abstract class Mutex extends Component
}
}
/**
/**
* Checks whether named lock was already opened.
* @param string $name of the lock to be checked. This lock must be already created.
* @return boolean|null whether named lock was already opened. Returns `null` value in case concrete
* mutex implementation does not support this operation.
*/
public
function
getIsLockAcquired
(
$name
)
{
if
(
in_array
(
$name
,
$this
->
_locks
))
{
return
true
;
}
else
{
return
$this
->
getIsAcquired
(
$name
);
}
}
/**
* Checks whether given lock is local. In other words local lock means that it was opened in the current
* PHP process.
* @param string $name of the lock to be checked. This lock must be already created.
* @return boolean whether named lock was locally acquired.
*/
public
function
getIsLockLocal
(
$name
)
{
return
in_array
(
$name
,
$this
->
_locks
);
}
/**
* This method should be extended by concrete mutex implementations. Acquires lock by given name.
* This method should be extended by concrete mutex implementations. Acquires lock by given name.
* @param string $name of the lock to be acquired.
* @param string $name of the lock to be acquired.
* @param integer $timeout to wait for lock to become released.
* @param integer $timeout to wait for lock to become released.
* @return boolean acquiring result.
* @return boolean acquiring result.
*/
*/
abstract
protected
function
acquire
(
$name
,
$timeout
=
0
);
abstract
protected
function
acquire
Lock
(
$name
,
$timeout
=
0
);
/**
/**
* This method should be extended by concrete mutex implementations. Releases lock by given name.
* This method should be extended by concrete mutex implementations. Releases lock by given name.
* @param string $name of the lock to be released.
* @param string $name of the lock to be released.
* @return boolean release result.
* @return boolean release result.
*/
*/
abstract
protected
function
release
(
$name
);
abstract
protected
function
releaseLock
(
$name
);
/**
* This method may optionally be extended by concrete mutex implementations. Checks whether lock has been
* already acquired by given name.
* @param string $name of the lock to be released.
* @return null|boolean whether lock has been already acquired. Returns `null` in case this feature
* is not supported by concrete mutex implementation.
*/
protected
function
getIsAcquired
(
$name
)
{
return
null
;
}
/**
* This method should be extended by concrete mutex implementations. Returns whether current mutex
* implementation can be used in a distributed environment.
* @return boolean whether current mutex implementation can be used in a distributed environment.
*/
abstract
public
function
getIsDistributed
();
}
}
extensions/mutex/yii/mutex/
db/mysql/
Mutex.php
→
extensions/mutex/yii/mutex/
Mysql
Mutex.php
View file @
733dbbfb
...
@@ -5,7 +5,7 @@
...
@@ -5,7 +5,7 @@
* @license http://www.yiiframework.com/license/
* @license http://www.yiiframework.com/license/
*/
*/
namespace
yii\mutex
\db\mysql
;
namespace
yii\mutex
;
use
Yii
;
use
Yii
;
use
yii\base\InvalidConfigException
;
use
yii\base\InvalidConfigException
;
...
@@ -14,7 +14,7 @@ use yii\base\InvalidConfigException;
...
@@ -14,7 +14,7 @@ use yii\base\InvalidConfigException;
* @author resurtm <resurtm@gmail.com>
* @author resurtm <resurtm@gmail.com>
* @since 2.0
* @since 2.0
*/
*/
class
M
utex
extends
\yii\mutex\db\
Mutex
class
M
ysqlMutex
extends
Mutex
{
{
/**
/**
* Initializes MySQL specific mutex component implementation.
* Initializes MySQL specific mutex component implementation.
...
@@ -24,7 +24,7 @@ class Mutex extends \yii\mutex\db\Mutex
...
@@ -24,7 +24,7 @@ class Mutex extends \yii\mutex\db\Mutex
{
{
parent
::
init
();
parent
::
init
();
if
(
$this
->
db
->
driverName
!==
'mysql'
)
{
if
(
$this
->
db
->
driverName
!==
'mysql'
)
{
throw
new
InvalidConfigException
(
''
);
throw
new
InvalidConfigException
(
'
In order to use MysqlMutex connection must be configured to use MySQL database.
'
);
}
}
}
}
...
@@ -35,7 +35,7 @@ class Mutex extends \yii\mutex\db\Mutex
...
@@ -35,7 +35,7 @@ class Mutex extends \yii\mutex\db\Mutex
* @return boolean acquiring result.
* @return boolean acquiring result.
* @see http://dev.mysql.com/doc/refman/5.0/en/miscellaneous-functions.html#function_get-lock
* @see http://dev.mysql.com/doc/refman/5.0/en/miscellaneous-functions.html#function_get-lock
*/
*/
protected
function
acquire
(
$name
,
$timeout
=
0
)
protected
function
acquire
Lock
(
$name
,
$timeout
=
0
)
{
{
return
(
boolean
)
$this
->
db
return
(
boolean
)
$this
->
db
->
createCommand
(
'SELECT GET_LOCK(:name, :timeout)'
,
array
(
':name'
=>
$name
,
':timeout'
=>
$timeout
))
->
createCommand
(
'SELECT GET_LOCK(:name, :timeout)'
,
array
(
':name'
=>
$name
,
':timeout'
=>
$timeout
))
...
@@ -48,25 +48,10 @@ class Mutex extends \yii\mutex\db\Mutex
...
@@ -48,25 +48,10 @@ class Mutex extends \yii\mutex\db\Mutex
* @return boolean release result.
* @return boolean release result.
* @see http://dev.mysql.com/doc/refman/5.0/en/miscellaneous-functions.html#function_release-lock
* @see http://dev.mysql.com/doc/refman/5.0/en/miscellaneous-functions.html#function_release-lock
*/
*/
protected
function
release
(
$name
)
protected
function
release
Lock
(
$name
)
{
{
return
(
boolean
)
$this
->
db
return
(
boolean
)
$this
->
db
->
createCommand
(
'SELECT RELEASE_LOCK(:name)'
,
array
(
':name'
=>
$name
))
->
createCommand
(
'SELECT RELEASE_LOCK(:name)'
,
array
(
':name'
=>
$name
))
->
queryScalar
();
->
queryScalar
();
}
}
/**
* This method may optionally be extended by concrete mutex implementations. Checks whether lock has been
* already acquired by given name.
* @param string $name of the lock to be released.
* @return null|boolean whether lock has been already acquired. Returns `null` in case this feature
* is not supported by concrete mutex implementation.
* @see http://dev.mysql.com/doc/refman/5.0/en/miscellaneous-functions.html#function_is-free-lock
*/
protected
function
getIsAcquired
(
$name
)
{
return
(
boolean
)
$this
->
db
->
createCommand
(
'SELECT IS_FREE_LOCK(:name)'
,
array
(
':name'
=>
$name
))
->
queryScalar
();
}
}
}
extensions/mutex/yii/mutex/cache/Mutex.php
deleted
100644 → 0
View file @
b98a1236
<?php
/**
* @link http://www.yiiframework.com/
* @copyright Copyright (c) 2008 Yii Software LLC
* @license http://www.yiiframework.com/license/
*/
namespace
yii\mutex\cache
;
use
Yii
;
use
yii\base\InvalidConfigException
;
use
yii\caching\Cache
;
use
yii\caching\DbCache
;
use
yii\caching\MemCache
;
/**
* @author resurtm <resurtm@gmail.com>
* @since 2.0
*/
class
Mutex
extends
\yii\mutex\Mutex
{
/**
* @var Cache|string the cache object or the application component ID of the cache object.
* The messages data will be cached using this cache object. Note, this property has meaning only
* in case [[cachingDuration]] set to non-zero value.
* After the Mutex object is created, if you want to change this property, you should only assign
* it with a cache object.
*/
public
$cache
=
'cache'
;
/**
* Initializes the DbMessageSource component. Configured [[cache]] component will be initialized.
* @throws InvalidConfigException if [[cache]] is invalid.
*/
public
function
init
()
{
parent
::
init
();
if
(
is_string
(
$this
->
cache
))
{
$this
->
cache
=
Yii
::
$app
->
getComponent
(
$this
->
cache
);
}
if
(
!
$this
->
cache
instanceof
Cache
)
{
throw
new
InvalidConfigException
(
'Mutex::cache must be either a cache object or the application component ID of the cache object.'
);
}
}
/**
* This method should be extended by concrete mutex implementations. Acquires lock by given name.
* @param string $name of the lock to be acquired.
* @param integer $timeout to wait for lock to become released.
* @return boolean acquiring result.
*/
protected
function
acquire
(
$name
,
$timeout
=
0
)
{
}
/**
* This method should be extended by concrete mutex implementations. Releases lock by given name.
* @param string $name of the lock to be released.
* @return boolean release result.
*/
protected
function
release
(
$name
)
{
return
$this
->
cache
->
delete
(
"mutex.
{
$name
}
"
);
}
/**
* This method may optionally be extended by concrete mutex implementations. Checks whether lock has been
* already acquired by given name.
* @param string $name of the lock to be released.
* @return null|boolean whether lock has been already acquired. Returns `null` in case this feature
* is not supported by concrete mutex implementation.
*/
protected
function
getIsAcquired
(
$name
)
{
}
/**
* This method should be extended by concrete mutex implementations. Returns whether current mutex
* implementation can be used in a distributed environment.
* @return boolean whether current mutex implementation can be used in a distributed environment.
*/
public
function
getIsDistributed
()
{
return
$this
->
cache
instanceof
DbCache
||
$this
->
cache
instanceof
MemCache
;
}
}
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