Skip to content

Commit 6528c72

Browse files
committed
initial commit mongof flow
1 parent de7151c commit 6528c72

File tree

6 files changed

+284
-1
lines changed

6 files changed

+284
-1
lines changed

src/Flow/File.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ class File
77
/**
88
* @var RequestInterface
99
*/
10-
private $request;
10+
protected $request;
1111

1212
/**
1313
* @var ConfigInterface

src/Flow/Mongo/MongoConfig.php

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
<?php
2+
3+
namespace Flow\Mongo;
4+
5+
use Flow\Config;
6+
7+
class MongoConfig extends Config implements MongoConfigInterface
8+
{
9+
private $gridFs;
10+
11+
/**
12+
* @param \MongoGridFS $gridFS storage of the upload (and chunks)
13+
*/
14+
function __construct(\MongoGridFS $gridFS)
15+
{
16+
parent::__construct();
17+
$this->gridFs = $gridFS;
18+
}
19+
20+
21+
/**
22+
* @return \MongoGridFS
23+
*/
24+
public function getGridFs()
25+
{
26+
return $this->gridFs;
27+
}
28+
}
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
<?php
2+
3+
namespace Flow\Mongo;
4+
5+
use Flow\ConfigInterface;
6+
7+
/**
8+
* @package Flow
9+
*/
10+
interface MongoConfigInterface extends ConfigInterface
11+
{
12+
13+
/**
14+
* @return \MongoGridFS
15+
*/
16+
public function getGridFs();
17+
18+
}

src/Flow/Mongo/MongoFile.php

Lines changed: 163 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,163 @@
1+
<?php
2+
3+
namespace Flow\Mongo;
4+
5+
use Flow\File;
6+
use Flow\Request;
7+
use Flow\RequestInterface;
8+
9+
10+
/**
11+
* Notes:
12+
*
13+
* - One should ensure indices on the gridfs collection on the property 'flowIdentifier'.
14+
* - Chunk preprocessor not supported (must not modify chunks size)!
15+
* - Must use 'forceChunkSize=true' on client side.
16+
*
17+
* @package Flow
18+
*/
19+
class MongoFile extends File
20+
{
21+
private $uploadGridFsFile;
22+
23+
/**
24+
* @var MongoConfigInterface
25+
*/
26+
private $config;
27+
28+
function __construct(MongoConfigInterface $config, RequestInterface $request = null)
29+
{
30+
if ($request === null) {
31+
$request = new Request();
32+
}
33+
parent::__construct($config, $request);
34+
$this->config = $config;
35+
}
36+
37+
/**
38+
* return array
39+
*/
40+
protected function getGridFsFile()
41+
{
42+
if (!$this->uploadGridFsFile) {
43+
$gridFsFileQuery = $this->getGridFsFileQuery();
44+
$changed = $gridFsFileQuery;
45+
$changed['flowUpdated'] = new \MongoDate();
46+
$this->uploadGridFsFile = $this->config->getGridFs()->findAndModify($gridFsFileQuery, $changed, null,
47+
['upsert' => true]);
48+
}
49+
50+
return $this->uploadGridFsFile;
51+
}
52+
53+
/**
54+
* @param $index int|string 1-based
55+
* @return bool
56+
*/
57+
public function chunkExists($index)
58+
{
59+
return $this->config->getGridFs()->chunks->find([
60+
'files_id' => $this->getGridFsFile()['_id'],
61+
'n' => (intval($index) - 1)
62+
])->limit(1)->hasNext();
63+
}
64+
65+
public function checkChunk()
66+
{
67+
return $this->chunkExists($this->request->getCurrentChunkNumber());
68+
}
69+
70+
/**
71+
* Save chunk
72+
*
73+
* @return bool
74+
*/
75+
public function saveChunk()
76+
{
77+
try {
78+
$file = $this->request->getFile();
79+
80+
$chunkQuery = [
81+
'files_id' => $this->getGridFsFile()['_id'],
82+
'n' => intval($this->request->getCurrentChunkNumber()) - 1,
83+
];
84+
$chunk = $chunkQuery;
85+
$data = file_get_contents($file['tmp_name']);
86+
$actualChunkSize = strlen($data);
87+
if ($actualChunkSize > $this->request->getDefaultChunkSize() ||
88+
($actualChunkSize < $this->request->getDefaultChunkSize() &&
89+
$this->request->getCurrentChunkNumber() != $this->request->getTotalChunks())
90+
) {
91+
throw new \Exception("Invalid upload! (size: {$actualChunkSize})");
92+
}
93+
$chunk['data'] = new \MongoBinData($data);
94+
$this->config->getGridFs()->chunks->findAndModify($chunkQuery, $chunk, [], ['upsert' => true]);
95+
unlink($file['tmp_name']);
96+
97+
return true;
98+
} catch (\Exception $e) {
99+
return false;
100+
}
101+
}
102+
103+
/**
104+
* @return bool
105+
*/
106+
public function validateFile()
107+
{
108+
$totalChunks = $this->request->getTotalChunks();
109+
110+
for ($i = 1; $i <= $totalChunks; $i++) {
111+
if (!$this->chunkExists($i)) {
112+
return false;
113+
}
114+
}
115+
116+
return true;
117+
}
118+
119+
120+
/**
121+
* Merge all chunks to single file
122+
* @param $metadata array additional metadata for final file
123+
* @return \MongoId|bool of saved file or false if file was already saved
124+
* @throws \Exception
125+
*/
126+
public function saveToGridFs($metadata = null)
127+
{
128+
$file = $this->getGridFsFile();
129+
$file['flowStatus'] = 'finished';
130+
$file['metadata'] = $metadata;
131+
$result = $this->config->getGridFs()->findAndModify($this->getGridFsFileQuery(), $file);
132+
// on second invocation no more file can be found, as the flowStatus changed:
133+
if (is_null($result)) {
134+
return false;
135+
} else {
136+
return $file['_id'];
137+
}
138+
}
139+
140+
public function save($destination)
141+
{
142+
throw new \Exception("Must not use 'save' on MongoFile - use 'saveToGridFs'!");
143+
}
144+
145+
public function deleteChunks()
146+
{
147+
// nothing to do, as chunks are directly part of the final file
148+
}
149+
150+
/**
151+
* @return array
152+
*/
153+
protected function getGridFsFileQuery()
154+
{
155+
return [
156+
'flowIdentifier' => $this->request->getIdentifier(),
157+
'flowStatus' => 'uploading',
158+
'filename' => $this->request->getFileName(),
159+
'chunkSize' => intval($this->request->getDefaultChunkSize()),
160+
'length' => intval($this->request->getTotalSize())
161+
];
162+
}
163+
}

src/Flow/Mongo/MongoUploader.php

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
<?php
2+
3+
namespace Flow\Mongo;
4+
5+
use Flow\FileOpenException;
6+
7+
class MongoUploader
8+
{
9+
/**
10+
* Delete chunks older than expiration time.
11+
*
12+
* @param \MongoGridFS $gridFs
13+
* @param int $expirationTime seconds
14+
*
15+
* @throws FileOpenException
16+
*/
17+
public static function pruneChunks($gridFs, $expirationTime = 172800)
18+
{
19+
$result = $gridFs->remove([
20+
'flowUpdated' => ['$lt' => new \MongoDate(time() - $expirationTime)],
21+
'flowStatus' => 'uploading'
22+
]);
23+
24+
if (!$result) {
25+
throw new FileOpenException("Could not remove chunks!");
26+
}
27+
}
28+
}

src/Flow/Mongo/README.md

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
Usage
2+
--------------
3+
4+
The usage is analogous to the 'normal' flow-php:
5+
6+
```php
7+
$config = new \Flow\Mongo\MongoConfig($yourGridFs);
8+
$file = new \Flow\Mongo\MongoFile($config);
9+
10+
if ($_SERVER['REQUEST_METHOD'] === 'GET') {
11+
if ($file->checkChunk()) {
12+
header("HTTP/1.1 200 Ok");
13+
} else {
14+
header("HTTP/1.1 204 No Content");
15+
return ;
16+
}
17+
} else {
18+
if ($file->validateChunk()) {
19+
$file->saveChunk();
20+
} else {
21+
// error, invalid chunk upload request, retry
22+
header("HTTP/1.1 400 Bad Request");
23+
return ;
24+
}
25+
}
26+
if ($file->validateFile()) {
27+
// File upload was completed
28+
$id = $file->saveToGridFs(['your metadata'=>'value']);
29+
if($id) {
30+
//do custom post processing here, $id is the MongoId of the gridfs file
31+
}
32+
} else {
33+
// This is not a final chunk, continue to upload
34+
}
35+
```
36+
37+
Delete unfinished files
38+
-----------------------
39+
40+
For this you should setup cron, which would check each chunk upload time.
41+
If chunk is uploaded long time ago, then chunk should be deleted.
42+
43+
Helper method for checking this:
44+
```php
45+
\Flow\Mongo\MongoUploader::pruneChunks($yourGridFs);
46+
```

0 commit comments

Comments
 (0)