Skip to content
Projects
Groups
Snippets
Help
Loading...
Help
Contribute to GitLab
Sign in
Toggle navigation
M
mongo-php-library
Project
Project
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
sinan
mongo-php-library
Commits
ba3efc05
Commit
ba3efc05
authored
Dec 28, 2015
by
Will Banfield
Committed by
Jeremy Mikola
Mar 14, 2016
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
Response to PR comments to mongodb-php-library pull 57
parent
edb5dd6f
Show whitespace changes
Inline
Side-by-side
Showing
7 changed files
with
308 additions
and
178 deletions
+308
-178
GridFSFileNotFoundException.php
src/Exception/GridFSFileNotFoundException.php
+2
-2
Bucket.php
src/GridFS/Bucket.php
+103
-98
GridFSCollectionsWrapper.php
src/GridFS/GridFSCollectionsWrapper.php
+133
-0
GridFsDownload.php
src/GridFS/GridFsDownload.php
+21
-26
GridFsUpload.php
src/GridFS/GridFsUpload.php
+29
-28
StreamWrapper.php
src/GridFS/StreamWrapper.php
+5
-5
SpecificationTests.php
tests/GridFS/SpecificationTests.php
+15
-19
No files found.
src/Exception/GridFSFileNotFoundException.php
View file @
ba3efc05
...
@@ -4,7 +4,7 @@ namespace MongoDB\Exception;
...
@@ -4,7 +4,7 @@ namespace MongoDB\Exception;
class
GridFSFileNotFoundException
extends
\MongoDB\Driver\Exception\RuntimeException
implements
Exception
class
GridFSFileNotFoundException
extends
\MongoDB\Driver\Exception\RuntimeException
implements
Exception
{
{
public
function
__construct
(
$fname
,
$
bucketName
,
$databaseNam
e
){
public
function
__construct
(
$fname
,
$
nameSpac
e
){
parent
::
__construct
(
sprintf
(
'Unable to find file by: %s in %s
.%s'
,
$fname
,
$databaseName
,
$bucketNam
e
));
parent
::
__construct
(
sprintf
(
'Unable to find file by: %s in %s
'
,
$fname
,
$nameSpac
e
));
}
}
}
}
src/GridFS/Bucket.php
View file @
ba3efc05
...
@@ -19,10 +19,8 @@ use MongoDB\Exception\UnexpectedValueException;
...
@@ -19,10 +19,8 @@ use MongoDB\Exception\UnexpectedValueException;
class
Bucket
class
Bucket
{
{
private
$databaseName
;
private
$databaseName
;
private
$collectionsWrapper
;
private
$options
;
private
$options
;
private
$filesCollection
;
private
$chunksCollection
;
private
$ensuredIndexes
=
false
;
/**
/**
* Constructs a GridFS bucket.
* Constructs a GridFS bucket.
*
*
...
@@ -50,122 +48,124 @@ class Bucket
...
@@ -50,122 +48,124 @@ class Bucket
'bucketName'
=>
'fs'
,
'bucketName'
=>
'fs'
,
'chunkSizeBytes'
=>
261120
,
'chunkSizeBytes'
=>
261120
,
];
];
if
(
isset
(
$options
[
'bucketName'
])
&&
!
is_string
(
$options
[
'bucketName'
]))
{
throw
new
InvalidArgumentTypeException
(
'"bucketName" option'
,
$options
[
'bucketName'
],
'string'
);
}
if
(
isset
(
$options
[
'chunkSizeBytes'
])
&&
!
is_integer
(
$options
[
'chunkSizeBytes'
]))
{
throw
new
InvalidArgumentTypeException
(
'"chunkSizeBytes" option'
,
$options
[
'chunkSizeBytes'
],
'integer'
);
}
if
(
isset
(
$options
[
'readPreference'
]))
{
if
(
!
$options
[
'readPreference'
]
instanceof
ReadPreference
)
{
throw
new
InvalidArgumentTypeException
(
'"readPreference" option'
,
$options
[
'readPreference'
],
'MongoDB\Driver\ReadPreference'
);
}
else
{
$collectionOptions
[
'readPreference'
]
=
$options
[
'readPreference'
];
}
}
if
(
isset
(
$options
[
'writeConcern'
]))
{
if
(
!
$options
[
'writeConcern'
]
instanceof
WriteConcern
)
{
throw
new
InvalidArgumentTypeException
(
'"writeConcern" option'
,
$options
[
'writeConcern'
],
'MongoDB\Driver\WriteConcern'
);
}
else
{
$collectionOptions
[
'writeConcern'
]
=
$options
[
'writeConcern'
];
}
}
$this
->
databaseName
=
(
string
)
$databaseName
;
$this
->
databaseName
=
(
string
)
$databaseName
;
$this
->
options
=
$options
;
$this
->
options
=
$options
;
$this
->
collectionsWrapper
=
new
GridFSCollectionsWrapper
(
$manager
,
$databaseName
,
$options
);
$this
->
filesCollection
=
new
Collection
(
$manager
,
sprintf
(
'%s.%s.files'
,
$this
->
databaseName
,
$options
[
'bucketName'
]),
$collectionOptions
);
$this
->
chunksCollection
=
new
Collection
(
$manager
,
sprintf
(
'%s.%s.chunks'
,
$this
->
databaseName
,
$options
[
'bucketName'
]),
$collectionOptions
);
}
}
/**
/**
*
Return the chunkSizeBytes option for this Bucket
.
*
Opens a Stream for writing the contents of a file
.
*
*
* @return integer
* @param string $filename file to upload
* @param array $options Stream Options
* @return Stream uploadStream
*/
*/
public
function
getChunkSizeBytes
()
public
function
openUploadStream
(
$filename
,
array
$options
=
[])
{
return
$this
->
options
[
'chunkSizeBytes'
];
}
public
function
getDatabaseName
()
{
{
return
$this
->
databaseName
;
$options
+=
[
'chunkSizeBytes'
=>
$this
->
options
[
'chunkSizeBytes'
]];
}
$streamOptions
=
[
public
function
getFilesCollection
()
'collectionsWrapper'
=>
$this
->
collectionsWrapper
,
{
'uploadOptions'
=>
$options
return
$this
->
filesCollection
;
];
$context
=
stream_context_create
([
'gridfs'
=>
$streamOptions
]);
return
fopen
(
sprintf
(
'gridfs://%s/%s'
,
$this
->
databaseName
,
$filename
),
'w'
,
false
,
$context
);
}
}
/**
public
function
getChunksCollection
()
* Upload a file to this bucket by specifying the source stream file
*
* @param String $filename Filename To Insert
* @param Stream $source Source Stream
* @param array $options Stream Options
* @return ObjectId
*/
public
function
uploadFromStream
(
$filename
,
$source
,
array
$options
=
[])
{
{
return
$this
->
chunksCollection
;
$options
[
'chunkSizeBytes'
]
=
$this
->
options
[
'chunkSizeBytes'
];
$gridFsStream
=
new
GridFsUpload
(
$this
->
collectionsWrapper
,
$filename
,
$options
);
return
$gridFsStream
->
uploadFromStream
(
$source
);
}
}
public
function
getBucketName
()
/**
* Opens a Stream for reading the contents of a file specified by ID.
*
* @param ObjectId $id
* @return Stream
*/
public
function
openDownloadStream
(
\MongoDB\BSON\ObjectId
$id
)
{
{
return
$this
->
options
[
'bucketName'
];
$options
=
[
'collectionsWrapper'
=>
$this
->
collectionsWrapper
];
$context
=
stream_context_create
([
'gridfs'
=>
$options
]);
return
fopen
(
sprintf
(
'gridfs://%s/%s'
,
$this
->
databaseName
,
$id
),
'r'
,
false
,
$context
);
}
}
public
function
getReadConcern
()
/**
* Downloads the contents of the stored file specified by id and writes
* the contents to the destination Stream.
* @param ObjectId $id GridFS File Id
* @param Stream $destination Destination Stream
*/
public
function
downloadToStream
(
\MongoDB\BSON\ObjectId
$id
,
$destination
)
{
{
if
(
isset
(
$this
->
options
[
'readPreference'
]))
{
$gridFsStream
=
new
GridFsDownload
(
$this
->
collectionsWrapper
,
$id
);
return
$this
->
options
[
'readPreference'
];
$gridFsStream
->
downloadToStream
(
$destination
);
}
else
{
return
null
;
}
}
}
/**
public
function
getWriteConcern
()
* Delete a file from the GridFS bucket. If the file collection entry is not found, still attempts to delete orphaned chunks
*
* @param ObjectId $id file id
* @throws GridFSFileNotFoundException
*/
public
function
delete
(
\MongoDB\BSON\ObjectId
$id
)
{
{
if
(
isset
(
$this
->
options
[
'writeConcern'
]))
{
$file
=
$this
->
collectionsWrapper
->
getFilesCollection
()
->
findOne
([
'_id'
=>
$id
]);
return
$this
->
options
[
'writeConcern'
];
$this
->
collectionsWrapper
->
getChunksCollection
()
->
deleteMany
([
'files_id'
=>
$id
]);
}
else
{
if
(
is_null
(
$file
))
{
return
null
;
throw
new
\MongoDB\Exception\GridFSFileNotFoundException
(
$id
,
$this
->
collectionsWrapper
->
getFilesCollection
()
->
getNameSpace
());
}
}
}
private
function
ensureIndexes
()
$this
->
collectionsWrapper
->
getFilesCollection
()
->
deleteOne
([
'_id'
=>
$id
]);
{
if
(
$this
->
ensuredIndexes
)
{
return
;
}
if
(
!
$this
->
isFilesCollectionEmpty
())
{
return
;
}
}
$this
->
ensureFilesIndex
();
/**
$this
->
ensureChunksIndex
();
* Open a stream to download a file from the GridFS bucket. Searches for the file by the specified name then returns a stream to the specified file
$this
->
ensuredIndexes
=
true
;
* @param string $filename name of the file to download
}
* @param int $revision the revision of the file to download
private
function
ensureChunksIndex
()
* @throws GridFSFileNotFoundException
*/
public
function
openDownloadStreamByName
(
$filename
,
$revision
=
-
1
)
{
{
foreach
(
$this
->
chunksCollection
->
listIndexes
()
as
$index
)
{
$file
=
$this
->
bucket
->
findFileRevision
(
$filename
,
$revision
);
if
(
$index
->
isUnique
()
&&
$index
->
getKey
()
===
[
'files_id'
=>
1
,
'n'
=>
1
])
{
$options
=
[
'bucket'
=>
$this
->
bucket
,
return
;
'file'
=>
$file
}
];
}
$context
=
stream_context_create
([
'gridfs'
=>
$options
]);
$this
->
chunksCollection
->
createIndex
([
'files_id'
=>
1
,
'n'
=>
1
],
[
'unique'
=>
true
]
);
return
fopen
(
sprintf
(
'gridfs://%s/%s'
,
$this
->
bucket
->
getDatabaseName
(),
$filename
),
'r'
,
false
,
$context
);
}
}
private
function
ensureFilesIndex
()
/**
* Download a file from the GridFS bucket by name. Searches for the file by the specified name then loads data into the stream
*
* @param string $filename name of the file to download
* @param int $revision the revision of the file to download
* @throws GridFSFileNotFoundException
*/
public
function
downloadToStreamByName
(
$filename
,
$destination
,
$revision
=-
1
)
{
{
foreach
(
$this
->
filesCollection
->
listIndexes
()
as
$index
)
{
$file
=
$this
->
findFileRevision
(
$filename
,
$revision
);
if
(
$index
->
getKey
()
===
[
'filename'
=>
1
,
'uploadDate'
=>
1
])
{
$gridFsStream
=
new
GridFsDownload
(
$this
->
collectionsWrapper
,
null
,
$file
);
return
;
$gridFsStream
->
downloadToStream
(
$destination
)
;
}
}
}
/**
$this
->
filesCollection
->
createIndex
([
'filename'
=>
1
,
'uploadDate'
=>
1
]);
* Find files from the GridFS bucket files collection.
}
*
private
function
isFilesCollectionEmpty
()
* @param array $filter filter to find by
* @param array $options options to
*/
public
function
find
(
$filter
,
array
$options
=
[])
{
{
return
null
===
$this
->
filesCollection
->
findOne
([],
[
return
$this
->
collectionsWrapper
->
getFilesCollection
()
->
find
(
$filter
,
$options
);
'readPreference'
=>
new
ReadPreference
(
ReadPreference
::
RP_PRIMARY
),
'projection'
=>
[
'_id'
=>
1
],
]);
}
}
public
function
findFileRevision
(
$filename
,
$revision
)
private
function
findFileRevision
(
$filename
,
$revision
)
{
{
if
(
$revision
<
0
)
{
if
(
$revision
<
0
)
{
$skip
=
abs
(
$revision
)
-
1
;
$skip
=
abs
(
$revision
)
-
1
;
...
@@ -174,10 +174,15 @@ class Bucket
...
@@ -174,10 +174,15 @@ class Bucket
$skip
=
$revision
;
$skip
=
$revision
;
$sortOrder
=
1
;
$sortOrder
=
1
;
}
}
$file
=
$this
->
filesCollection
->
findOne
([
"filename"
=>
$filename
],
[
"sort"
=>
[
"uploadDate"
=>
$sortOrder
],
"limit"
=>
1
,
"skip"
=>
$skip
]);
$filesCollection
=
$this
->
collectionsWrapper
->
getFilesCollection
();
$file
=
$filesCollection
->
findOne
([
"filename"
=>
$filename
],
[
"sort"
=>
[
"uploadDate"
=>
$sortOrder
],
"limit"
=>
1
,
"skip"
=>
$skip
]);
if
(
is_null
(
$file
))
{
if
(
is_null
(
$file
))
{
throw
new
\MongoDB\Exception\GridFSFileNotFoundException
(
$filename
,
$
this
->
getBucketName
(),
$this
->
getDatabaseName
());
;
throw
new
\MongoDB\Exception\GridFSFileNotFoundException
(
$filename
,
$
filesCollection
->
getNameSpace
())
;
}
}
return
$file
;
return
$file
;
}
}
public
function
getCollectionsWrapper
()
{
return
$this
->
collectionsWrapper
;
}
}
}
src/GridFS/GridFSCollectionsWrapper.php
0 → 100644
View file @
ba3efc05
<?php
namespace
MongoDB\GridFS
;
use
MongoDB\Collection
;
use
MongoDB\Database
;
use
MongoDB\BSON\ObjectId
;
use
MongoDB\Driver\ReadPreference
;
use
MongoDB\Driver\WriteConcern
;
use
MongoDB\Driver\Manager
;
use
MongoDB\Exception\InvalidArgumentException
;
use
MongoDB\Exception\InvalidArgumentTypeException
;
use
MongoDB\Exception\RuntimeException
;
use
MongoDB\Exception\UnexpectedValueException
;
/**
* Bucket abstracts the GridFS files and chunks collections.
*
* @api
*/
class
GridFSCollectionsWrapper
{
private
$filesCollection
;
private
$chunksCollection
;
private
$ensuredIndexes
=
false
;
/**
* Constructs a GridFS bucket.
*
* Supported options:
*
* * bucketName (string): The bucket name, which will be used as a prefix
* for the files and chunks collections. Defaults to "fs".
*
* * chunkSizeBytes (integer): The chunk size in bytes. Defaults to
* 261120 (i.e. 255 KiB).
*
* * readPreference (MongoDB\Driver\ReadPreference): Read preference.
*
* * writeConcern (MongoDB\Driver\WriteConcern): Write concern.
*
* @param Manager $manager Manager instance from the driver
* @param string $databaseName Database name
* @param array $options Bucket options
* @throws InvalidArgumentException
*/
public
function
__construct
(
Manager
$manager
,
$databaseName
,
$options
)
{
$collectionOptions
=
[];
$options
+=
[
'bucketName'
=>
'fs'
,
];
if
(
isset
(
$options
[
'readPreference'
]))
{
if
(
!
$options
[
'readPreference'
]
instanceof
ReadPreference
)
{
throw
new
InvalidArgumentTypeException
(
'"readPreference" option'
,
$options
[
'readPreference'
],
'MongoDB\Driver\ReadPreference'
);
}
else
{
$collectionOptions
[
'readPreference'
]
=
$options
[
'readPreference'
];
}
}
if
(
isset
(
$options
[
'writeConcern'
]))
{
if
(
!
$options
[
'writeConcern'
]
instanceof
WriteConcern
)
{
throw
new
InvalidArgumentTypeException
(
'"writeConcern" option'
,
$options
[
'writeConcern'
],
'MongoDB\Driver\WriteConcern'
);
}
else
{
$collectionOptions
[
'writeConcern'
]
=
$options
[
'writeConcern'
];
}
}
$this
->
filesCollection
=
new
Collection
(
$manager
,
sprintf
(
'%s.%s.files'
,
$databaseName
,
$options
[
'bucketName'
]),
$collectionOptions
);
$this
->
chunksCollection
=
new
Collection
(
$manager
,
sprintf
(
'%s.%s.chunks'
,
$databaseName
,
$options
[
'bucketName'
]),
$collectionOptions
);
}
public
function
chunkInsert
(
$toUpload
)
{
$this
->
ensureIndexes
();
$this
->
chunksCollection
->
insertOne
(
$toUpload
);
}
public
function
fileInsert
(
$toUpload
)
{
$this
->
ensureIndexes
();
$this
->
filesCollection
->
insertOne
(
$toUpload
);
}
public
function
getChunksCollection
()
{
return
$this
->
chunksCollection
;
}
public
function
getFilesCollection
()
{
return
$this
->
filesCollection
;
}
private
function
ensureIndexes
()
{
if
(
$this
->
ensuredIndexes
)
{
return
;
}
if
(
!
$this
->
isFilesCollectionEmpty
())
{
return
;
}
$this
->
ensureFilesIndex
();
$this
->
ensureChunksIndex
();
$this
->
ensuredIndexes
=
true
;
}
private
function
ensureChunksIndex
()
{
foreach
(
$this
->
chunksCollection
->
listIndexes
()
as
$index
)
{
if
(
$index
->
isUnique
()
&&
$index
->
getKey
()
===
[
'files_id'
=>
1
,
'n'
=>
1
])
{
return
;
}
}
$this
->
chunksCollection
->
createIndex
([
'files_id'
=>
1
,
'n'
=>
1
],
[
'unique'
=>
true
]);
}
private
function
ensureFilesIndex
()
{
foreach
(
$this
->
filesCollection
->
listIndexes
()
as
$index
)
{
if
(
$index
->
getKey
()
===
[
'filename'
=>
1
,
'uploadDate'
=>
1
])
{
return
;
}
}
$this
->
filesCollection
->
createIndex
([
'filename'
=>
1
,
'uploadDate'
=>
1
]);
}
private
function
isFilesCollectionEmpty
()
{
return
null
===
$this
->
filesCollection
->
findOne
([],
[
'readPreference'
=>
new
ReadPreference
(
ReadPreference
::
RP_PRIMARY
),
'projection'
=>
[
'_id'
=>
1
],
]);
}
}
src/GridFS/GridFsDownload.php
View file @
ba3efc05
...
@@ -9,7 +9,7 @@ use MongoDB\BSON\ObjectId;
...
@@ -9,7 +9,7 @@ use MongoDB\BSON\ObjectId;
*
*
* @api
* @api
*/
*/
class
GridFsDownload
extends
GridFsStream
class
GridFsDownload
{
{
private
$chunksIterator
;
private
$chunksIterator
;
private
$bytesSeen
=
0
;
private
$bytesSeen
=
0
;
...
@@ -18,46 +18,41 @@ class GridFsDownload extends GridFsStream
...
@@ -18,46 +18,41 @@ class GridFsDownload extends GridFsStream
private
$firstCheck
=
true
;
private
$firstCheck
=
true
;
private
$bufferFresh
=
true
;
private
$bufferFresh
=
true
;
private
$bufferEmpty
=
true
;
private
$bufferEmpty
=
true
;
private
$collectionsWrapper
;
private
$chunkOffset
=
0
;
private
$buffer
;
private
$file
;
/**
/**
* Constructs a GridFS download stream
* Constructs a GridFS download stream
*
*
* Supported options:
*
* * contentType (string): DEPRECATED content type to be stored with the file.
* This information should now be added to the metadata
*
* * aliases (array of strings): DEPRECATED An array of aliases.
* Applications wishing to store aliases should add an aliases field to the
* metadata document instead.
*
* * metadata (array or object): User data for the 'metadata' field of the files
* collection document.
*
* * writeConcern (MongoDB\Driver\WriteConcern): Write concern.
*
*
* @param GridFSCollectionsWrapper $collectionsWrapper File options
* @param \MongoDB\BSON\ObjectId $options File options
* @param array $options File options
* @param array $options File options
* @throws FileNotFoundException
* @throws FileNotFoundException
*/
*/
public
function
__construct
(
public
function
__construct
(
Bucket
$bucket
,
GridFSCollectionsWrapper
$collectionsWrapper
,
$objectId
,
$objectId
,
$file
=
null
$file
=
null
)
)
{
{
$this
->
collectionsWrapper
=
$collectionsWrapper
;
if
(
!
is_null
(
$file
))
{
if
(
!
is_null
(
$file
))
{
$this
->
file
=
$file
;
$this
->
file
=
$file
;
}
else
{
}
else
{
$this
->
file
=
$
bucket
->
getFilesCollection
()
->
findOne
([
'_id'
=>
$objectId
]);
$this
->
file
=
$
collectionsWrapper
->
getFilesCollection
()
->
findOne
([
'_id'
=>
$objectId
]);
if
(
is_null
(
$this
->
file
))
{
if
(
is_null
(
$this
->
file
))
{
throw
new
\MongoDB\Exception\GridFSFileNotFoundException
(
$objectId
,
$
bucket
->
getBucketName
(),
$bucket
->
getDatabaseNam
e
());
throw
new
\MongoDB\Exception\GridFSFileNotFoundException
(
$objectId
,
$
this
->
collectionsWrapper
->
getFilesCollection
()
->
getNameSpac
e
());
}
}
}
}
if
(
$this
->
file
->
length
>
0
)
{
if
(
$this
->
file
->
length
>
=
0
)
{
$cursor
=
$
bucket
->
getChunksCollection
()
->
find
([
'files_id'
=>
$this
->
file
->
_id
],
[
'sort'
=>
[
'n'
=>
1
]]);
$cursor
=
$
this
->
collectionsWrapper
->
getChunksCollection
()
->
find
([
'files_id'
=>
$this
->
file
->
_id
],
[
'sort'
=>
[
'n'
=>
1
]]);
$this
->
chunksIterator
=
new
\IteratorIterator
(
$cursor
);
$this
->
chunksIterator
=
new
\IteratorIterator
(
$cursor
);
$this
->
numChunks
=
ceil
(
$this
->
file
->
length
/
$this
->
file
->
chunkSize
);
$this
->
numChunks
=
ceil
(
$this
->
file
->
length
/
$this
->
file
->
chunkSize
);
}
}
parent
::
__construct
(
$bucket
);
$this
->
buffer
=
fopen
(
'php://temp'
,
'w+'
);
}
}
/**
/**
* Reads data from a stream into GridFS
* Reads data from a stream into GridFS
...
@@ -104,7 +99,7 @@ class GridFsDownload extends GridFsStream
...
@@ -104,7 +99,7 @@ class GridFsDownload extends GridFsStream
private
function
advanceChunks
()
private
function
advanceChunks
()
{
{
if
(
$this
->
n
>=
$this
->
numChunks
)
{
if
(
$this
->
chunkOffset
>=
$this
->
numChunks
)
{
$this
->
iteratorEmpty
=
true
;
$this
->
iteratorEmpty
=
true
;
return
false
;
return
false
;
}
}
...
@@ -117,22 +112,22 @@ class GridFsDownload extends GridFsStream
...
@@ -117,22 +112,22 @@ class GridFsDownload extends GridFsStream
if
(
!
$this
->
chunksIterator
->
valid
())
{
if
(
!
$this
->
chunksIterator
->
valid
())
{
throw
new
\MongoDB\Exception\GridFSCorruptFileException
();
throw
new
\MongoDB\Exception\GridFSCorruptFileException
();
}
}
if
(
$this
->
chunksIterator
->
current
()
->
n
!=
$this
->
n
)
{
if
(
$this
->
chunksIterator
->
current
()
->
n
!=
$this
->
chunkOffset
)
{
throw
new
\MongoDB\Exception\GridFSCorruptFileException
();
throw
new
\MongoDB\Exception\GridFSCorruptFileException
();
}
}
$chunkSizeIs
=
strlen
(
$this
->
chunksIterator
->
current
()
->
data
->
getData
());
$chunkSizeIs
=
strlen
(
$this
->
chunksIterator
->
current
()
->
data
->
getData
());
if
(
$this
->
n
==
$this
->
numChunks
-
1
)
{
if
(
$this
->
chunkOffset
==
$this
->
numChunks
-
1
)
{
$chunkSizeShouldBe
=
$this
->
file
->
length
-
$this
->
bytesSeen
;
$chunkSizeShouldBe
=
$this
->
file
->
length
-
$this
->
bytesSeen
;
if
(
$chunkSizeShouldBe
!=
$chunkSizeIs
)
{
if
(
$chunkSizeShouldBe
!=
$chunkSizeIs
)
{
throw
new
\MongoDB\Exception\GridFSCorruptFileException
();
throw
new
\MongoDB\Exception\GridFSCorruptFileException
();
}
}
}
else
if
(
$this
->
n
<
$this
->
numChunks
-
1
)
{
}
else
if
(
$this
->
chunkOffset
<
$this
->
numChunks
-
1
)
{
if
(
$chunkSizeIs
!=
$this
->
file
->
chunkSize
)
{
if
(
$chunkSizeIs
!=
$this
->
file
->
chunkSize
)
{
throw
new
\MongoDB\Exception\GridFSCorruptFileException
();
throw
new
\MongoDB\Exception\GridFSCorruptFileException
();
}
}
}
}
$this
->
bytesSeen
+=
$chunkSizeIs
;
$this
->
bytesSeen
+=
$chunkSizeIs
;
$this
->
n
++
;
$this
->
chunkOffset
++
;
return
true
;
return
true
;
}
}
public
function
close
()
public
function
close
()
...
...
src/GridFS/GridFsUpload.php
View file @
ba3efc05
...
@@ -11,12 +11,17 @@ use MongoDB\BSON;
...
@@ -11,12 +11,17 @@ use MongoDB\BSON;
*
*
* @api
* @api
*/
*/
class
GridFsUpload
extends
GridFsStream
class
GridFsUpload
{
{
private
$ctx
;
private
$ctx
;
private
$bufferLength
;
private
$bufferLength
=
0
;
private
$indexChecker
;
private
$indexChecker
;
private
$length
=
0
;
private
$length
=
0
;
private
$collectionsWrapper
;
private
$chunkOffset
=
0
;
private
$chunkSize
;
private
$buffer
;
private
$file
;
/**
/**
* Constructs a GridFS upload stream
* Constructs a GridFS upload stream
*
*
...
@@ -34,26 +39,28 @@ class GridFsUpload extends GridFsStream
...
@@ -34,26 +39,28 @@ class GridFsUpload extends GridFsStream
*
*
* * writeConcern (MongoDB\Driver\WriteConcern): Write concern.
* * writeConcern (MongoDB\Driver\WriteConcern): Write concern.
*
*
*
@param \MongoDB\Collection $filesCollection Files Collection
*
* chunkSizeBytes: size of each chunk
*
@param \MongoDB\Collection $chunksCollection Chunks Collection
*
* @param
int32 $chunkSizeBytes Size of chunk
* @param
GridFSCollectionsWrapper $collectionsWrapper Files Collection
* @param string $filename Filename to insert
* @param string $filename Filename to insert
* @param array $options File options
* @param array $options File options
* @throws InvalidArgumentException
* @throws InvalidArgumentException
*/
*/
public
function
__construct
(
public
function
__construct
(
Bucket
$bucket
,
GridFsCollectionsWrapper
$collectionsWrapper
,
$filename
,
$filename
,
array
$options
=
[]
array
$options
=
[]
)
)
{
{
$this
->
bufferLength
=
0
;
$this
->
ctx
=
hash_init
(
'md5'
);
$this
->
ctx
=
hash_init
(
'md5'
);
$this
->
collectionsWrapper
=
$collectionsWrapper
;
$this
->
buffer
=
fopen
(
'php://temp'
,
'w+'
);
$this
->
chunkSize
=
$options
[
'chunkSizeBytes'
];
$uploadDate
=
time
();
$uploadDate
=
time
();
$objectId
=
new
\MongoDB\BSON\ObjectId
();
$objectId
=
new
\MongoDB\BSON\ObjectId
();
$main_file
=
[
$main_file
=
[
"chunkSize"
=>
$
bucket
->
getChunkSizeBytes
()
,
"chunkSize"
=>
$
this
->
chunkSize
,
"filename"
=>
$filename
,
"filename"
=>
$filename
,
"uploadDate"
=>
$uploadDate
,
"uploadDate"
=>
$uploadDate
,
"_id"
=>
$objectId
"_id"
=>
$objectId
...
@@ -83,7 +90,7 @@ class GridFsUpload extends GridFsStream
...
@@ -83,7 +90,7 @@ class GridFsUpload extends GridFsStream
}
}
}
}
$this
->
file
=
array_merge
(
$main_file
,
$fileOptions
);
$this
->
file
=
array_merge
(
$main_file
,
$fileOptions
);
parent
::
__construct
(
$bucket
);
}
}
/**
/**
* Reads data from a stream into GridFS
* Reads data from a stream into GridFS
...
@@ -93,8 +100,6 @@ class GridFsUpload extends GridFsStream
...
@@ -93,8 +100,6 @@ class GridFsUpload extends GridFsStream
*/
*/
public
function
uploadFromStream
(
$source
)
public
function
uploadFromStream
(
$source
)
{
{
$this
->
bucket
->
ensureIndexes
();
if
(
!
is_resource
(
$source
)
||
get_resource_type
(
$source
)
!=
"stream"
)
{
if
(
!
is_resource
(
$source
)
||
get_resource_type
(
$source
)
!=
"stream"
)
{
throw
new
UnexpectedTypeException
(
'stream'
,
$source
);
throw
new
UnexpectedTypeException
(
'stream'
,
$source
);
}
else
{
}
else
{
...
@@ -103,7 +108,7 @@ class GridFsUpload extends GridFsStream
...
@@ -103,7 +108,7 @@ class GridFsUpload extends GridFsStream
// throw new InvalidArgumentException("stream not readable");
// throw new InvalidArgumentException("stream not readable");
//issue being that php's is_readable reports native streams as not readable like php://temp
//issue being that php's is_readable reports native streams as not readable like php://temp
}
}
while
(
$data
=
fread
(
$source
,
$this
->
bucket
->
getChunkSizeBytes
()
))
{
while
(
$data
=
fread
(
$source
,
$this
->
chunkSize
))
{
$this
->
insertChunk
(
$data
);
$this
->
insertChunk
(
$data
);
}
}
return
$this
->
fileCollectionInsert
();
return
$this
->
fileCollectionInsert
();
...
@@ -116,17 +121,15 @@ class GridFsUpload extends GridFsStream
...
@@ -116,17 +121,15 @@ class GridFsUpload extends GridFsStream
*/
*/
public
function
insertChunks
(
$toWrite
)
public
function
insertChunks
(
$toWrite
)
{
{
$this
->
bucket
->
ensureIndexes
();
$readBytes
=
0
;
$readBytes
=
0
;
while
(
$readBytes
!=
strlen
(
$toWrite
))
{
while
(
$readBytes
!=
strlen
(
$toWrite
))
{
$addToBuffer
=
substr
(
$toWrite
,
$readBytes
,
$this
->
bucket
->
getChunkSizeBytes
()
-
$this
->
bufferLength
);
$addToBuffer
=
substr
(
$toWrite
,
$readBytes
,
$this
->
chunkSize
-
$this
->
bufferLength
);
fwrite
(
$this
->
buffer
,
$addToBuffer
);
fwrite
(
$this
->
buffer
,
$addToBuffer
);
$readBytes
+=
strlen
(
$addToBuffer
);
$readBytes
+=
strlen
(
$addToBuffer
);
$this
->
bufferLength
+=
strlen
(
$addToBuffer
);
$this
->
bufferLength
+=
strlen
(
$addToBuffer
);
if
(
$this
->
bufferLength
==
$this
->
bucket
->
getChunkSizeBytes
()
)
{
if
(
$this
->
bufferLength
==
$this
->
chunkSize
)
{
rewind
(
$this
->
buffer
);
rewind
(
$this
->
buffer
);
$this
->
insertChunk
(
fread
(
$this
->
buffer
,
$this
->
bucket
->
getChunkSizeBytes
()
));
$this
->
insertChunk
(
stream_get_contents
(
$this
->
buffer
));
ftruncate
(
$this
->
buffer
,
0
);
ftruncate
(
$this
->
buffer
,
0
);
$this
->
bufferLength
=
0
;
$this
->
bufferLength
=
0
;
}
}
...
@@ -140,30 +143,28 @@ class GridFsUpload extends GridFsStream
...
@@ -140,30 +143,28 @@ class GridFsUpload extends GridFsStream
public
function
close
()
public
function
close
()
{
{
rewind
(
$this
->
buffer
);
rewind
(
$this
->
buffer
);
$cached
=
fread
(
$this
->
buffer
,
$this
->
bucket
->
getChunkSizeBytes
()
);
$cached
=
stream_get_contents
(
$this
->
buffer
);
if
(
strlen
(
$cached
)
>
0
)
{
if
(
strlen
(
$cached
)
>
0
)
{
insertChunk
(
$cached
);
$this
->
insertChunk
(
$cached
);
}
}
fclose
(
$this
->
buffer
);
fclose
(
$this
->
buffer
);
$this
->
fileCollectionInsert
();
$this
->
fileCollectionInsert
();
}
}
private
function
insertChunk
(
$data
)
private
function
insertChunk
(
$data
)
{
{
$toUpload
=
[
"files_id"
=>
$this
->
file
[
'_id'
],
"n"
=>
$this
->
n
,
"data"
=>
new
\MongoDB\BSON\Binary
(
$data
,
\MongoDB\BSON\Binary
::
TYPE_GENERIC
)];
$toUpload
=
[
"files_id"
=>
$this
->
file
[
'_id'
],
"n"
=>
$this
->
chunkOffset
,
"data"
=>
new
\MongoDB\BSON\Binary
(
$data
,
\MongoDB\BSON\Binary
::
TYPE_GENERIC
)];
hash_update
(
$this
->
ctx
,
$data
);
hash_update
(
$this
->
ctx
,
$data
);
$this
->
bucket
->
chunkInsert
(
$toUpload
);
$this
->
collectionsWrapper
->
chunkInsert
(
$toUpload
);
$this
->
length
+=
strlen
(
$data
);
$this
->
length
+=
strlen
(
$data
);
$this
->
n
++
;
$this
->
chunkOffset
++
;
}
}
private
function
fileCollectionInsert
()
private
function
fileCollectionInsert
()
{
{
$md5
=
hash_final
(
$this
->
ctx
);
$md5
=
hash_final
(
$this
->
ctx
);
$this
->
file
=
array_merge
(
$this
->
file
,
[
'length'
=>
$this
->
length
,
'md5'
=>
$md5
]);
$this
->
file
=
array_merge
(
$this
->
file
,
[
'length'
=>
$this
->
length
,
'md5'
=>
$md5
]);
$this
->
bucket
->
fileInsert
(
$this
->
file
);
$this
->
collectionsWrapper
->
fileInsert
(
$this
->
file
);
return
$this
->
file
[
'_id'
];
return
$this
->
file
[
'_id'
];
}
}
}
}
src/GridFS/StreamWrapper.php
View file @
ba3efc05
...
@@ -21,7 +21,7 @@ class StreamWrapper
...
@@ -21,7 +21,7 @@ class StreamWrapper
private
$protocol
=
'gridfs'
;
private
$protocol
=
'gridfs'
;
private
$mode
;
private
$mode
;
private
$gridFsStream
;
private
$gridFsStream
;
private
$
bucket
;
private
$
collectionsWrapper
;
/**
/**
* Register the GridFS stream wrapper.
* Register the GridFS stream wrapper.
...
@@ -58,7 +58,7 @@ class StreamWrapper
...
@@ -58,7 +58,7 @@ class StreamWrapper
{
{
$this
->
initProtocol
(
$path
);
$this
->
initProtocol
(
$path
);
$context
=
stream_context_get_options
(
$this
->
context
);
$context
=
stream_context_get_options
(
$this
->
context
);
$this
->
bucket
=
$context
[
'gridfs'
][
'bucket
'
];
$this
->
collectionsWrapper
=
$context
[
'gridfs'
][
'collectionsWrapper
'
];
$this
->
mode
=
$mode
;
$this
->
mode
=
$mode
;
switch
(
$this
->
mode
)
{
switch
(
$this
->
mode
)
{
case
'w'
:
return
$this
->
openWriteStream
();
case
'w'
:
return
$this
->
openWriteStream
();
...
@@ -69,17 +69,17 @@ class StreamWrapper
...
@@ -69,17 +69,17 @@ class StreamWrapper
public
function
openWriteStream
()
{
public
function
openWriteStream
()
{
$context
=
stream_context_get_options
(
$this
->
context
);
$context
=
stream_context_get_options
(
$this
->
context
);
$options
=
$context
[
'gridfs'
][
'uploadOptions'
];
$options
=
$context
[
'gridfs'
][
'uploadOptions'
];
$this
->
gridFsStream
=
new
GridFsUpload
(
$this
->
bucket
,
$this
->
identifier
,
$options
);
$this
->
gridFsStream
=
new
GridFsUpload
(
$this
->
collectionsWrapper
,
$this
->
identifier
,
$options
);
return
true
;
return
true
;
}
}
public
function
openReadStream
()
{
public
function
openReadStream
()
{
$context
=
stream_context_get_options
(
$this
->
context
);
$context
=
stream_context_get_options
(
$this
->
context
);
if
(
isset
(
$context
[
'gridfs'
][
'file'
])){
if
(
isset
(
$context
[
'gridfs'
][
'file'
])){
$this
->
gridFsStream
=
new
GridFsDownload
(
$this
->
bucket
,
null
,
$context
[
'gridfs'
][
'file'
]);
$this
->
gridFsStream
=
new
GridFsDownload
(
$this
->
collectionsWrapper
,
null
,
$context
[
'gridfs'
][
'file'
]);
}
else
{
}
else
{
$objectId
=
new
\MongoDB\BSON\ObjectId
(
$this
->
identifier
);
$objectId
=
new
\MongoDB\BSON\ObjectId
(
$this
->
identifier
);
$this
->
gridFsStream
=
new
GridFsDownload
(
$this
->
bucket
,
$objectId
);
$this
->
gridFsStream
=
new
GridFsDownload
(
$this
->
collectionsWrapper
,
$objectId
);
}
}
return
true
;
return
true
;
}
}
...
...
tests/GridFS/SpecificationTests.php
View file @
ba3efc05
...
@@ -45,7 +45,6 @@ class SpecificationTests extends FunctionalTestCase
...
@@ -45,7 +45,6 @@ class SpecificationTests extends FunctionalTestCase
$options
=
[];
$options
=
[];
}
}
$this
->
bucket
=
new
\MongoDB\GridFS\Bucket
(
$this
->
manager
,
$this
->
getDatabaseName
(),
$this
->
fixTypes
(
$options
,
false
));
$this
->
bucket
=
new
\MongoDB\GridFS\Bucket
(
$this
->
manager
,
$this
->
getDatabaseName
(),
$this
->
fixTypes
(
$options
,
false
));
$this
->
bucketReadWriter
=
new
\MongoDB\GridFS\BucketReadWriter
(
$this
->
bucket
);
$func
=
$test
[
'act'
][
'operation'
]
.
"Command"
;
$func
=
$test
[
'act'
][
'operation'
]
.
"Command"
;
$error
=
null
;
$error
=
null
;
try
{
try
{
...
@@ -53,37 +52,35 @@ class SpecificationTests extends FunctionalTestCase
...
@@ -53,37 +52,35 @@ class SpecificationTests extends FunctionalTestCase
}
catch
(
\MongoDB\Exception\Exception
$e
)
{
}
catch
(
\MongoDB\Exception\Exception
$e
)
{
$error
=
$e
;
$error
=
$e
;
}
}
$errors
=
[
'FileNotFound'
=>
'\MongoDB\Exception\GridFSFileNotFoundException'
,
$errors
=
[
'FileNotFound'
=>
'\MongoDB\Exception\GridFSFileNotFoundException'
,
'ChunkIsMissing'
=>
'\MongoDB\Exception\GridFSCorruptFileException'
,
'ChunkIsMissing'
=>
'\MongoDB\Exception\GridFSCorruptFileException'
,
'ExtraChunk'
=>
'\MongoDB\Exception\GridFSCorruptFileException'
,
'ExtraChunk'
=>
'\MongoDB\Exception\GridFSCorruptFileException'
,
'ChunkIsWrongSize'
=>
'\MongoDB\Exception\GridFSCorruptFileException'
,
'ChunkIsWrongSize'
=>
'\MongoDB\Exception\GridFSCorruptFileException'
,
'RevisionNotFound'
=>
'\MongoDB\Exception\GridFSFileNotFoundException'
'RevisionNotFound'
=>
'\MongoDB\Exception\GridFSFileNotFoundException'
];
];
if
(
!
isset
(
$test
[
'assert'
][
'error'
]))
{
if
(
!
isset
(
$test
[
'assert'
][
'error'
]))
{
$this
->
assertNull
(
$error
);
$this
->
assertNull
(
$error
);
}
else
{
}
else
{
$shouldError
=
$test
[
'assert'
][
'error'
];
$shouldError
=
$test
[
'assert'
][
'error'
];
$this
->
assertTrue
(
$error
instanceof
$errors
[
$shouldError
]);
$this
->
assertTrue
(
$error
instanceof
$errors
[
$shouldError
]);
}
}
$fixedAssert
=
$this
->
fixTypes
(
$test
[
'assert'
],
false
);
if
(
isset
(
$test
[
'assert'
][
'result'
]))
{
if
(
isset
(
$test
[
'assert'
][
'result'
]))
{
$testResult
=
$test
[
'assert'
][
'result'
];
$testResult
=
$test
[
'assert'
][
'result'
];
if
(
$testResult
==
"&result"
)
{
if
(
$testResult
==
'&result'
)
{
$test
[
'assert'
][
'result'
]
=
$result
;
$test
[
'assert'
][
'result'
]
=
$result
;
}
}
if
(
$testResult
==
"void"
)
{
if
(
$testResult
==
"void"
)
{
$
fixedAssert
[
'result'
]
=
null
;
$
test
[
'assert'
]
[
'result'
]
=
null
;
}
}
$this
->
assertEquals
(
$result
,
$fixedAssert
[
'result'
]);
$fixedAssertFalse
=
$this
->
fixTypes
(
$test
[
'assert'
],
false
);
$this
->
assertEquals
(
$result
,
$fixedAssertFalse
[
'result'
]);
}
}
$fixedAssertTrue
=
$this
->
fixTypes
(
$test
[
'assert'
],
true
);
if
(
isset
(
$test
[
'assert'
][
'data'
]))
{
if
(
isset
(
$test
[
'assert'
][
'data'
]))
{
$this
->
runCommands
(
$fixedAssert
[
'data'
],
$result
);
$this
->
runCommands
(
$fixedAssert
True
[
'data'
],
$result
);
$this
->
collectionsEqual
(
$this
->
collections
[
'expected.files'
],
$this
->
bucket
->
getFilesCollection
());
$this
->
collectionsEqual
(
$this
->
collections
[
'expected.files'
],
$this
->
bucket
->
get
CollectionsWrapper
()
->
get
FilesCollection
());
if
(
isset
(
$this
->
collections
[
'expected.chunks'
]))
{
if
(
isset
(
$this
->
collections
[
'expected.chunks'
]))
{
$this
->
collectionsEqual
(
$this
->
collections
[
'expected.chunks'
],
$this
->
bucket
->
getChunksCollection
());
$this
->
collectionsEqual
(
$this
->
collections
[
'expected.chunks'
],
$this
->
bucket
->
getC
ollectionsWrapper
()
->
getC
hunksCollection
());
}
}
}
}
}
}
...
@@ -91,8 +88,7 @@ class SpecificationTests extends FunctionalTestCase
...
@@ -91,8 +88,7 @@ class SpecificationTests extends FunctionalTestCase
public
function
provideSpecificationTests
()
public
function
provideSpecificationTests
()
{
{
$testPath
=
getcwd
()
.
'/tests/GridFS/Specification/tests/download_by_name.json'
;
$testPath
=
__DIR__
.
'/Specification/tests/*.json'
;
$testArgs
=
[];
$testArgs
=
[];
foreach
(
glob
(
$testPath
)
as
$filename
)
{
foreach
(
glob
(
$testPath
)
as
$filename
)
{
$fileContents
=
file_get_contents
(
$filename
);
$fileContents
=
file_get_contents
(
$filename
);
...
@@ -214,7 +210,7 @@ class SpecificationTests extends FunctionalTestCase
...
@@ -214,7 +210,7 @@ class SpecificationTests extends FunctionalTestCase
$stream
=
fopen
(
'php://temp'
,
'w+'
);
$stream
=
fopen
(
'php://temp'
,
'w+'
);
fwrite
(
$stream
,
$args
[
'source'
]);
fwrite
(
$stream
,
$args
[
'source'
]);
rewind
(
$stream
);
rewind
(
$stream
);
$result
=
$this
->
bucket
ReadWriter
->
uploadFromStream
(
$args
[
'filename'
],
$stream
,
$args
[
'options'
]);
$result
=
$this
->
bucket
->
uploadFromStream
(
$args
[
'filename'
],
$stream
,
$args
[
'options'
]);
fclose
(
$stream
);
fclose
(
$stream
);
return
$result
;
return
$result
;
}
}
...
@@ -224,7 +220,7 @@ class SpecificationTests extends FunctionalTestCase
...
@@ -224,7 +220,7 @@ class SpecificationTests extends FunctionalTestCase
$streamWrapper
=
new
\MongoDB\GridFS\StreamWrapper
();
$streamWrapper
=
new
\MongoDB\GridFS\StreamWrapper
();
$streamWrapper
->
register
(
$this
->
manager
);
$streamWrapper
->
register
(
$this
->
manager
);
$stream
=
fopen
(
'php://temp'
,
'w+'
);
$stream
=
fopen
(
'php://temp'
,
'w+'
);
$this
->
bucket
ReadWriter
->
downloadToStream
(
$args
[
'id'
],
$stream
);
$this
->
bucket
->
downloadToStream
(
$args
[
'id'
],
$stream
);
rewind
(
$stream
);
rewind
(
$stream
);
$result
=
stream_get_contents
(
$stream
);
$result
=
stream_get_contents
(
$stream
);
fclose
(
$stream
);
fclose
(
$stream
);
...
@@ -233,7 +229,7 @@ class SpecificationTests extends FunctionalTestCase
...
@@ -233,7 +229,7 @@ class SpecificationTests extends FunctionalTestCase
function
deleteCommand
(
$args
)
function
deleteCommand
(
$args
)
{
{
$args
=
$this
->
fixTypes
(
$args
,
false
);
$args
=
$this
->
fixTypes
(
$args
,
false
);
$this
->
bucket
ReadWriter
->
delete
(
$args
[
'id'
]);
$this
->
bucket
->
delete
(
$args
[
'id'
]);
}
}
function
download_by_nameCommand
(
$args
)
function
download_by_nameCommand
(
$args
)
{
{
...
@@ -242,9 +238,9 @@ class SpecificationTests extends FunctionalTestCase
...
@@ -242,9 +238,9 @@ class SpecificationTests extends FunctionalTestCase
$streamWrapper
->
register
(
$this
->
manager
);
$streamWrapper
->
register
(
$this
->
manager
);
$stream
=
fopen
(
'php://temp'
,
'w+'
);
$stream
=
fopen
(
'php://temp'
,
'w+'
);
if
(
isset
(
$args
[
'options'
][
'revision'
]))
{
if
(
isset
(
$args
[
'options'
][
'revision'
]))
{
$this
->
bucket
ReadWriter
->
downloadToStreamByName
(
$args
[
'filename'
],
$stream
,
$args
[
'options'
][
'revision'
]);
$this
->
bucket
->
downloadToStreamByName
(
$args
[
'filename'
],
$stream
,
$args
[
'options'
][
'revision'
]);
}
else
{
}
else
{
$this
->
bucket
ReadWriter
->
downloadToStreamByName
(
$args
[
'filename'
],
$stream
);
$this
->
bucket
->
downloadToStreamByName
(
$args
[
'filename'
],
$stream
);
}
}
rewind
(
$stream
);
rewind
(
$stream
);
$result
=
stream_get_contents
(
$stream
);
$result
=
stream_get_contents
(
$stream
);
...
...
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