...
 
Commits (2)
......@@ -35,12 +35,14 @@ class File extends FileSystem
/**
* validate constraints and check file permissions
* @return void
* @throws FileNotFoundException
* @throws FileNotFoundException|AccessDeniedException
*/
protected function checkFileReadPermissions(): void
{
if (!$this->isFile() || !$this->storage->doesSatisfyConstraints() || !$this->isReadable()) {
throw new FileNotFoundException(sprintf('unable to open file: "%s"', $this->storage instanceof Storage\Disk ? $this->storage->path()->raw : get_class($this->storage)), 404, $this->storage->getConstraintViolations());
if (!$this->isFile() || !$this->isReadable()) {
throw new FileNotFoundException(sprintf('unable to open file: "%s"', $this->storage instanceof Storage\Disk ? $this->storage->path()->raw : get_class($this->storage)), 404);
} elseif (!$this->storage->doesSatisfyConstraints()) {
throw new AccessDeniedException(sprintf('unable to open file: "%s"', $this->storage instanceof Storage\Disk ? $this->storage->path()->raw : get_class($this->storage)), 404, $this->storage->getConstraintViolations());
}
}
......@@ -63,7 +65,7 @@ class File extends FileSystem
* @param int|null $length
* @param int $mode
* @return string
* @throws FileNotFoundException
* @throws FileNotFoundException|AccessDeniedException
*/
public function read(?int $offset = null, ?int $length = null, int $mode = LOCK_SH): string
{
......@@ -77,7 +79,7 @@ class File extends FileSystem
* @param int|null $length
* @param int $mode
* @return void
* @throws FileNotFoundException
* @throws FileNotFoundException|AccessDeniedException
*/
public function stream(?int $offset = null, ?int $length = null, int $mode = LOCK_SH): void
{
......@@ -133,22 +135,9 @@ class File extends FileSystem
$destination->cd([$this->storage instanceof Storage\Disk ? $this->path()->filename : (uniqid() . '.file')]);
}
// copy file from filesystem to filesystem
if ($this->storage instanceof Storage\Disk && $destination instanceof Storage\Disk) {
if (!copy($this->storage->path()->real, $destination->path()->raw)) {
throw new AccessDeniedException('unable to copy file', 403);
}
$destination->path()->reload();
return new static($destination);
}
// read content into memory and write it into destination
$destination->writeFile($this->storage->readFile());
// reload disk-path, since we may just created a new file
if ($destination instanceof Storage\Disk) {
$destination->path()->reload();
// actual copy file to file: use native functions if possible
if (!$this->storage->copyFileTo($destination)) {
throw new AccessDeniedException('unable to copy file', 403);
}
return new static($destination);
......@@ -220,7 +209,7 @@ class File extends FileSystem
* guess content-type (mime) of storage
* @param bool $withEncoding
* @return string
* @throws UnexpectedValueException|FileNotFoundException
* @throws UnexpectedValueException|FileNotFoundException|AccessDeniedException
*/
public function getType(bool $withEncoding = false): string
{
......@@ -235,7 +224,7 @@ class File extends FileSystem
/**
* @inheritDoc
* @throws UnexpectedValueException|FileNotFoundException
* @throws UnexpectedValueException|FileNotFoundException|AccessDeniedException
*/
public function getHash(int $mode = Hash::CONTENT, string $algo = 'sha256'): string
{
......@@ -251,7 +240,7 @@ class File extends FileSystem
/**
* calculate size
* @return int
* @throws UnexpectedValueException|AccessDeniedException
* @throws UnexpectedValueException|FileNotFoundException|AccessDeniedException
*/
public function getSize(): int
{
......@@ -297,7 +286,7 @@ class File extends FileSystem
* access file for binary read/write actions
* @param int $mode
* @return Binary
* @throws RuntimeException
* @throws RuntimeException|FileNotFoundException|AccessDeniedException
*/
public function getHandle(int $mode): Binary
{
......@@ -340,4 +329,18 @@ class File extends FileSystem
$constraints !== null ? $constraints : $this->storage->getConstraints()
);
}
/**
* open and return file-stream
* @param string $mode
* @return resource
* @throws FileNotFoundException|AccessDeniedException
*/
public function getStream(string $mode = 'r+')
{
$this->checkFileReadPermissions();
return $this->storage->getStream($mode);
}
}
......@@ -156,7 +156,7 @@ abstract class FileSystem
* remove file from filesystem on object destruction
* => leaving scope or removing object reference
* @param bool $activate
* @return static
* @return self
*/
public function removeOnFree(bool $activate = true): self
{
......
......@@ -225,4 +225,31 @@ abstract class Storage
{
throw new UnsupportedException(sprintf('Listing Directory-Content is not supported for the current "%s" Storage', get_class($this)), 500);
}
/**
* @param string $mode
* @return resource
*/
abstract public function getStream(string $mode = 'r+');
/**
* update content from stream
* @param resource $stream file-handle
* @return bool
*/
abstract public function writeFromStream($stream): bool;
/**
* <b>copy</b> file to new destination
* @param Storage $destination
* @return bool success
*/
abstract public function copyFileTo(Storage $destination): bool;
/**
* <b>move</b> file to new destination
* @param Storage $destination
* @return bool success
*/
abstract public function moveFileTo(Storage $destination): bool;
}
......@@ -135,7 +135,7 @@ class Disk extends Storage
}
// open file-handler in readonly mode
$handle = \fopen($this->path->real, 'r');
$handle = $this->getStream('r');
try {
......@@ -166,7 +166,7 @@ class Disk extends Storage
}
// open file-handler in readonly mode
$handle = \fopen($this->path->real, 'r');
$handle = $this->getStream('r');
try {
......@@ -195,7 +195,7 @@ class Disk extends Storage
$this->touch(true);
// open file-handler in readonly mode
$handle = \fopen($this->path->real, $append ? 'a' : 'w');
$handle = $this->getStream($append ? 'a' : 'w');
try {
......@@ -447,4 +447,82 @@ class Disk extends Storage
array_unshift($path, $this->path);
$this->path = new Path($path);
}
/**
* @inheritDoc
* @throws RuntimeException
*/
public function getStream(string $mode = 'r+')
{
$stream = \fopen($this->path->real, $mode);
if ($stream === false) {
throw new RuntimeException('failed to open stream', 500);
}
return $stream;
}
/**
* @inheritDoc
* @throws RuntimeException
*/
public function writeFromStream($stream): bool
{
if (!is_resource($stream)) {
throw new RuntimeException(sprintf('file-handle must be of type \'resource\' but \'%s\' given', is_object($handle) ? get_class($handle) : gettype($handle)), 500);
}
$destStream = $this->getStream('w');
try {
if (!\stream_copy_to_stream($stream, $destStream)) {
return false;
}
$destination->path()->reload();
return true;
} finally {
\fclose($destStream);
}
}
/**
* @inheritDoc
*/
public function copyFileTo(Storage $destination): bool
{
switch (true) {
// copy file from disk to disk
case $destination instanceof Disk:
if (!\copy($this->path->real, $destination->path()->raw)) {
return false;
}
$destination->path()->reload();
return true;
case $destination instanceof Flysystem:
$readStream = $this->getStream('r');
try {
return $destination->writeFromStream($readStream);
} finally {
\fclose($readStream);
}
case $destination instanceof Memory:
default:
return $destination->writeFile($this->readFile());
}
}
/**
* @inheritDoc
*/
public function moveFileTo(Storage $destination): bool
{
return true;
}
}
......@@ -350,4 +350,66 @@ class Flysystem extends Storage
{
return sprintf('%s/[Adapter: %s] at: "%s"', parent::__toString(), get_class($this->flysystem->getAdapter()), $this->path);
}
/**
* @inheritDoc
* @throws RuntimeException
*/
public function getStream(string $mode = 'r+')
{
$stream = $this->flysystem->readStream($this->path);
if ($stream === false) {
throw new RuntimeException('failed to open stream', 500);
}
return $stream;
}
/**
* @inheritDoc
* @throws RuntimeException
*/
public function writeFromStream($stream): bool
{
if (!is_resource($stream)) {
throw new RuntimeException(sprintf('file-handle must be of type \'resource\' but \'%s\' given', is_object($handle) ? get_class($handle) : gettype($handle)), 500);
}
return $this->flysystem->updateStream($this->path, $stream);
}
/**
* @inheritDoc
*/
public function copyFileTo(Storage $destination): bool
{
switch (true) {
case $destination instanceof Disk:
$readStream = $this->getStream('r');
try {
return $destination->writeFromStream($readStream);
} finally {
\fclose($readStream);
}
case $destination instanceof Flysystem:
return $this->flysystem->copy($this->path, $destination->path());
case $destination instanceof Memory:
default:
return $destination->writeFile($this->readFile());
}
}
/**
* @inheritDoc
*/
public function moveFileTo(Storage $destination): bool
{
return true;
}
}
......@@ -4,6 +4,8 @@
*/
namespace ricwein\FileSystem\Storage;
use ricwein\FileSystem\Helper\Stream;
use ricwein\FileSystem\Storage;
use ricwein\FileSystem\Enum\Hash;
use ricwein\FileSystem\Exceptions\RuntimeException;
......@@ -190,4 +192,68 @@ class Memory extends Storage
{
return new Binary\Memory($this, $mode);
}
/**
* this stream is <b>READONLY!</b>
* @inheritDoc
* @throws RuntimeException
*/
public function getStream(string $mode = 'r+')
{
if (!in_array($mode, ['r+','w','w+','a+','x','x+','c+',true])) {
throw new RuntimeException(sprintf('unable to open stream for memory-storage in non-write mode "%s"', $mode), 500);
}
$stream = \fopen('php://memory', $mode);
if ($stream === false) {
throw new RuntimeException('failed to open stream', 500);
}
// pre-fill stream
\fwrite($stream, $this->content);
\rewind($stream);
return $stream;
}
/**
* @inheritDoc
* @throws RuntimeException
*/
public function writeFromStream($stream): bool
{
$streamer = new Stream($stream);
$this->content = $streamer->read();
return true;
}
/**
* @inheritDoc
*/
public function copyFileTo(Storage $destination): bool
{
switch (true) {
case $destination instanceof Disk:
if (!$destination->writeFile($this->readFile())) {
return false;
}
$destination->path()->reload();
return true;
case $destination instanceof Flysystem:
case $destination instanceof Memory:
default:
return $destination->writeFile($this->readFile());
}
}
/**
* @inheritDoc
*/
public function moveFileTo(Storage $destination): bool
{
return true;
}
}