Skip to content

Commit ca5e06f

Browse files
authored
Merge pull request #112 from WyriHaximus-labs/0.2.x-reointroduce-eio-adapter
Reintroduce the ext-eio adapter
2 parents 57d8f3a + e469054 commit ca5e06f

File tree

13 files changed

+564
-10
lines changed

13 files changed

+564
-10
lines changed

.github/workflows/ci.yml

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,11 +22,16 @@ jobs:
2222
extensions:
2323
- ""
2424
- "uv-amphp/ext-uv@master"
25+
- "uv-amphp/ext-uv@master, eio"
26+
- "eio"
2527
steps:
2628
- uses: actions/checkout@v2
2729
- name: Install libuv
2830
if: matrix.os == 'ubuntu-latest'
2931
run: sudo apt-get install libuv1-dev
32+
- name: Install ext-eio
33+
if: matrix.os == 'ubuntu-latest' && (matrix.extensions == 'eio' || matrix.extensions == 'uv-amphp/ext-uv@master, eio')
34+
run: sudo pecl install eio || sudo pecl install eio-beta
3035
- uses: shivammathur/setup-php@v2
3136
with:
3237
php-version: ${{ matrix.php }}

README.md

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
* [Factory](#factory)
1717
* [create()](#create)
1818
* [Filesystem implementations](#filesystem-implementations)
19+
* [Eio](#eio)
1920
* [Uv](#uv)
2021
* [AdapterInterface](#adapterinterface)
2122
* [detect()](#detect)
@@ -110,6 +111,15 @@ manually instantiate one of the following classes.
110111
Note that you may have to install the required PHP extensions for the respective
111112
event loop implementation first or they will throw a `BadMethodCallException` on creation.
112113

114+
#### Eio
115+
116+
An `ext-eio` based filesystem.
117+
118+
This filesystem uses the [`eio` PECL extension](https://pecl.php.net/package/eio), that
119+
provides an interface to `libeio` library.
120+
121+
This filesystem is known to work with PHP 7+.
122+
113123
#### Uv
114124

115125
An `ext-uv` based filesystem.

composer.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@
3434
}
3535
},
3636
"suggest": {
37+
"ext-eio": "* for great I/O performance",
3738
"ext-uv": "* for better I/O performance"
3839
},
3940
"config": {

src/Eio/Adapter.php

Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
<?php
2+
3+
namespace React\Filesystem\Eio;
4+
5+
use React\EventLoop\Loop;
6+
use React\EventLoop\LoopInterface;
7+
use React\Filesystem\AdapterInterface;
8+
use React\Filesystem\ModeTypeDetector;
9+
use React\Filesystem\Node;
10+
use React\Filesystem\PollInterface;
11+
use React\Filesystem\Stat;
12+
use React\Promise\PromiseInterface;
13+
14+
final class Adapter implements AdapterInterface
15+
{
16+
use StatTrait;
17+
18+
private LoopInterface $loop;
19+
private PollInterface $poll;
20+
21+
public function __construct()
22+
{
23+
$this->loop = Loop::get();
24+
$this->poll = new Poll($this->loop);
25+
}
26+
27+
public function detect(string $path): PromiseInterface
28+
{
29+
return $this->internalStat($path)->then(function (?Stat $stat) use ($path) {
30+
if ($stat === null) {
31+
return new NotExist($this->poll, $this, $this->loop, dirname($path) . DIRECTORY_SEPARATOR, basename($path));
32+
}
33+
34+
switch (ModeTypeDetector::detect($stat->mode())) {
35+
case Node\FileInterface::class:
36+
return $this->file($stat->path());
37+
break;
38+
case Node\DirectoryInterface::class:
39+
return $this->directory($stat->path());
40+
break;
41+
default:
42+
return new Node\Unknown($stat->path(), $stat->path());
43+
break;
44+
}
45+
});
46+
}
47+
48+
public function directory(string $path): Node\DirectoryInterface
49+
{
50+
return new Directory($this->poll, $this, dirname($path) . DIRECTORY_SEPARATOR, basename($path));
51+
}
52+
53+
public function file(string $path): Node\FileInterface
54+
{
55+
return new File($this->poll, dirname($path) . DIRECTORY_SEPARATOR, basename($path));
56+
}
57+
58+
protected function activate(): void
59+
{
60+
$this->poll->activate();
61+
}
62+
63+
protected function deactivate(): void
64+
{
65+
$this->poll->deactivate();
66+
}
67+
}

src/Eio/Directory.php

Lines changed: 101 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,101 @@
1+
<?php
2+
3+
namespace React\Filesystem\Eio;
4+
5+
use React\Filesystem\AdapterInterface;
6+
use React\Filesystem\Node;
7+
use React\Filesystem\PollInterface;
8+
use React\Promise\Promise;
9+
use React\Promise\PromiseInterface;
10+
use function React\Promise\all;
11+
12+
final class Directory implements Node\DirectoryInterface
13+
{
14+
use StatTrait;
15+
16+
private PollInterface $poll;
17+
private AdapterInterface $filesystem;
18+
private string $path;
19+
private string $name;
20+
21+
public function __construct(PollInterface $poll, AdapterInterface $filesystem, string $path, string $name)
22+
{
23+
$this->poll = $poll;
24+
$this->filesystem = $filesystem;
25+
$this->path = $path;
26+
$this->name = $name;
27+
}
28+
29+
public function stat(): PromiseInterface
30+
{
31+
return $this->internalStat($this->path . $this->name);
32+
}
33+
34+
public function ls(): PromiseInterface
35+
{
36+
$this->activate();
37+
return new Promise(function (callable $resolve): void {
38+
\eio_readdir($this->path . $this->name . DIRECTORY_SEPARATOR, \EIO_READDIR_STAT_ORDER | \EIO_READDIR_DIRS_FIRST, \EIO_PRI_DEFAULT, function ($_, $contents) use ($resolve): void {
39+
$this->deactivate();
40+
$list = [];
41+
foreach ($contents['dents'] as $node) {
42+
$fullPath = $this->path . $this->name . DIRECTORY_SEPARATOR . $node['name'];
43+
switch ($node['type'] ?? null) {
44+
case EIO_DT_DIR:
45+
$list[] = $this->filesystem->directory($fullPath);
46+
break;
47+
case EIO_DT_REG :
48+
$list[] = $this->filesystem->file($fullPath);
49+
break;
50+
default:
51+
$list[] = $this->filesystem->detect($this->path . $this->name . DIRECTORY_SEPARATOR . $node['name']);
52+
break;
53+
}
54+
}
55+
56+
$resolve(all($list));
57+
});
58+
});
59+
}
60+
61+
public function unlink(): PromiseInterface
62+
{
63+
$this->activate();
64+
return new Promise(function (callable $resolve): void {
65+
\eio_readdir($this->path . $this->name . DIRECTORY_SEPARATOR, \EIO_READDIR_STAT_ORDER | \EIO_READDIR_DIRS_FIRST, \EIO_PRI_DEFAULT, function ($_, $contents) use ($resolve): void {
66+
$this->deactivate();
67+
if (count($contents['dents']) > 0) {
68+
$resolve(false);
69+
70+
return;
71+
}
72+
73+
$this->activate();
74+
\eio_rmdir($this->path . $this->name, function () use ($resolve): void {
75+
$this->deactivate();
76+
$resolve(true);
77+
});
78+
});
79+
});
80+
}
81+
82+
public function path(): string
83+
{
84+
return $this->path;
85+
}
86+
87+
public function name(): string
88+
{
89+
return $this->name;
90+
}
91+
92+
protected function activate(): void
93+
{
94+
$this->poll->activate();
95+
}
96+
97+
protected function deactivate(): void
98+
{
99+
$this->poll->deactivate();
100+
}
101+
}

src/Eio/EventStream.php

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
<?php
2+
3+
namespace React\Filesystem\Eio;
4+
5+
/**
6+
* Singleton to make sure we always only have one file descriptor for the ext-eio event stream.
7+
* Creating more than one will invalidate the previous ones and make anything still using those fail.
8+
*
9+
* @internal
10+
*/
11+
final class EventStream
12+
{
13+
private static $fd = null;
14+
15+
public static function get()
16+
{
17+
if (self::$fd !== null) {
18+
return self::$fd;
19+
}
20+
21+
self::$fd = eio_get_event_stream();
22+
23+
return self::$fd;
24+
}
25+
}

src/Eio/File.php

Lines changed: 149 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,149 @@
1+
<?php
2+
3+
namespace React\Filesystem\Eio;
4+
5+
use React\Filesystem\Node\FileInterface;
6+
use React\Filesystem\PollInterface;
7+
use React\Promise\Promise;
8+
use React\Promise\PromiseInterface;
9+
use function React\Promise\resolve;
10+
11+
final class File implements FileInterface
12+
{
13+
use StatTrait;
14+
15+
private PollInterface $poll;
16+
private string $path;
17+
private string $name;
18+
19+
public function __construct(PollInterface $poll, string $path, string $name)
20+
{
21+
$this->poll = $poll;
22+
$this->path = $path;
23+
$this->name = $name;
24+
}
25+
26+
public function stat(): PromiseInterface
27+
{
28+
return $this->internalStat($this->path . $this->name);
29+
}
30+
31+
public function getContents(int $offset = 0 , ?int $maxlen = null): PromiseInterface
32+
{
33+
$this->activate();
34+
return $this->openFile(
35+
$this->path . DIRECTORY_SEPARATOR . $this->name,
36+
\EIO_O_RDONLY,
37+
0,
38+
)->then(
39+
function ($fileDescriptor) use ($offset, $maxlen): PromiseInterface {
40+
if ($maxlen === null) {
41+
$sizePromise = $this->statFileDescriptor($fileDescriptor)->then(static function ($stat): int {
42+
return (int)$stat['size'];
43+
});
44+
} else {
45+
$sizePromise = resolve($maxlen);
46+
}
47+
return $sizePromise->then(function ($length) use ($fileDescriptor, $offset): PromiseInterface {
48+
return new Promise (function (callable $resolve) use ($fileDescriptor, $offset, $length): void {
49+
\eio_read($fileDescriptor, $length, $offset, \EIO_PRI_DEFAULT, function ($fileDescriptor, string $buffer) use ($resolve): void {
50+
$resolve($this->closeOpenFile($fileDescriptor)->then(function () use ($buffer): string {
51+
return $buffer;
52+
}));
53+
}, $fileDescriptor);
54+
});
55+
});
56+
}
57+
);
58+
}
59+
60+
public function putContents(string $contents, int $flags = 0)
61+
{
62+
$this->activate();
63+
return $this->openFile(
64+
$this->path . DIRECTORY_SEPARATOR . $this->name,
65+
(($flags & \FILE_APPEND) == \FILE_APPEND) ? \EIO_O_RDWR | \EIO_O_APPEND : \EIO_O_RDWR | \EIO_O_CREAT,
66+
0644
67+
)->then(
68+
function ($fileDescriptor) use ($contents, $flags): PromiseInterface {
69+
return new Promise (function (callable $resolve) use ($contents, $fileDescriptor): void {
70+
\eio_write($fileDescriptor, $contents, strlen($contents), 0, \EIO_PRI_DEFAULT, function ($fileDescriptor, int $bytesWritten) use ($resolve): void {
71+
$resolve($this->closeOpenFile($fileDescriptor)->then(function () use ($bytesWritten): int {
72+
return $bytesWritten;
73+
}));
74+
}, $fileDescriptor);
75+
});
76+
}
77+
);
78+
}
79+
80+
private function statFileDescriptor($fileDescriptor): PromiseInterface
81+
{
82+
return new Promise(function (callable $resolve, callable $reject) use ($fileDescriptor) {
83+
\eio_fstat($fileDescriptor, \EIO_PRI_DEFAULT, function ($_, $stat) use ($resolve): void {
84+
$resolve($stat);
85+
}, $fileDescriptor);
86+
});
87+
}
88+
89+
private function openFile(string $path, int $flags, int $mode): PromiseInterface
90+
{
91+
return new Promise(function (callable $resolve, callable $reject) use ($path, $flags, $mode): void {
92+
\eio_open(
93+
$path,
94+
$flags,
95+
$mode,
96+
\EIO_PRI_DEFAULT,
97+
function ($_, $fileDescriptor) use ($resolve): void {
98+
$resolve($fileDescriptor);
99+
}
100+
);
101+
});
102+
}
103+
104+
private function closeOpenFile($fileDescriptor): PromiseInterface
105+
{
106+
return new Promise(function (callable $resolve) use ($fileDescriptor) {
107+
try {
108+
\eio_close($fileDescriptor, \EIO_PRI_DEFAULT, function () use ($resolve): void {
109+
$this->deactivate();
110+
$resolve();
111+
});
112+
} catch (\Throwable $error) {
113+
$this->deactivate();
114+
throw $error;
115+
}
116+
});
117+
}
118+
119+
public function unlink(): PromiseInterface
120+
{
121+
$this->activate();
122+
return new Promise(function (callable $resolve): void {
123+
\eio_unlink($this->path . DIRECTORY_SEPARATOR . $this->name, \EIO_PRI_DEFAULT, function () use ($resolve): void {
124+
$this->deactivate();
125+
$resolve(true);
126+
});
127+
});
128+
}
129+
130+
public function path(): string
131+
{
132+
return $this->path;
133+
}
134+
135+
public function name(): string
136+
{
137+
return $this->name;
138+
}
139+
140+
protected function activate(): void
141+
{
142+
$this->poll->activate();
143+
}
144+
145+
protected function deactivate(): void
146+
{
147+
$this->poll->deactivate();
148+
}
149+
}

0 commit comments

Comments
 (0)