2016-08-30 21:05:14 +02:00
|
|
|
<?php
|
2017-11-23 03:55:30 +01:00
|
|
|
|
2016-08-30 21:05:14 +02:00
|
|
|
namespace Amp\File\Internal;
|
|
|
|
|
2017-06-17 23:41:57 +02:00
|
|
|
use Amp\File\BlockingDriver;
|
|
|
|
use Amp\File\BlockingHandle;
|
|
|
|
use Amp\File\FilesystemException;
|
2017-07-18 07:37:07 +02:00
|
|
|
use Amp\File\StatCache;
|
2017-06-17 23:41:57 +02:00
|
|
|
use Amp\Parallel\Worker\Environment;
|
|
|
|
use Amp\Parallel\Worker\Task;
|
2016-08-30 21:05:14 +02:00
|
|
|
|
2017-03-17 04:39:49 +01:00
|
|
|
/**
|
|
|
|
* @codeCoverageIgnore
|
|
|
|
*
|
|
|
|
* @internal
|
|
|
|
*/
|
2018-10-27 17:57:31 +02:00
|
|
|
class FileTask implements Task
|
|
|
|
{
|
2017-12-15 05:36:16 +01:00
|
|
|
const ENV_PREFIX = "amp/file#";
|
2017-06-17 23:41:57 +02:00
|
|
|
|
2016-08-30 21:05:14 +02:00
|
|
|
/** @var string */
|
|
|
|
private $operation;
|
|
|
|
|
|
|
|
/** @var mixed[] */
|
|
|
|
private $args;
|
|
|
|
|
|
|
|
/** @var string|null */
|
|
|
|
private $id;
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @param string $operation
|
|
|
|
* @param array $args
|
|
|
|
* @param int $id File ID.
|
|
|
|
*
|
|
|
|
* @throws \Error
|
|
|
|
*/
|
2018-10-27 17:57:31 +02:00
|
|
|
public function __construct(string $operation, array $args = [], int $id = null)
|
|
|
|
{
|
2016-08-30 21:05:14 +02:00
|
|
|
if (!\strlen($operation)) {
|
|
|
|
throw new \Error('Operation must be a non-empty string');
|
|
|
|
}
|
|
|
|
|
|
|
|
$this->operation = $operation;
|
|
|
|
$this->args = $args;
|
2017-06-22 17:16:33 +02:00
|
|
|
$this->id = $id;
|
2016-08-30 21:05:14 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* {@inheritdoc}
|
|
|
|
*
|
|
|
|
* @throws \Amp\File\FilesystemException
|
|
|
|
* @throws \Error
|
|
|
|
*/
|
2018-10-27 17:57:31 +02:00
|
|
|
public function run(Environment $environment)
|
|
|
|
{
|
2016-08-30 21:05:14 +02:00
|
|
|
if ('f' === $this->operation[0]) {
|
|
|
|
if ("fopen" === $this->operation) {
|
|
|
|
$path = $this->args[0];
|
2017-10-30 04:58:08 +01:00
|
|
|
$mode = \str_replace(['b', 't', 'e'], '', $this->args[1]);
|
2017-06-17 23:41:57 +02:00
|
|
|
|
2016-08-30 21:05:14 +02:00
|
|
|
switch ($mode) {
|
|
|
|
case "r":
|
|
|
|
case "r+":
|
|
|
|
case "w":
|
|
|
|
case "w+":
|
|
|
|
case "a":
|
|
|
|
case "a+":
|
|
|
|
case "x":
|
|
|
|
case "x+":
|
|
|
|
case "c":
|
|
|
|
case "c+":
|
|
|
|
break;
|
2017-06-17 23:41:57 +02:00
|
|
|
|
2016-08-30 21:05:14 +02:00
|
|
|
default:
|
2017-06-20 07:06:12 +02:00
|
|
|
throw new \Error("Invalid file mode");
|
2016-08-30 21:05:14 +02:00
|
|
|
}
|
2017-06-17 23:41:57 +02:00
|
|
|
|
2017-10-30 04:56:27 +01:00
|
|
|
$handle = @\fopen($path, $mode . 'be');
|
2017-06-17 23:41:57 +02:00
|
|
|
|
2016-08-30 21:05:14 +02:00
|
|
|
if (!$handle) {
|
|
|
|
$message = 'Could not open the file.';
|
|
|
|
if ($error = \error_get_last()) {
|
|
|
|
$message .= \sprintf(" Errno: %d; %s", $error["type"], $error["message"]);
|
|
|
|
}
|
|
|
|
throw new FilesystemException($message);
|
|
|
|
}
|
2017-06-17 23:41:57 +02:00
|
|
|
|
2016-08-30 21:05:14 +02:00
|
|
|
$file = new BlockingHandle($handle, $path, $mode);
|
|
|
|
$id = (int) $handle;
|
|
|
|
$size = \fstat($handle)["size"];
|
2017-06-22 17:16:33 +02:00
|
|
|
$environment->set(self::makeId($id), $file);
|
2017-06-17 23:41:57 +02:00
|
|
|
|
2016-08-30 21:05:14 +02:00
|
|
|
return [$id, $size, $mode];
|
|
|
|
}
|
|
|
|
|
2017-06-22 17:16:33 +02:00
|
|
|
if ($this->id === null) {
|
2016-08-30 21:05:14 +02:00
|
|
|
throw new FilesystemException("No file ID provided");
|
|
|
|
}
|
|
|
|
|
2017-06-22 17:16:33 +02:00
|
|
|
$id = self::makeId($this->id);
|
|
|
|
|
|
|
|
if (!$environment->exists($id)) {
|
|
|
|
throw new FilesystemException(\sprintf("No file handle with the ID %d has been opened on the worker", $this->id));
|
2016-08-30 21:05:14 +02:00
|
|
|
}
|
|
|
|
|
2017-03-17 04:39:49 +01:00
|
|
|
/** @var \Amp\File\BlockingHandle $file */
|
2017-06-22 17:16:33 +02:00
|
|
|
if (!($file = $environment->get($id)) instanceof BlockingHandle) {
|
2016-08-30 21:05:14 +02:00
|
|
|
throw new FilesystemException("File storage found in inconsistent state");
|
|
|
|
}
|
|
|
|
|
|
|
|
switch ($this->operation) {
|
|
|
|
case "fread":
|
|
|
|
case "fwrite":
|
|
|
|
case "fseek":
|
2018-10-29 05:55:11 +01:00
|
|
|
case "ftruncate":
|
2017-05-12 17:12:44 +02:00
|
|
|
return ([$file, \substr($this->operation, 1)])(...$this->args);
|
2016-08-30 21:05:14 +02:00
|
|
|
|
|
|
|
case "fclose":
|
2017-12-15 05:36:16 +01:00
|
|
|
$environment->delete($id);
|
2017-03-17 04:39:49 +01:00
|
|
|
$file->close();
|
2017-06-22 17:18:55 +02:00
|
|
|
return;
|
2016-08-30 21:05:14 +02:00
|
|
|
|
|
|
|
default:
|
|
|
|
throw new \Error('Invalid operation');
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2017-07-18 07:37:07 +02:00
|
|
|
StatCache::clear();
|
|
|
|
|
2016-08-30 21:05:14 +02:00
|
|
|
switch ($this->operation) {
|
|
|
|
case "stat":
|
|
|
|
case "unlink":
|
|
|
|
case "rename":
|
|
|
|
case "link":
|
|
|
|
case "symlink":
|
|
|
|
case "readlink":
|
|
|
|
case "lstat":
|
|
|
|
case "exists":
|
|
|
|
case "mkdir":
|
|
|
|
case "scandir":
|
|
|
|
case "rmdir":
|
|
|
|
case "chmod":
|
|
|
|
case "chown":
|
|
|
|
case "touch":
|
|
|
|
case "get":
|
|
|
|
case "put":
|
2017-07-18 06:10:17 +02:00
|
|
|
return ([new BlockingDriver, $this->operation])(...$this->args);
|
2016-08-30 21:05:14 +02:00
|
|
|
|
|
|
|
default:
|
|
|
|
throw new \Error("Invalid operation");
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @param int $id
|
|
|
|
*
|
|
|
|
* @return string
|
|
|
|
*/
|
2018-10-27 17:57:31 +02:00
|
|
|
private static function makeId(int $id): string
|
|
|
|
{
|
2016-08-30 21:05:14 +02:00
|
|
|
return self::ENV_PREFIX . $id;
|
|
|
|
}
|
|
|
|
}
|