Skip to content

Commit 00ed432

Browse files
author
Charlotte Dunois
committed
Implement write and read file optimizations
1 parent 36f25ca commit 00ed432

File tree

11 files changed

+311
-19
lines changed

11 files changed

+311
-19
lines changed

src/AdapterInterface.php

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -184,6 +184,48 @@ public function write($fileDescriptor, $data, $length, $offset);
184184
*/
185185
public function close($fd);
186186

187+
/**
188+
* Reads the entire file.
189+
*
190+
* This is an optimization for adapters which can optimize
191+
* the open -> (seek ->) read -> close sequence into one call.
192+
*
193+
* @param string $path
194+
* @param int $offset
195+
* @param int|null $length
196+
* @return PromiseInterface
197+
*/
198+
public function getContents($path, $offset = 0, $length = null);
199+
200+
/**
201+
* Writes the given content to the specified file.
202+
* If the file exists, the file is truncated.
203+
* If the file does not exist, the file will be created.
204+
*
205+
* This is an optimization for adapters which can optimize
206+
* the open -> write -> close sequence into one call.
207+
*
208+
* @param string $path
209+
* @param string $content
210+
* @return PromiseInterface
211+
* @see AdapterInterface::appendContents()
212+
*/
213+
public function putContents($path, $content);
214+
215+
/**
216+
* Appends the given content to the specified file.
217+
* If the file does not exist, the file will be created.
218+
*
219+
* This is an optimization for adapters which can optimize
220+
* the open -> write -> close sequence into one call.
221+
*
222+
* @param string $path
223+
* @param string $content
224+
* @return PromiseInterface
225+
* @see AdapterInterface::putContents()
226+
*/
227+
public function appendContents($path, $content);
228+
187229
/**
188230
* Rename a node.
189231
*

src/ChildProcess/Adapter.php

Lines changed: 75 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44

55
use DateTime;
66
use Exception;
7+
use Throwable;
78
use React\EventLoop\LoopInterface;
89
use React\Filesystem\ObjectStream;
910
use React\Filesystem\ObjectStreamSink;
@@ -181,6 +182,10 @@ public function callFilesystem($function, $args, $errorResultCode = -1)
181182
return $this->pool->rpc(Factory::rpc($function, $args))->then(function (Payload $payload) {
182183
return \React\Promise\resolve($payload->getPayload());
183184
}, function ($payload) {
185+
if ($payload instanceof Throwable) {
186+
return \React\Promise\reject($payload);
187+
}
188+
184189
return \React\Promise\reject(new Exception($payload['error']['message']));
185190
});
186191
}
@@ -280,7 +285,76 @@ public function close($fd)
280285
return $fileDescriptor->softTerminate();
281286
});
282287
}
283-
288+
289+
/**
290+
* Reads the entire file.
291+
*
292+
* This is an optimization for adapters which can optimize
293+
* the open -> (seek ->) read -> close sequence into one call.
294+
*
295+
* @param string $path
296+
* @param int $offset
297+
* @param int|null $length
298+
* @return PromiseInterface
299+
*/
300+
public function getContents($path, $offset = 0, $length = null)
301+
{
302+
return $this->invoker->invokeCall('getContents', [
303+
'path' => $path,
304+
'offset' => $offset,
305+
'maxlen' => $length,
306+
])->then(function ($payload) {
307+
return \React\Promise\resolve(base64_decode($payload['chunk']));
308+
});
309+
}
310+
311+
/**
312+
* Writes the given content to the specified file.
313+
* If the file exists, the file is truncated.
314+
* If the file does not exist, the file will be created.
315+
*
316+
* This is an optimization for adapters which can optimize
317+
* the open -> write -> close sequence into one call.
318+
*
319+
* @param string $path
320+
* @param string $content
321+
* @return PromiseInterface
322+
* @see AdapterInterface::appendContents()
323+
*/
324+
public function putContents($path, $content)
325+
{
326+
return $this->invoker->invokeCall('putContents', [
327+
'path' => $path,
328+
'chunk' => base64_encode($content),
329+
'flags' => 0,
330+
])->then(function ($payload) {
331+
return \React\Promise\resolve($payload['written']);
332+
});
333+
}
334+
335+
/**
336+
* Appends the given content to the specified file.
337+
* If the file does not exist, the file will be created.
338+
*
339+
* This is an optimization for adapters which can optimize
340+
* the open -> write -> close sequence into one call.
341+
*
342+
* @param string $path
343+
* @param string $content
344+
* @return PromiseInterface
345+
* @see AdapterInterface::putContents()
346+
*/
347+
public function appendContents($path, $content)
348+
{
349+
return $this->invoker->invokeCall('putContents', [
350+
'path' => $path,
351+
'chunk' => base64_encode($content),
352+
'flags' => FILE_APPEND,
353+
])->then(function ($payload) {
354+
return \React\Promise\resolve($payload['written']);
355+
});
356+
}
357+
284358
/**
285359
* @param string $path
286360
* @return PromiseInterface

src/ChildProcess/Process.php

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,8 @@ public function __construct(Messenger $messenger)
3939
'read',
4040
'write',
4141
'close',
42+
'getContents',
43+
'putContents',
4244
'rename',
4345
'readlink',
4446
'symlink',
@@ -247,6 +249,34 @@ public function close(array $payload)
247249
]);
248250
}
249251

252+
/**
253+
* @param array $payload
254+
* @return PromiseInterface
255+
*/
256+
public function getContents(array $payload)
257+
{
258+
if ($payload['maxlen'] > 0) {
259+
$chunk = file_get_contents($payload['path'], false, null, $payload['offset'], $payload['maxlen']);
260+
} else {
261+
$chunk = file_get_contents($payload['path'], false, null, $payload['offset']);
262+
}
263+
264+
return \React\Promise\resolve([
265+
'chunk' => base64_encode($chunk),
266+
]);
267+
}
268+
269+
/**
270+
* @param array $payload
271+
* @return PromiseInterface
272+
*/
273+
public function putContents(array $payload)
274+
{
275+
return \React\Promise\resolve([
276+
'written' => file_put_contents($payload['path'], base64_decode($payload['chunk']), $payload['flags']),
277+
]);
278+
}
279+
250280
/**
251281
* @param array $payload
252282
* @return PromiseInterface

src/Eio/Adapter.php

Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -309,6 +309,75 @@ public function close($fd)
309309
});
310310
}
311311

312+
/**
313+
* Reads the entire file.
314+
*
315+
* This is an optimization for adapters which can optimize
316+
* the open -> (seek ->) read -> close sequence into one call.
317+
*
318+
* @param string $path
319+
* @param int $offset
320+
* @param int|null $length
321+
* @return PromiseInterface
322+
*/
323+
public function getContents($path, $offset = 0, $length = null)
324+
{
325+
if ($length === null) {
326+
return $this->stat($path)->then(function ($stat) use ($path, $offset) {
327+
return $this->getContents($path, $offset, $stat['size']);
328+
});
329+
}
330+
331+
return $this->open($path, 'r')->then(function ($fd) use ($offset, $length) {
332+
return $this->read($fd, $length, $offset)->always(function () use ($fd) {
333+
return $this->close($fd);
334+
});
335+
});
336+
}
337+
338+
/**
339+
* Writes the given content to the specified file.
340+
* If the file exists, the file is truncated.
341+
* If the file does not exist, the file will be created.
342+
*
343+
* This is an optimization for adapters which can optimize
344+
* the open -> write -> close sequence into one call.
345+
*
346+
* @param string $path
347+
* @param string $content
348+
* @return PromiseInterface
349+
* @see AdapterInterface::appendContents()
350+
*/
351+
public function putContents($path, $content)
352+
{
353+
return $this->open($path, 'cw')->then(function ($fd) use ($content) {
354+
return $this->write($fd, $content, strlen($content), 0)->always(function () use ($fd) {
355+
return $this->close($fd);
356+
});
357+
});
358+
}
359+
360+
/**
361+
* Appends the given content to the specified file.
362+
* If the file does not exist, the file will be created.
363+
*
364+
* This is an optimization for adapters which can optimize
365+
* the open -> write -> close sequence into one call.
366+
*
367+
* @param string $path
368+
* @param string $content
369+
* @return PromiseInterface
370+
* @see AdapterInterface::putContents()
371+
*/
372+
public function appendContents($path, $content)
373+
{
374+
return $this->open($path, 'cwa')->then(function ($fd) use ($content) {
375+
return $this->write($fd, $content, strlen($content), 0)->always(function () use ($fd) {
376+
return $this->close($fd);
377+
});
378+
});
379+
}
380+
312381
/**
313382
* {@inheritDoc}
314383
*/

tests/Adapters/AbstractAdaptersTest.php

Lines changed: 0 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -85,15 +85,6 @@ protected function getEioProvider(callable $loopFactory)
8585
];
8686
}
8787

88-
protected function getPthreadsProvider(callable $loopFactory)
89-
{
90-
$loop = $loopFactory();
91-
return [
92-
$loop,
93-
new Pthreads\Adapter($loop),
94-
];
95-
}
96-
9788
protected function getFacoryProvider(callable $loopFactory)
9889
{
9990
$loop = $loopFactory();

tests/Adapters/DirectoryTest.php

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,10 +3,7 @@
33
namespace React\Tests\Filesystem\Adapters;
44

55
use React\EventLoop\LoopInterface;
6-
use React\Filesystem\ChildProcess;
7-
use React\Filesystem\Eio;
86
use React\Filesystem\FilesystemInterface;
9-
use React\Filesystem\Pthreads;
107

118
/**
129
* @group adapters

tests/Adapters/FileTest.php

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,10 +3,7 @@
33
namespace React\Tests\Filesystem\Adapters;
44

55
use React\EventLoop\LoopInterface;
6-
use React\Filesystem\ChildProcess;
7-
use React\Filesystem\Eio;
86
use React\Filesystem\FilesystemInterface;
9-
use React\Filesystem\Pthreads;
107

118
/**
129
* @group adapters

tests/Adapters/InterfaceTest.php

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,9 +4,6 @@
44

55
use React\EventLoop\LoopInterface;
66
use React\Filesystem\AdapterInterface;
7-
use React\Filesystem\ChildProcess;
8-
use React\Filesystem\Eio;
9-
use React\Filesystem\Pthreads;
107

118
class InterfaceTest extends AbstractAdaptersTest
129
{

tests/ChildProcess/AdapterTest.php

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -416,4 +416,50 @@ public function testErrorFromPool()
416416
]);
417417
$this->await($adapter->touch('foo.bar'), $loop, 1);
418418
}
419+
420+
public function testGetContents()
421+
{
422+
$loop = \React\EventLoop\Factory::create();
423+
$adapter = new Adapter($loop);
424+
425+
$contents = $this->await($adapter->getContents(__FILE__), $loop);
426+
$this->assertSame(file_get_contents(__FILE__), $contents);
427+
}
428+
429+
public function testGetContentsMinMax()
430+
{
431+
$loop = \React\EventLoop\Factory::create();
432+
$adapter = new Adapter($loop);
433+
434+
$contents = $this->await($adapter->getContents(__FILE__, 5, 10), $loop);
435+
$this->assertSame(file_get_contents(__FILE__, false, null, 5, 10), $contents);
436+
}
437+
438+
public function testPutContents()
439+
{
440+
$loop = \React\EventLoop\Factory::create();
441+
$adapter = new Adapter($loop);
442+
443+
$tempFile = $this->tmpDir . uniqid('', true);
444+
$contents = sha1_file(__FILE__);
445+
446+
$this->await($adapter->putContents($tempFile, $contents), $loop);
447+
$this->assertSame($contents, file_get_contents($tempFile));
448+
}
449+
450+
public function testAppendContents()
451+
{
452+
$loop = \React\EventLoop\Factory::create();
453+
$adapter = new Adapter($loop);
454+
455+
$tempFile = $this->tmpDir . uniqid('', true);
456+
$contents = sha1_file(__FILE__);
457+
458+
file_put_contents($tempFile, $contents);
459+
$time = sha1(time());
460+
$contents .= $time;
461+
462+
$this->await($adapter->appendContents($tempFile, $time, FILE_APPEND), $loop);
463+
$this->assertSame($contents, file_get_contents($tempFile));
464+
}
419465
}

0 commit comments

Comments
 (0)