Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings

Commit bfa6eea

Browse files
committed
wip
1 parent ed4cfa1 commit bfa6eea

File tree

4 files changed

+472
-1
lines changed

4 files changed

+472
-1
lines changed

‎composer.json‎

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,8 @@
1111
],
1212
"require": {
1313
"php": ">=5.4",
14-
"ext-mongodb": "^1.0.0"
14+
"ext-mongodb": "^1.0.0",
15+
"ext-hash": "*"
1516
},
1617
"autoload": {
1718
"psr-4": { "MongoDB\\": "src/" },

‎src/GridFS/Bucket.php‎

Lines changed: 171 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,171 @@
1+
<?php
2+
3+
namespace MongoDB\GridFS;
4+
5+
use MongoDB\Collection;
6+
use MongoDB\Database;
7+
use MongoDB\BSON\ObjectId;
8+
use MongoDB\Driver\ReadPreference;
9+
use MongoDB\Driver\WriteConcern;
10+
use MongoDB\Exception\InvalidArgumentException;
11+
use MongoDB\Exception\InvalidArgumentTypeException;
12+
use MongoDB\Exception\RuntimeException;
13+
use MongoDB\Exception\UnexpectedValueException;
14+
15+
/**
16+
* Bucket abstracts the GridFS files and chunks collections.
17+
*
18+
* @api
19+
*/
20+
class Bucket
21+
{
22+
private $manager;
23+
private $databaseName;
24+
private $options;
25+
26+
private $filesCollection;
27+
private $chunksCollection;
28+
29+
private $ensuredIndexes = false;
30+
31+
/**
32+
* Constructs a GridFS bucket.
33+
*
34+
* Supported options:
35+
*
36+
* * bucketName (string): The bucket name, which will be used as a prefix
37+
* for the files and chunks collections. Defaults to "fs".
38+
*
39+
* * chunkSizeBytes (integer): The chunk size in bytes. Defaults to
40+
* 261120 (i.e. 255 KiB).
41+
*
42+
* * readPreference (MongoDB\Driver\ReadPreference): Read preference.
43+
*
44+
* * writeConcern (MongoDB\Driver\WriteConcern): Write concern.
45+
*
46+
* @param Manager $manager Manager instance from the driver
47+
* @param string $databaseName Database name
48+
* @param array $options Bucket options
49+
* @throws InvalidArgumentException
50+
*/
51+
public function __construct(Manager $manager, $databaseName, array $options = [])
52+
{
53+
$options += [
54+
'bucketName' => 'fs',
55+
'chunkSizeBytes' => 261120,
56+
];
57+
58+
if (isset($options['bucketName']) && ! is_string($options['bucketName'])) {
59+
throw new InvalidArgumentTypeException('"bucketName" option', $options['bucketName'], 'string');
60+
}
61+
62+
if (isset($options['chunkSizeBytes']) && ! is_integer($options['chunkSizeBytes'])) {
63+
throw new InvalidArgumentTypeException('"chunkSizeBytes" option', $options['chunkSizeBytes'], 'integer');
64+
}
65+
66+
if (isset($options['readPreference']) && ! $options['readPreference'] instanceof ReadPreference) {
67+
throw new InvalidArgumentTypeException('"readPreference" option', $options['readPreference'], 'MongoDB\Driver\ReadPreference');
68+
}
69+
70+
if (isset($options['writeConcern']) && ! $options['writeConcern'] instanceof WriteConcern) {
71+
throw new InvalidArgumentTypeException('"writeConcern" option', $options['writeConcern'], 'MongoDB\Driver\WriteConcern');
72+
}
73+
74+
$this->manager = $manager;
75+
$this->databaseName = (string) $databaseName;
76+
$this->options = $options;
77+
78+
$this->filesCollection = new Collection(
79+
$manager,
80+
sprintf('%s.%s.files', $this->databaseName, $options['bucketName']),
81+
isset($options['writeConcern']) ? $options['writeConcern'] : null,
82+
isset($options['readPreference']) ? $options['readPreference'] : null,
83+
);
84+
85+
$this->chunksCollection = new Collection(
86+
$manager,
87+
sprintf('%s.%s.chunks', $this->databaseName, $options['bucketName']),
88+
isset($options['writeConcern']) ? $options['writeConcern'] : null,
89+
isset($options['readPreference']) ? $options['readPreference'] : null,
90+
);
91+
}
92+
93+
/**
94+
* Opens a Stream for reading the contents of a file specified by ID.
95+
*
96+
* @param ObjectId $id
97+
* @return Stream
98+
*/
99+
public function openDownloadStream(ObjectId $id)
100+
{
101+
102+
}
103+
104+
/**
105+
* Return the chunkSizeBytes option for this Bucket.
106+
*
107+
* @return integer
108+
*/
109+
public function getChunkSizeBytes()
110+
{
111+
return $this->options['chunkSizeBytes'];
112+
}
113+
114+
/**
115+
* Opens a Stream for writing the contents of a file.
116+
*
117+
* @param ObjectId $id
118+
* @return Stream
119+
*/
120+
public function openUploadStream($filename, array $options = [])
121+
{
122+
123+
}
124+
125+
private function ensureIndexes()
126+
{
127+
// Indexes should only be ensured once before the first write operation
128+
if ($this->ensuredIndexes) {
129+
return;
130+
}
131+
132+
if ( ! $this->isFilesCollectionEmpty()) {
133+
return;
134+
}
135+
136+
$this->ensureFilesIndex();
137+
$this->ensureChunksIndex();
138+
139+
$this->ensuredIndexes = true;
140+
}
141+
142+
private function ensureChunksIndex()
143+
{
144+
foreach ($this->chunksCollection->listIndexes() as $index) {
145+
if ($index->isUnique() && $index->getKey() === ['files_id' => 1, 'n' => 1]) {
146+
return;
147+
}
148+
}
149+
150+
$this->chunksCollection->createIndex(['files_id' => 1, 'n' => 1], ['unique' => true]);
151+
}
152+
153+
private function ensureFilesIndex()
154+
{
155+
foreach ($this->filesCollection->listIndexes() as $index) {
156+
if ($index->getKey() === ['filename' => 1, 'uploadDate' => 1]) {
157+
return;
158+
}
159+
}
160+
161+
$this->filesCollection->createIndex(['filename' => 1, 'uploadDate' => 1]);
162+
}
163+
164+
private function isFilesCollectionEmpty()
165+
{
166+
return null === $this->filesCollection->findOne([], [
167+
'readPreference' => new ReadPreference(ReadPreference::RP_PRIMARY),
168+
'projection' => ['_id' => 1],
169+
]);
170+
}
171+
}

‎src/GridFS/StreamWrapper.php‎

Lines changed: 197 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,197 @@
1+
<?php
2+
3+
namespace MongoDB\GridFS;
4+
5+
use MongoDB\Driver\Server;
6+
use MongoDB\Exception\InvalidArgumentException;
7+
use MongoDB\Exception\InvalidArgumentTypeException;
8+
use MongoDB\Exception\RuntimeException;
9+
use MongoDB\Exception\UnexpectedValueException;
10+
11+
/**
12+
* Stream wrapper for reading and writing a GridFS file.
13+
*
14+
* @internal
15+
* @see MongoDB\GridFS\Bucket::openUploadStream()
16+
*/
17+
class StreamWrapper
18+
{
19+
public $context;
20+
21+
private $bucket;
22+
private $filename;
23+
private $options;
24+
25+
private $protocol = 'gridfs';
26+
27+
private $at;
28+
private $hashContext;
29+
30+
/**
31+
* Register the GridFS stream wrapper.
32+
*
33+
* @param Manager $manager Manager instance from the driver
34+
* @param string $protocol Protocol to register
35+
*/
36+
public static function register(Manager $manager, $protocol = 'gridfs')
37+
{
38+
if (in_array($protocol, stream_get_wrappers())) {
39+
stream_wrapper_unregister($protocol);
40+
}
41+
42+
// Set the client passed in as the default stream context client
43+
stream_wrapper_register($protocol, get_called_class(), STREAM_IS_URL);
44+
$default = stream_context_get_options(stream_context_get_default());
45+
$default[$protocol]['manager'] = $manager;
46+
stream_context_set_default($default);
47+
}
48+
49+
public function stream_open($path, $mode, $options, &$openedPath)
50+
{
51+
$this->initProtocol($path);
52+
$this->params = $this->getDatabase($path);
53+
$this->mode = rtrim($mode, 'bt');
54+
55+
if ($errors = $this->validate($path, $this->mode)) {
56+
return $this->triggerError($errors);
57+
}
58+
59+
return $this->boolCall(function() use ($path) {
60+
switch ($this->mode) {
61+
case 'r': return $this->openReadStream($path);
62+
case 'a': return $this->openAppendStream($path);
63+
default: return $this->openWriteStream($path);
64+
}
65+
});
66+
}
67+
68+
/**
69+
* Validates the provided stream arguments for fopen and returns an array
70+
* of errors.
71+
*/
72+
private function validate($path, $mode)
73+
{
74+
$errors = [];
75+
if (!$this->getOption('Key')) {
76+
$errors[] = 'Cannot open a bucket. You must specify a path in the '
77+
. 'form of s3://bucket/key';
78+
}
79+
if (!in_array($mode, ['r', 'w', 'a', 'x'])) {
80+
$errors[] = "Mode not supported: {$mode}. "
81+
. "Use one 'r', 'w', 'a', or 'x'.";
82+
}
83+
// When using mode "x" validate if the file exists before attempting
84+
// to read
85+
if ($mode == 'x' &&
86+
$this->getClient()->doesObjectExist(
87+
$this->getOption('Bucket'),
88+
$this->getOption('Key'),
89+
$this->getOptions(true)
90+
)
91+
) {
92+
$errors[] = "{$path} already exists on Amazon S3";
93+
}
94+
return $errors;
95+
}
96+
97+
/**
98+
* Trigger one or more errors
99+
*
100+
* @param string|array $errors Errors to trigger
101+
* @param mixed $flags If set to STREAM_URL_STAT_QUIET, then no
102+
* error or exception occurs
103+
*
104+
* @return bool Returns false
105+
* @throws \RuntimeException if throw_errors is true
106+
*/
107+
private function triggerError($errors, $flags = null)
108+
{
109+
// This is triggered with things like file_exists()
110+
if ($flags & STREAM_URL_STAT_QUIET) {
111+
return $flags & STREAM_URL_STAT_LINK
112+
// This is triggered for things like is_link()
113+
? $this->formatUrlStat(false)
114+
: false;
115+
}
116+
// This is triggered when doing things like lstat() or stat()
117+
trigger_error(implode("\n", (array) $errors), E_USER_WARNING);
118+
return false;
119+
}
120+
121+
/**
122+
* Constructs a writable upload stream.
123+
*
124+
* Supported options:
125+
*
126+
* * chunkSizeBytes (integer): The number of bytes per chunk of this file.
127+
* Defaults to the chunkSizeBytes of the Bucket.
128+
*
129+
* * metadata (document): User data for the "metadata" field of the files
130+
* collection document.
131+
*
132+
* The following options are deprecated:
133+
*
134+
* * aliases (string[]): An array of aliases (i.e. filenames). Applications
135+
* wishing to store aliases should add an aliases field to the metadata
136+
* document instead.
137+
*
138+
* * contentType (string): A valid MIME type. Applications wishing to store
139+
* a contentType should add a contentType field to the metadata document
140+
* instead.
141+
*
142+
* @param Bucket $bucket Database name
143+
* @param string $filename Filename
144+
* @param array $options Upload options
145+
* @throws InvalidArgumentException
146+
*/
147+
public function __construct(Bucket $bucket, $filename, array $options = [])
148+
{
149+
$options += [
150+
'chunkSizeBytes' => $bucket->getChunkSizeBytes(),
151+
];
152+
153+
if (isset($options['chunkSizeBytes']) && ! is_integer($options['chunkSizeBytes'])) {
154+
throw new InvalidArgumentTypeException('"chunkSizeBytes" option', $options['chunkSizeBytes'], 'integer');
155+
}
156+
157+
if (isset($options['metadata']) && ! is_array($options['metadata']) && ! is_object($options['metadata'])) {
158+
throw new InvalidArgumentTypeException('"metadata" option', $options['metadata'], 'array or object');
159+
}
160+
161+
if (isset($options['aliases'])) {
162+
if ( ! is_array($options['aliases'])) {
163+
throw new InvalidArgumentTypeException('"aliases" option', $options['aliases'], 'array or object');
164+
}
165+
166+
$expectedIndex = 0;
167+
168+
foreach ($options['aliases'] as $i => $alias) {
169+
if ($i !== $expectedIndex) {
170+
throw new InvalidArgumentException(sprintf('"aliases" option is not a list (unexpected index: "%s")', $i));
171+
}
172+
173+
if ( ! is_string($alias)) {
174+
throw new InvalidArgumentTypeException(sprintf('$options["aliases"][%d]', $i), $alias, 'string');
175+
}
176+
177+
$expectedIndex += 1;
178+
}
179+
}
180+
181+
if (isset($options['contentType']) && ! is_string($options['contentType'])) {
182+
throw new InvalidArgumentTypeException('"contentType" option', $options['contentType'], 'string');
183+
}
184+
185+
$this->bucket = $bucket;
186+
$this->filename = (string) $filename;
187+
$this->options = $options;
188+
$this->hashContext = hash_init('md5');
189+
}
190+
191+
function stream_write($data)
192+
{
193+
hash_update($this->hashContext, $data);
194+
195+
fopen('php://memory', )
196+
}
197+
}

0 commit comments

Comments
(0)

AltStyle によって変換されたページ (->オリジナル) /