mirror of
https://github.com/danog/file.git
synced 2024-11-29 20:09:10 +01:00
[WIP] initial code commit
This commit is contained in:
parent
40b2dbcf49
commit
39de6ea2cf
2
.gitignore
vendored
Normal file
2
.gitignore
vendored
Normal file
@ -0,0 +1,2 @@
|
||||
composer.lock
|
||||
vendor/
|
15
.php_cs
Normal file
15
.php_cs
Normal file
@ -0,0 +1,15 @@
|
||||
<?php
|
||||
|
||||
return Symfony\CS\Config\Config::create()
|
||||
->level(Symfony\CS\FixerInterface::NONE_LEVEL)
|
||||
->fixers([
|
||||
"psr2",
|
||||
"-braces",
|
||||
"-psr0",
|
||||
])
|
||||
->finder(
|
||||
Symfony\CS\Finder\DefaultFinder::create()
|
||||
->in(__DIR__ . "/lib")
|
||||
->in(__DIR__ . "/test")
|
||||
)
|
||||
;
|
33
CONTRIBUTING.md
Normal file
33
CONTRIBUTING.md
Normal file
@ -0,0 +1,33 @@
|
||||
## Submitting useful bug reports
|
||||
|
||||
Please search existing issues first to make sure this is not a duplicate.
|
||||
Every issue report has a cost for the developers required to field it; be
|
||||
respectful of others' time and ensure your report isn't spurious prior to
|
||||
submission. Additionally, please do us all a favor by adhering to the
|
||||
principles of sound bug reporting laid out by Simon Tatham here:
|
||||
|
||||
http://www.chiark.greenend.org.uk/~sgtatham/bugs.html
|
||||
|
||||
## Development ideology
|
||||
|
||||
Truths which we believe to be self-evident:
|
||||
|
||||
- **It's an asynchronous world.** Be wary of anything that undermines
|
||||
async principles.
|
||||
|
||||
- **The answer is not more options.** If you feel compelled to expose
|
||||
new preferences to the user it's very possible you've made a wrong
|
||||
turn somewhere.
|
||||
|
||||
- **There are no power users.** The idea that some users "understand"
|
||||
concepts better than others has proven to be, for the most part, false.
|
||||
If anything, "power users" are more dangerous than the rest, and we
|
||||
should avoid exposing dangerous functionality to them.
|
||||
|
||||
## Code style
|
||||
|
||||
The amphp project adheres to the PSR-2 style guide with the exception that
|
||||
opening braces for classes and methods must appear on the same line as
|
||||
the declaration:
|
||||
|
||||
https://github.com/php-fig/fig-standards/blob/master/accepted/PSR-2-coding-style-guide.md
|
25
README.md
Normal file
25
README.md
Normal file
@ -0,0 +1,25 @@
|
||||
# amp/fs [![Build Status](https://travis-ci.org/amphp/fs.svg?branch=master)](https://travis-ci.org/amphp/fs)
|
||||
|
||||
`amp/fs` is a non-blocking filesystem manipulation library for use with the
|
||||
[amp concurrency framework](https://github.com/amphp/amp).
|
||||
|
||||
**Dependencies**
|
||||
|
||||
- PHP 5.5+
|
||||
- [php-uv](https://github.com/chobie/php-uv) (optional)
|
||||
- [eio](https://pecl.php.net/package/eio) (optional)
|
||||
|
||||
`amp/fs` works out of the box without any PHP extensions. However, it does so
|
||||
in a blocking manner. This capability only exists to simplify development across
|
||||
environments where extensions may not be present. Using this library in
|
||||
production without either the php-uv or eio extension is **NOT** reccommended.
|
||||
|
||||
**Current Version**
|
||||
|
||||
- v0.1.0
|
||||
|
||||
**Installation**
|
||||
|
||||
```bash
|
||||
$ composer require amphp/fs: ~0.1
|
||||
```
|
45
composer.json
Normal file
45
composer.json
Normal file
@ -0,0 +1,45 @@
|
||||
{
|
||||
"name": "amphp/fs",
|
||||
"homepage": "https://github.com/amphp/fs",
|
||||
"description": "Non-blocking filesystem tools for amp applications",
|
||||
"keywords": [
|
||||
"filesystem",
|
||||
"static",
|
||||
"async",
|
||||
"non-blocking",
|
||||
"amp",
|
||||
"amphp"
|
||||
],
|
||||
"license": "MIT",
|
||||
"authors": [
|
||||
{
|
||||
"name": "Daniel Lowrey",
|
||||
"email": "rdlowrey@php.net",
|
||||
"role": "Creator / Lead Developer"
|
||||
}
|
||||
],
|
||||
"require": {
|
||||
"php": ">=7.0.0-dev",
|
||||
"amphp/amp": "dev-master"
|
||||
},
|
||||
"require-dev": {
|
||||
"phpunit/phpunit": "~4.4.0",
|
||||
"fabpot/php-cs-fixer": "~1.9"
|
||||
},
|
||||
"autoload": {
|
||||
"psr-4": {
|
||||
"Amp\\Fs\\": "lib"
|
||||
},
|
||||
"files": ["lib/functions.php"]
|
||||
},
|
||||
"autoload-dev": {
|
||||
"psr-4": {
|
||||
"Amp\\Fs\\Test\\": "test/"
|
||||
}
|
||||
},
|
||||
"extra": {
|
||||
"branch-alias": {
|
||||
"dev-master": "0.1.0-dev"
|
||||
}
|
||||
}
|
||||
}
|
90
lib/BlockingDescriptor.php
Normal file
90
lib/BlockingDescriptor.php
Normal file
@ -0,0 +1,90 @@
|
||||
<?php
|
||||
|
||||
namespace Amp\Fs;
|
||||
|
||||
use Amp\{ Promise, Success, Failure };
|
||||
|
||||
class BlockingDescriptor implements Descriptor {
|
||||
private $fh;
|
||||
|
||||
/**
|
||||
* @param resource $fh An open uv filesystem descriptor
|
||||
*/
|
||||
public function __construct($fh, string $path) {
|
||||
$this->fh = $fh;
|
||||
$this->path = $path;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function read(int $offset, int $len): Promise {
|
||||
\fseek($this->fh, $offset);
|
||||
$data = \fread($this->fh, $len);
|
||||
|
||||
if ($data !== false) {
|
||||
return new Success($data);
|
||||
} else {
|
||||
return new Failure(new \RuntimeException(
|
||||
"Failed reading from file handle"
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function write(int $offset, string $data): Promise {
|
||||
\fseek($this->fh, $offset);
|
||||
$len = \fwrite($this->fh, $data);
|
||||
|
||||
if ($len !== false) {
|
||||
return new Success($data);
|
||||
} else {
|
||||
return new Failure(new \RuntimeException(
|
||||
"Failed writing to file handle"
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function truncate(int $length = 0): Promise {
|
||||
if (ftruncate($this->fh, $length)) {
|
||||
return new Success;
|
||||
} else {
|
||||
return new Failure(new \RuntimeException(
|
||||
"Failed truncating file handle"
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function stat(): Promise {
|
||||
if ($stat = fstat($this->fh)) {
|
||||
$stat["isfile"] = (bool) is_file($this->path);
|
||||
$stat["isdir"] = empty($stat["isfile"]);
|
||||
return new Success($stat);
|
||||
} else {
|
||||
return new Failure(new \RuntimeException(
|
||||
"File handle stat failed"
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function close(): Promise {
|
||||
if (\fclose($this->fh)) {
|
||||
return new Success;
|
||||
} else {
|
||||
return new Failure(new \RuntimeException(
|
||||
"Failed closing file handle"
|
||||
));
|
||||
}
|
||||
}
|
||||
}
|
169
lib/BlockingFilesystem.php
Normal file
169
lib/BlockingFilesystem.php
Normal file
@ -0,0 +1,169 @@
|
||||
<?php
|
||||
|
||||
namespace Amp\Fs;
|
||||
|
||||
use Amp\{ Reactor, function reactor, Promise, Success, Failure };
|
||||
|
||||
class BlockingFilesystem implements Filesystem {
|
||||
private $reactor;
|
||||
|
||||
public function __construct(Reactor $reactor = null) {
|
||||
$this->reactor = $reactor ?: reactor();
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function open(string $path, int $mode = self::READ): Promise {
|
||||
$openMode = 0;
|
||||
|
||||
if ($mode & self::READ && $mode & self::WRITE) {
|
||||
$openMode = ($mode & self::CREATE) ? "c+" : "r+";
|
||||
} elseif ($mode & self::READ) {
|
||||
$openMode = "r";
|
||||
} elseif ($mode & self::WRITE) {
|
||||
$openMode = "c";
|
||||
} else {
|
||||
return new Failure(new \InvalidArgumentException(
|
||||
"Invalid file open mode: Filesystem::READ or Filesystem::WRITE or both required"
|
||||
));
|
||||
}
|
||||
|
||||
if ($fh = @fopen($path, $openMode)) {
|
||||
$descriptor = new BlockingDescriptor($fh, $path);
|
||||
return new Success($descriptor);
|
||||
} else {
|
||||
return new Failure(new \RuntimeException(
|
||||
"Failed opening file handle"
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function stat(string $path): Promise {
|
||||
if ($stat = @stat($path)) {
|
||||
$stat["isfile"] = (bool) is_file($path);
|
||||
$stat["isdir"] = empty($stat["isfile"]);
|
||||
clearstatcache(true, $path);
|
||||
} else {
|
||||
$stat = null;
|
||||
}
|
||||
|
||||
return new Success($stat);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function lstat(string $path): Promise {
|
||||
if ($stat = @lstat($path)) {
|
||||
$stat["isfile"] = (bool) is_file($path);
|
||||
$stat["isdir"] = empty($stat["isfile"]);
|
||||
clearstatcache(true, $path);
|
||||
} else {
|
||||
$stat = null;
|
||||
}
|
||||
|
||||
return new Success($stat);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function symlink(string $target, string $link): Promise {
|
||||
return new Success((bool) symlink($target, $link));
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function rename(string $from, string $to): Promise {
|
||||
return new Success((bool) rename($from, $to));
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function unlink(string $path): Promise {
|
||||
return new Success((bool) unlink($path));
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function mkdir(string $path, int $mode = 0644): Promise {
|
||||
return new Success((bool) mkdir($path, $mode));
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function rmdir(string $path): Promise {
|
||||
return new Success((bool) rmdir($path));
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function scandir(string $path): Promise {
|
||||
if ($arr = scandir($path)) {
|
||||
$arr = array_values(array_filter($arr, function($el) {
|
||||
return !($el === "." || $el === "..");
|
||||
}));
|
||||
clearstatcache(true, $path);
|
||||
return new Success($arr);
|
||||
} else {
|
||||
return new Failure(new \RuntimeException(
|
||||
"Failed reading contents from {$path}"
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function chmod(string $path, int $mode): Promise {
|
||||
return new Success((bool) chmod($path, $mode));
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function chown(string $path, int $uid, int $gid): Promise {
|
||||
if (!@chown($path, $uid)) {
|
||||
return new Failure(new \RuntimeException(
|
||||
error_get_last()["message"]
|
||||
));
|
||||
} elseif (!@chgrp($path, $gid)) {
|
||||
return new Failure(new \RuntimeException(
|
||||
error_get_last()["message"]
|
||||
));
|
||||
} else {
|
||||
return new Success;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function get(string $path): Promise {
|
||||
$result = @file_get_contents($path);
|
||||
return ($result === false)
|
||||
? new Failure(new \RuntimeException(error_get_last()["message"]))
|
||||
: new Success($result)
|
||||
;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function put(string $path, string $contents): Promise {
|
||||
$result = @file_put_contents($path, $contents);
|
||||
return ($result === false)
|
||||
? new Failure(new \RuntimeException(error_get_last()["message"]))
|
||||
: new Success($result)
|
||||
;
|
||||
}
|
||||
}
|
49
lib/Descriptor.php
Normal file
49
lib/Descriptor.php
Normal file
@ -0,0 +1,49 @@
|
||||
<?php
|
||||
|
||||
namespace Amp\Fs;
|
||||
|
||||
use Amp\Promise;
|
||||
|
||||
interface Descriptor {
|
||||
/**
|
||||
* Read $len bytes from the open file handle starting at $offset
|
||||
*
|
||||
* @param int $offset
|
||||
* @param int $len
|
||||
* @return \Amp\Promise
|
||||
*/
|
||||
public function read(int $offset, int $len): Promise;
|
||||
|
||||
/**
|
||||
* Write $data to the open file handle starting at $offset
|
||||
*
|
||||
* @param int $offset
|
||||
* @param string $data
|
||||
* @return \Amp\Promise
|
||||
*/
|
||||
public function write(int $offset, string $data): Promise;
|
||||
|
||||
/**
|
||||
* Truncate the file to the specified $length
|
||||
*
|
||||
* Note: The descriptor must be opened for writing
|
||||
*
|
||||
* @param int $length
|
||||
* @return \Amp\Promise
|
||||
*/
|
||||
public function truncate(int $length = 0): Promise;
|
||||
|
||||
/**
|
||||
* Retrieve the filesystem stat array for the current descriptor
|
||||
*
|
||||
* @return \Amp\Promise
|
||||
*/
|
||||
public function stat(): Promise;
|
||||
|
||||
/**
|
||||
* Close the file handle
|
||||
*
|
||||
* @return \Amp\Promise
|
||||
*/
|
||||
public function close(): Promise;
|
||||
}
|
146
lib/Filesystem.php
Normal file
146
lib/Filesystem.php
Normal file
@ -0,0 +1,146 @@
|
||||
<?php
|
||||
|
||||
namespace Amp\Fs;
|
||||
|
||||
use Amp\Promise;
|
||||
|
||||
interface Filesystem {
|
||||
const READ = 0b001;
|
||||
const WRITE = 0b010;
|
||||
const CREATE = 0b100;
|
||||
|
||||
/**
|
||||
* Open a file handle for reading and/or writing
|
||||
*
|
||||
* At least READ or WRITE is required in the mode bitmask. If the file does not exist the
|
||||
* CREATE flag is necessary in READ mode or the operation will fail. When WRITE mode is
|
||||
* specified in the bitmask the file will always be created if it does not already exist.
|
||||
*
|
||||
* Example:
|
||||
*
|
||||
* <?php
|
||||
* use function Amp\Fs\fs();
|
||||
* use Amp\Fs\{ Filesystem, Descriptor };
|
||||
*
|
||||
* function() {
|
||||
* $fs = fs();
|
||||
* $mode = Filesystem::READ | Filesystem::WRITE;
|
||||
* $fh = yield $fs->open("/path/to/file", $mode);
|
||||
* assert($fh instanceof Descriptor);
|
||||
*
|
||||
*
|
||||
* NOTE: This operation is only valid for files (not directories).
|
||||
*
|
||||
* @param string $path The filesystem path to open
|
||||
* @param int $mode A flag bitmask: [Filesystem::READ | Filesystem::WRITE | Filesystem::CREATE]
|
||||
* @return \Amp\Promise
|
||||
*/
|
||||
public function open(string $path, int $mode = self::READ): Promise;
|
||||
|
||||
/**
|
||||
* Execute a file stat operation
|
||||
*
|
||||
* If the requested path does not exist the resulting Promise will resolve to NULL.
|
||||
*
|
||||
* @param string $path The file system path to stat
|
||||
* @return \Amp\Promise A promise resolving to an associative array upon successful resolution
|
||||
*/
|
||||
public function stat(string $path): Promise;
|
||||
|
||||
/**
|
||||
* Same as stat() except if the path is a link then the link's data is returned
|
||||
*
|
||||
* @param string $path The file system path to stat
|
||||
* @return \Amp\Promise A promise resolving to an associative array upon successful resolution
|
||||
*/
|
||||
public function lstat(string $path): Promise;
|
||||
|
||||
/**
|
||||
* Create a symlink $link pointing to the file/directory located at $target
|
||||
*
|
||||
* @param string $target
|
||||
* @param string $link
|
||||
* @return \Amp\Promise
|
||||
*/
|
||||
public function symlink(string $target, string $link): Promise;
|
||||
|
||||
/**
|
||||
* Rename a file or directory
|
||||
*
|
||||
* @param string $from
|
||||
* @param string $to
|
||||
* @return \Amp\Promise
|
||||
*/
|
||||
public function rename(string $from, string $to): Promise;
|
||||
|
||||
/**
|
||||
* Delete a file
|
||||
*
|
||||
* @param string $path
|
||||
* @return \Amp\Promise
|
||||
*/
|
||||
public function unlink(string $path): Promise;
|
||||
|
||||
/**
|
||||
* Create a director
|
||||
*
|
||||
* @param string $path
|
||||
* @param int $mode
|
||||
* @return \Amp\Promise
|
||||
*/
|
||||
public function mkdir(string $path, int $mode = 0644): Promise;
|
||||
|
||||
/**
|
||||
* Delete a directory
|
||||
*
|
||||
* @param string $path
|
||||
* @return \Amp\Promise
|
||||
*/
|
||||
public function rmdir(string $path): Promise;
|
||||
|
||||
/**
|
||||
* Retrieve an array of files and directories inside the specified path
|
||||
*
|
||||
* Dot entries are not included in the resulting array (i.e. "." and "..").
|
||||
*
|
||||
* @param string $path
|
||||
* @return \Amp\Promise
|
||||
*/
|
||||
public function scandir(string $path): Promise;
|
||||
|
||||
/**
|
||||
* chmod a file or directory
|
||||
*
|
||||
* @param string $path
|
||||
* @param int $mode
|
||||
* @return \Amp\Promise
|
||||
*/
|
||||
public function chmod(string $path, int $mode): Promise;
|
||||
|
||||
/**
|
||||
* chown a file or directory
|
||||
*
|
||||
* @param string $path
|
||||
* @param int $uid
|
||||
* @param int $gid
|
||||
* @return \Amp\Promise
|
||||
*/
|
||||
public function chown(string $path, int $uid, int $gid): Promise;
|
||||
|
||||
/**
|
||||
* Buffer the specified file's contents
|
||||
*
|
||||
* @param string $path The file path from which to buffer contents
|
||||
* @return \Amp\Promise A promise resolving to a string upon successful resolution
|
||||
*/
|
||||
public function get(string $path): Promise;
|
||||
|
||||
/**
|
||||
* Write the contents string to the specified path.
|
||||
*
|
||||
* @param string $path The file path to which to $contents should be written
|
||||
* @param string $contents The data to write to the specified $path
|
||||
* @return \Amp\Promise A promise resolving to the integer length written upon success
|
||||
*/
|
||||
public function put(string $path, string $contents): Promise;
|
||||
}
|
130
lib/UvDescriptor.php
Normal file
130
lib/UvDescriptor.php
Normal file
@ -0,0 +1,130 @@
|
||||
<?php
|
||||
|
||||
namespace Amp\Fs;
|
||||
|
||||
use Amp\{ Promise, Deferred, UvReactor };
|
||||
|
||||
class UvDescriptor implements Descriptor {
|
||||
private $reactor;
|
||||
private $fh;
|
||||
private $loop;
|
||||
private $isCloseInitialized = false;
|
||||
|
||||
/**
|
||||
* @param \Amp\UvReactor $reactor
|
||||
* @param resource $fh An open uv filesystem descriptor
|
||||
*/
|
||||
public function __construct(UvReactor $reactor, $fh) {
|
||||
$this->reactor = $reactor;
|
||||
$this->fh = $fh;
|
||||
$this->loop = $reactor->getUnderlyingLoop();
|
||||
}
|
||||
|
||||
public function __destruct() {
|
||||
if (empty($this->isCloseInitialized)) {
|
||||
$this->close();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function read(int $offset, int $len): Promise {
|
||||
$this->reactor->addRef();
|
||||
$promisor = new Deferred;
|
||||
uv_fs_read($this->loop, $this->fh, $offset, $len, function($fh, $result, $buf) use ($promisor) {
|
||||
$this->reactor->delRef();
|
||||
if ($result < 0) {
|
||||
$promisor->fail(new \RuntimeException(
|
||||
"Failed reading from file handle: " . \uv_strerror($result)
|
||||
));
|
||||
} else {
|
||||
$promisor->succeed($buf);
|
||||
}
|
||||
});
|
||||
|
||||
return $promisor->promise();
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function write(int $offset, string $data): Promise {
|
||||
$this->reactor->addRef();
|
||||
$promisor = new Deferred;
|
||||
uv_fs_write($this->loop, $this->fh, $data, $offset, function($fh, $result) use ($promisor) {
|
||||
$this->reactor->delRef();
|
||||
if ($result < 0) {
|
||||
$promisor->fail(new \RuntimeException(
|
||||
"Failed writing to file handle: " . \uv_strerror($result)
|
||||
));
|
||||
} else {
|
||||
$promisor->succeed($result);
|
||||
}
|
||||
});
|
||||
|
||||
return $promisor->promise();
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function truncate(int $length = 0): Promise {
|
||||
$this->reactor->addRef();
|
||||
$promisor = new Deferred;
|
||||
uv_fs_ftruncate($this->loop, $this->fh, $length, function($fh) use ($promisor) {
|
||||
$this->reactor->delRef();
|
||||
if (empty($fh)) {
|
||||
$promisor->fail(new \RuntimeException(
|
||||
"Failed truncating file handle"
|
||||
));
|
||||
} else {
|
||||
$promisor->succeed();
|
||||
}
|
||||
});
|
||||
|
||||
return $promisor->promise();
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function stat(): Promise {
|
||||
// @TODO Pull result from stat cache if it exists
|
||||
$this->reactor->addRef();
|
||||
$promisor = new Deferred;
|
||||
\uv_fs_fstat($this->loop, $this->fh, function($fh, $stat) use ($promisor) {
|
||||
if ($fh) {
|
||||
$stat["isdir"] = (bool) ($stat["mode"] & \UV::S_IFDIR);
|
||||
$stat["isfile"] = empty($stat["isdir"]);
|
||||
} else {
|
||||
$stat = null;
|
||||
}
|
||||
$this->reactor->delRef();
|
||||
$promisor->succeed($stat);
|
||||
});
|
||||
|
||||
return $promisor->promise();
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function close(): Promise {
|
||||
$this->isCloseInitialized = true;
|
||||
$this->reactor->addRef();
|
||||
$promisor = new Deferred;
|
||||
uv_fs_close($this->loop, $this->fh, function($fh) use ($promisor) {
|
||||
$this->reactor->delRef();
|
||||
if (empty($fh)) {
|
||||
$promisor->fail(new \RuntimeException(
|
||||
"Failed closing file handle"
|
||||
));
|
||||
} else {
|
||||
$promisor->succeed();
|
||||
}
|
||||
});
|
||||
|
||||
return $promisor->promise();
|
||||
}
|
||||
}
|
340
lib/UvFilesystem.php
Normal file
340
lib/UvFilesystem.php
Normal file
@ -0,0 +1,340 @@
|
||||
<?php
|
||||
|
||||
namespace Amp\Fs;
|
||||
|
||||
use Amp\{ UvReactor, Promise, Failure, Deferred };
|
||||
use function Amp\{ resolve, reactor };
|
||||
|
||||
class UvFilesystem implements Filesystem {
|
||||
private $reactor;
|
||||
private $loop;
|
||||
|
||||
/**
|
||||
* @param \Amp\UvReactor $reactor
|
||||
*/
|
||||
public function __construct(UvReactor $reactor) {
|
||||
$this->reactor = $reactor;
|
||||
$this->loop = $this->reactor->getUnderlyingLoop();
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function open(string $path, int $mode = self::READ): Promise {
|
||||
$openFlags = 0;
|
||||
$fileChmod = 0;
|
||||
|
||||
if ($mode & self::READ && $mode & self::WRITE) {
|
||||
$openFlags = \UV::O_RDWR;
|
||||
$mode |= self::CREATE;
|
||||
} elseif ($mode & self::READ) {
|
||||
$openFlags = \UV::O_RDONLY;
|
||||
} elseif ($mode & self::WRITE) {
|
||||
$openFlags = \UV::O_WRONLY;
|
||||
$mode |= self::CREATE;
|
||||
} else {
|
||||
return new Failure(new \InvalidArgumentException(
|
||||
"Invalid file open mode: Filesystem::READ or Filesystem::WRITE or both required"
|
||||
));
|
||||
}
|
||||
|
||||
if ($mode & self::CREATE) {
|
||||
$openFlags |= \UV::O_CREAT;
|
||||
$fileChmod = 0644;
|
||||
}
|
||||
|
||||
$this->reactor->addRef();
|
||||
$promisor = new Deferred;
|
||||
\uv_fs_open($this->loop, $path, $openFlags, $fileChmod, function($fh) use ($promisor) {
|
||||
$this->reactor->delRef();
|
||||
if ($fh) {
|
||||
$descriptor = new UvDescriptor($this->reactor, $fh);
|
||||
$promisor->succeed($descriptor);
|
||||
} else {
|
||||
$promisor->fail(new \RuntimeException(
|
||||
"Failed opening file handle"
|
||||
));
|
||||
}
|
||||
});
|
||||
|
||||
return $promisor->promise();
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function stat(string $path): Promise {
|
||||
$this->reactor->addRef();
|
||||
$promisor = new Deferred;
|
||||
\uv_fs_stat($this->loop, $path, function($fh, $stat) use ($promisor) {
|
||||
if ($fh) {
|
||||
$stat["isdir"] = (bool) ($stat["mode"] & \UV::S_IFDIR);
|
||||
$stat["isfile"] = empty($stat["isdir"]);
|
||||
} else {
|
||||
$stat = null;
|
||||
}
|
||||
$this->reactor->delRef();
|
||||
$promisor->succeed($stat);
|
||||
});
|
||||
|
||||
return $promisor->promise();
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function lstat(string $path): Promise {
|
||||
$this->reactor->addRef();
|
||||
$promisor = new Deferred;
|
||||
\uv_fs_lstat($this->loop, $path, function($fh, $stat) use ($promisor) {
|
||||
if ($fh) {
|
||||
$stat["isdir"] = (bool) ($stat["mode"] & \UV::S_IFDIR);
|
||||
$stat["isfile"] = empty($stat["isdir"]);
|
||||
} else {
|
||||
$stat = null;
|
||||
}
|
||||
$this->reactor->delRef();
|
||||
$promisor->succeed($stat);
|
||||
});
|
||||
|
||||
return $promisor->promise();
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function symlink(string $target, string $link): Promise {
|
||||
$this->reactor->addRef();
|
||||
$promisor = new Deferred;
|
||||
uv_fs_symlink($this->loop, $target, $link, \UV::S_IRWXU | \UV::S_IRUSR, function($fh) use ($promisor) {
|
||||
$this->reactor->delRef();
|
||||
$promisor->succeed((bool)$fh);
|
||||
});
|
||||
|
||||
return $promisor->promise();
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function rename(string $from, string $to): Promise {
|
||||
$this->reactor->addRef();
|
||||
$promisor = new Deferred;
|
||||
\uv_fs_rename($this->loop, $from, $to, function($fh) use ($promisor) {
|
||||
$this->reactor->delRef();
|
||||
$promisor->succeed((bool)$fh);
|
||||
});
|
||||
|
||||
return $promisor->promise();
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function unlink(string $path): Promise {
|
||||
$this->reactor->addRef();
|
||||
$promisor = new Deferred;
|
||||
\uv_fs_unlink($this->loop, $path, function($fh) use ($promisor) {
|
||||
$this->reactor->delRef();
|
||||
$promisor->succeed((bool)$fh);
|
||||
});
|
||||
|
||||
return $promisor->promise();
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function mkdir(string $path, int $mode = 0644): Promise {
|
||||
$this->reactor->addRef();
|
||||
$promisor = new Deferred;
|
||||
\uv_fs_mkdir($this->loop, $path, $mode, function($fh) use ($promisor) {
|
||||
$this->reactor->delRef();
|
||||
$promisor->succeed((bool)$fh);
|
||||
});
|
||||
|
||||
return $promisor->promise();
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function rmdir(string $path): Promise {
|
||||
$this->reactor->addRef();
|
||||
$promisor = new Deferred;
|
||||
\uv_fs_rmdir($this->loop, $path, function($fh) use ($promisor) {
|
||||
$this->reactor->delRef();
|
||||
$promisor->succeed((bool)$fh);
|
||||
});
|
||||
|
||||
return $promisor->promise();
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function scandir(string $path): Promise {
|
||||
$this->reactor->addRef();
|
||||
$promisor = new Deferred;
|
||||
uv_fs_readdir($this->loop, $path, 0, function($fh, $data) use ($promisor, $path) {
|
||||
$this->reactor->delRef();
|
||||
if (empty($fh)) {
|
||||
$promisor->fail(new \RuntimeException(
|
||||
"Failed reading contents from {$path}"
|
||||
));
|
||||
} else {
|
||||
$promisor->succeed($data);
|
||||
}
|
||||
});
|
||||
|
||||
return $promisor->promise();
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function chmod(string $path, int $mode): Promise {
|
||||
$this->reactor->addRef();
|
||||
$promisor = new Deferred;
|
||||
\uv_fs_chmod($this->loop, $path, $mode, function($fh) use ($promisor) {
|
||||
$this->reactor->delRef();
|
||||
$promisor->succeed((bool)$fh);
|
||||
});
|
||||
|
||||
return $promisor->promise();
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function chown(string $path, int $uid, int $gid): Promise {
|
||||
// @TODO Return a failure in windows environments
|
||||
$this->reactor->addRef();
|
||||
$promisor = new Deferred;
|
||||
\uv_fs_chown($this->loop, $path, $uid, $gid, function($fh) use ($promisor) {
|
||||
$this->reactor->delRef();
|
||||
$promisor->succeed((bool)$fh);
|
||||
});
|
||||
|
||||
return $promisor->promise();
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function get(string $path): Promise {
|
||||
return resolve($this->doGet($path), $this->reactor);
|
||||
}
|
||||
|
||||
private function doGet(string $path): \Generator {
|
||||
$this->reactor->addRef();
|
||||
if (!$fh = yield $this->doFsOpen($path, $flags = \UV::O_RDONLY, $mode = 0)) {
|
||||
$this->reactor->delRef();
|
||||
throw new \RuntimeException(
|
||||
"Failed opening file handle: {$path}"
|
||||
);
|
||||
}
|
||||
|
||||
$promisor = new Deferred;
|
||||
$stat = yield $this->doFsStat($fh);
|
||||
if (empty($stat)) {
|
||||
$this->reactor->delRef();
|
||||
$promisor->fail(new \RuntimeException(
|
||||
"stat operation failed on open file handle"
|
||||
));
|
||||
} elseif (!$stat["isfile"]) {
|
||||
\uv_fs_close($this->loop, $fh, function() use ($promisor) {
|
||||
$this->reactor->delRef();
|
||||
$promisor->fail(new \RuntimeException(
|
||||
"cannot buffer contents: path is not a file"
|
||||
));
|
||||
});
|
||||
} else {
|
||||
$buffer = yield $this->doFsRead($fh, $offset = 0, $stat["size"]);
|
||||
if ($buffer === false ) {
|
||||
\uv_fs_close($this->loop, $fh, function() use ($promisor) {
|
||||
$this->reactor->delRef();
|
||||
$promisor->fail(new \RuntimeException(
|
||||
"read operation failed on open file handle"
|
||||
));
|
||||
});
|
||||
} else {
|
||||
\uv_fs_close($this->loop, $fh, function() use ($promisor, $buffer) {
|
||||
$this->reactor->delRef();
|
||||
$promisor->succeed($buffer);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
return yield $promisor->promise();
|
||||
}
|
||||
|
||||
private function doFsOpen(string $path, int $flags, int $mode): Promise {
|
||||
$promisor = new Deferred;
|
||||
\uv_fs_open($this->loop, $path, $flags, $mode, function($fh) use ($promisor, $path) {
|
||||
$promisor->succeed($fh);
|
||||
});
|
||||
|
||||
return $promisor->promise();
|
||||
}
|
||||
|
||||
private function doFsStat($fh): Promise {
|
||||
$promisor = new Deferred;
|
||||
\uv_fs_fstat($this->loop, $fh, function($fh, $stat) use ($promisor) {
|
||||
if ($fh) {
|
||||
$stat["isdir"] = (bool) ($stat["mode"] & \UV::S_IFDIR);
|
||||
$stat["isfile"] = !$stat["isdir"];
|
||||
$promisor->succeed($stat);
|
||||
} else {
|
||||
$promisor->succeed();
|
||||
}
|
||||
});
|
||||
|
||||
return $promisor->promise();
|
||||
}
|
||||
|
||||
private function doFsRead($fh, int $offset, int $len): Promise {
|
||||
$promisor = new Deferred;
|
||||
\uv_fs_read($this->loop, $fh, $offset, $len, function($fh, $nread, $buffer) use ($promisor) {
|
||||
$promisor->succeed(($nread < 0) ? false : $buffer);
|
||||
});
|
||||
|
||||
return $promisor->promise();
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function put(string $path, string $contents): Promise {
|
||||
return resolve($this->doPut($path, $contents), $this->reactor);
|
||||
}
|
||||
|
||||
private function doPut(string $path, string $contents): \Generator {
|
||||
$flags = \UV::O_WRONLY | \UV::O_CREAT;
|
||||
$mode = \UV::S_IRWXU | \UV::S_IRUSR;
|
||||
$this->reactor->addRef();
|
||||
if (!$fh = yield $this->doFsOpen($path, $flags, $mode)) {
|
||||
$this->reactor->delRef();
|
||||
throw new \RuntimeException(
|
||||
"Failed opening write file handle"
|
||||
);
|
||||
}
|
||||
|
||||
$promisor = new Deferred;
|
||||
$len = strlen($contents);
|
||||
\uv_fs_write($this->loop, $fh, $contents, $offset = 0, function($fh, $result) use ($promisor, $len) {
|
||||
\uv_fs_close($this->loop, $fh, function() use ($promisor, $result, $len) {
|
||||
$this->reactor->delRef();
|
||||
if ($result < 0) {
|
||||
$promisor->fail(new \RuntimeException(
|
||||
uv_strerror($result)
|
||||
));
|
||||
} else {
|
||||
$promisor->succeed($len);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
return yield $promisor->promise();
|
||||
}
|
||||
}
|
30
lib/functions.php
Normal file
30
lib/functions.php
Normal file
@ -0,0 +1,30 @@
|
||||
<?php
|
||||
|
||||
namespace Amp\Fs;
|
||||
|
||||
use function Amp\reactor;
|
||||
|
||||
/**
|
||||
* Get the global default filesystem instance
|
||||
*
|
||||
* @param \Amp\Fs\Filesystem $assign Optionally specify a new default filesystem instance
|
||||
* @return \Amp\Fs\Filesystem Returns the default filesystem instance
|
||||
*/
|
||||
function fs(Filesystem $assign = null): Filesystem {
|
||||
static $filesystem;
|
||||
if ($assign) {
|
||||
return ($filesystem = $assign);
|
||||
} elseif ($filesystem) {
|
||||
return $filesystem;
|
||||
} elseif (\extension_loaded('uv')) {
|
||||
return ($filesystem = new UvFilesystem(reactor()));
|
||||
/*
|
||||
// @TODO
|
||||
} elseif (\extension_loaded("eio") {
|
||||
return ($filesystem = new EioFilesystem);
|
||||
}
|
||||
*/
|
||||
} else {
|
||||
return ($filesystem = new BlockingFilesystem(reactor()));
|
||||
}
|
||||
}
|
35
phpunit.xml
Normal file
35
phpunit.xml
Normal file
@ -0,0 +1,35 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
|
||||
<phpunit bootstrap="./vendor/autoload.php" colors="true">
|
||||
<testsuites>
|
||||
<testsuite name="Tests">
|
||||
<directory>./test</directory>
|
||||
</testsuite>
|
||||
</testsuites>
|
||||
<filter>
|
||||
<whitelist addUncoveredFilesFromWhitelist="true">
|
||||
<directory>./lib</directory>
|
||||
</whitelist>
|
||||
</filter>
|
||||
|
||||
<logging>
|
||||
<!--
|
||||
<log
|
||||
type="coverage-html"
|
||||
target="./test/coverage"
|
||||
charset="UTF-8"
|
||||
yui="true"
|
||||
lowUpperBound="35"
|
||||
highLowerBound="70"
|
||||
showUncoveredFiles="true"
|
||||
/>
|
||||
-->
|
||||
<log
|
||||
type="coverage-text"
|
||||
target="php://stdout"
|
||||
lowUpperBound="35"
|
||||
highLowerBound="70"
|
||||
/>
|
||||
|
||||
</logging>
|
||||
</phpunit>
|
15
test/BlockingDescriptorTest.php
Normal file
15
test/BlockingDescriptorTest.php
Normal file
@ -0,0 +1,15 @@
|
||||
<?php
|
||||
|
||||
namespace Amp\Fs\Test;
|
||||
|
||||
use Amp\{ Reactor, NativeReactor };
|
||||
use Amp\Fs\{ Filesystem, BlockingFilesystem };
|
||||
|
||||
class BlockingDescriptorTest extends DescriptorTest {
|
||||
protected function getReactor(): Reactor {
|
||||
return new NativeReactor;
|
||||
}
|
||||
protected function getFilesystem(Reactor $reactor): Filesystem {
|
||||
return new BlockingFilesystem($reactor);
|
||||
}
|
||||
}
|
15
test/BlockingFilesystemTest.php
Normal file
15
test/BlockingFilesystemTest.php
Normal file
@ -0,0 +1,15 @@
|
||||
<?php
|
||||
|
||||
namespace Amp\Fs\Test;
|
||||
|
||||
use Amp\{ Reactor, NativeReactor };
|
||||
use Amp\Fs\{ Filesystem, BlockingFilesystem };
|
||||
|
||||
class BlockingFilesystemTest extends FilesystemTest {
|
||||
protected function getReactor(): Reactor {
|
||||
return new NativeReactor;
|
||||
}
|
||||
protected function getFilesystem(Reactor $reactor): Filesystem {
|
||||
return new BlockingFilesystem($reactor);
|
||||
}
|
||||
}
|
86
test/DescriptorTest.php
Normal file
86
test/DescriptorTest.php
Normal file
@ -0,0 +1,86 @@
|
||||
<?php
|
||||
|
||||
namespace Amp\Fs\Test;
|
||||
|
||||
use Amp\Reactor;
|
||||
use Amp\Fs\Filesystem;
|
||||
|
||||
abstract class DescriptorTest extends \PHPUnit_Framework_TestCase {
|
||||
abstract protected function getReactor(): Reactor;
|
||||
abstract protected function getFilesystem(Reactor $reactor): Filesystem;
|
||||
|
||||
public function testReadWriteCreate() {
|
||||
($this->getReactor())->run(function($reactor) {
|
||||
$path = __DIR__ . "/fixture/new.txt";
|
||||
$fs = $this->getFilesystem($reactor);
|
||||
$flags = Filesystem::READ | Filesystem::WRITE | Filesystem::CREATE;
|
||||
$fh = yield $fs->open($path, $flags);
|
||||
yield $fh->write(0, "test");
|
||||
$data = yield $fh->read(0, 8192);
|
||||
$this->assertSame("test", $data);
|
||||
yield $fh->close();
|
||||
yield $fs->unlink($path);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* @expectedException RuntimeException
|
||||
*/
|
||||
public function testWriteFailsOnDirectory() {
|
||||
($this->getReactor())->run(function($reactor) {
|
||||
$path = __DIR__ . "/fixture/dir";
|
||||
$fs = $this->getFilesystem($reactor);
|
||||
$flags = Filesystem::READ | Filesystem::WRITE | Filesystem::CREATE;
|
||||
$fh = yield $fs->open($path, $flags);
|
||||
yield $fh->write(0, "should fail because this is a directory");
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* @expectedException RuntimeException
|
||||
*/
|
||||
public function testReadFailsOnDirectory() {
|
||||
($this->getReactor())->run(function($reactor) {
|
||||
$path = __DIR__ . "/fixture/dir";
|
||||
$fs = $this->getFilesystem($reactor);
|
||||
$flags = Filesystem::READ | Filesystem::WRITE | Filesystem::CREATE;
|
||||
$fh = yield $fs->open($path, $flags);
|
||||
yield $fh->read(0, 8192);
|
||||
});
|
||||
}
|
||||
|
||||
public function testTruncate() {
|
||||
($this->getReactor())->run(function($reactor) {
|
||||
$path = __DIR__ . "/fixture/truncate.txt";
|
||||
$fs = $this->getFilesystem($reactor);
|
||||
$flags = Filesystem::READ | Filesystem::WRITE | Filesystem::CREATE;
|
||||
$fh = yield $fs->open($path, $flags);
|
||||
yield $fh->write(0, "test");
|
||||
$data = yield $fh->read(0, 8192);
|
||||
$this->assertSame("test", $data);
|
||||
yield $fh->truncate();
|
||||
yield $fh->close();
|
||||
|
||||
$this->assertEquals(0, (yield $fs->stat($path))["size"]);
|
||||
yield $fs->unlink($path);
|
||||
});
|
||||
}
|
||||
|
||||
public function testStat() {
|
||||
($this->getReactor())->run(function($reactor) {
|
||||
$fs = $this->getFilesystem($reactor);
|
||||
|
||||
// file
|
||||
$fh = yield $fs->open(__DIR__ . "/fixture/small.txt");
|
||||
$stat = yield $fh->stat();
|
||||
$this->assertTrue($stat["isfile"]);
|
||||
$this->assertFalse($stat["isdir"]);
|
||||
|
||||
// directory
|
||||
$fh = yield $fs->open(__DIR__ . "/fixture/dir");
|
||||
$stat = yield $fh->stat();
|
||||
$this->assertFalse($stat["isfile"]);
|
||||
$this->assertTrue($stat["isdir"]);
|
||||
});
|
||||
}
|
||||
}
|
129
test/FilesystemTest.php
Normal file
129
test/FilesystemTest.php
Normal file
@ -0,0 +1,129 @@
|
||||
<?php
|
||||
|
||||
namespace Amp\Fs\Test;
|
||||
|
||||
use Amp\Reactor;
|
||||
use Amp\Fs\Filesystem;
|
||||
|
||||
abstract class FilesystemTest extends \PHPUnit_Framework_TestCase {
|
||||
abstract protected function getReactor(): Reactor;
|
||||
abstract protected function getFilesystem(Reactor $reactor): Filesystem;
|
||||
|
||||
public function testOpen() {
|
||||
($this->getReactor())->run(function($reactor) {
|
||||
$fs = $this->getFilesystem($reactor);
|
||||
$descriptor = yield $fs->open(__DIR__ . "/fixture/small.txt", Filesystem::READ);
|
||||
$this->assertInstanceOf("Amp\Fs\Descriptor", $descriptor);
|
||||
});
|
||||
}
|
||||
|
||||
public function testScandir() {
|
||||
($this->getReactor())->run(function($reactor) {
|
||||
$fs = $this->getFilesystem($reactor);
|
||||
$actual = yield $fs->scandir(__DIR__ . "/fixture");
|
||||
$expected = ["dir", "small.txt"];
|
||||
$this->assertSame($expected, $actual);
|
||||
});
|
||||
}
|
||||
|
||||
public function testSymlink() {
|
||||
($this->getReactor())->run(function($reactor) {
|
||||
$fs = $this->getFilesystem($reactor);
|
||||
|
||||
$target = __DIR__ . "/fixture/small.txt";
|
||||
$link = __DIR__ . "/fixture/symlink.txt";
|
||||
$this->assertTrue(yield $fs->symlink($target, $link));
|
||||
$this->assertTrue(is_link($link));
|
||||
yield $fs->unlink($link);
|
||||
});
|
||||
}
|
||||
|
||||
public function testLstat() {
|
||||
($this->getReactor())->run(function($reactor) {
|
||||
$fs = $this->getFilesystem($reactor);
|
||||
|
||||
$target = __DIR__ . "/fixture/small.txt";
|
||||
$link = __DIR__ . "/fixture/symlink.txt";
|
||||
$this->assertTrue(yield $fs->symlink($target, $link));
|
||||
$this->assertTrue(is_array(yield $fs->lstat($link)));
|
||||
yield $fs->unlink($link);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* @expectedException RuntimeException
|
||||
*/
|
||||
public function testOpenFailsOnNonexistentFile() {
|
||||
($this->getReactor())->run(function($reactor) {
|
||||
$fs = $this->getFilesystem($reactor);
|
||||
$descriptor = yield $fs->open(__DIR__ . "/fixture/nonexistent", Filesystem::READ);
|
||||
$this->assertInstanceOf("Amp\Fs\Descriptor", $descriptor);
|
||||
});
|
||||
}
|
||||
|
||||
public function testStat() {
|
||||
($this->getReactor())->run(function($reactor) {
|
||||
$fs = $this->getFilesystem($reactor);
|
||||
|
||||
// file
|
||||
$stat = yield $fs->stat(__DIR__ . "/fixture/small.txt");
|
||||
$this->assertTrue($stat["isfile"]);
|
||||
$this->assertFalse($stat["isdir"]);
|
||||
|
||||
// directory
|
||||
$stat = yield $fs->stat(__DIR__ . "/fixture/dir");
|
||||
$this->assertFalse($stat["isfile"]);
|
||||
$this->assertTrue($stat["isdir"]);
|
||||
|
||||
// nonexistent
|
||||
$stat = yield $fs->stat(__DIR__ . "/fixture/nonexistent");
|
||||
$this->assertNull($stat);
|
||||
});
|
||||
}
|
||||
|
||||
public function testRename() {
|
||||
($this->getReactor())->run(function($reactor) {
|
||||
$fs = $this->getFilesystem($reactor);
|
||||
|
||||
$contents1 = "rename test";
|
||||
$old = __DIR__ . "/fixture/rename1.txt";
|
||||
$new = __DIR__ . "/fixture/rename2.txt";
|
||||
|
||||
yield $fs->put($old, $contents1);
|
||||
yield $fs->rename($old, $new);
|
||||
$contents2 = yield $fs->get($new);
|
||||
yield $fs->unlink($new);
|
||||
|
||||
$this->assertSame($contents1, $contents2);
|
||||
});
|
||||
}
|
||||
|
||||
public function testUnlink() {
|
||||
($this->getReactor())->run(function($reactor) {
|
||||
$fs = $this->getFilesystem($reactor);
|
||||
|
||||
$toUnlink = __DIR__ . "/fixture/unlink";
|
||||
|
||||
yield $fs->put($toUnlink, "");
|
||||
$this->assertTrue((bool) yield $fs->stat($toUnlink));
|
||||
yield $fs->unlink($toUnlink);
|
||||
$this->assertNull(yield $fs->stat($toUnlink));
|
||||
});
|
||||
}
|
||||
|
||||
public function testMkdirRmdir() {
|
||||
($this->getReactor())->run(function($reactor) {
|
||||
$fs = $this->getFilesystem($reactor);
|
||||
|
||||
$dir = __DIR__ . "/fixture/newdir";
|
||||
|
||||
yield $fs->mkdir($dir);
|
||||
$stat = yield $fs->stat($dir);
|
||||
$this->assertTrue($stat["isdir"]);
|
||||
$this->assertFalse($stat["isfile"]);
|
||||
yield $fs->rmdir($dir);
|
||||
$this->assertNull(yield $fs->stat($dir));
|
||||
});
|
||||
}
|
||||
|
||||
}
|
15
test/UvDescriptorTest.php
Normal file
15
test/UvDescriptorTest.php
Normal file
@ -0,0 +1,15 @@
|
||||
<?php
|
||||
|
||||
namespace Amp\Fs\Test;
|
||||
|
||||
use Amp\{ Reactor, UvReactor };
|
||||
use Amp\Fs\{ Filesystem, UvFilesystem };
|
||||
|
||||
class UvDescriptorTest extends DescriptorTest {
|
||||
protected function getReactor(): Reactor {
|
||||
return new UvReactor;
|
||||
}
|
||||
protected function getFilesystem(Reactor $reactor): Filesystem {
|
||||
return new UvFilesystem($reactor);
|
||||
}
|
||||
}
|
19
test/UvFilesystemTest.php
Normal file
19
test/UvFilesystemTest.php
Normal file
@ -0,0 +1,19 @@
|
||||
<?php
|
||||
|
||||
namespace Amp\Fs\Test;
|
||||
|
||||
use Amp\{ Reactor, UvReactor };
|
||||
use Amp\Fs\{ Filesystem, UvFilesystem };
|
||||
|
||||
class UvFilesystemTest extends FilesystemTest {
|
||||
protected function getReactor(): Reactor {
|
||||
return new UvReactor;
|
||||
}
|
||||
protected function getFilesystem(Reactor $reactor): Filesystem {
|
||||
return new UvFilesystem($reactor);
|
||||
}
|
||||
|
||||
public function testScandir() {
|
||||
$this->markTestSkipped("currently crashes php");
|
||||
}
|
||||
}
|
1
test/fixture/small.txt
Normal file
1
test/fixture/small.txt
Normal file
@ -0,0 +1 @@
|
||||
small
|
Loading…
Reference in New Issue
Block a user