Merge pull request #12072 from nextcloud/writestream
extend storage api to allow directly writing a stream to storage
This commit is contained in:
commit
3503329b52
|
@ -164,14 +164,19 @@ class File extends Node implements IFile {
|
|||
$this->changeLock(ILockingProvider::LOCK_EXCLUSIVE);
|
||||
}
|
||||
|
||||
$target = $partStorage->fopen($internalPartPath, 'wb');
|
||||
if ($target === false) {
|
||||
\OC::$server->getLogger()->error('\OC\Files\Filesystem::fopen() failed', ['app' => 'webdav']);
|
||||
// because we have no clue about the cause we can only throw back a 500/Internal Server Error
|
||||
throw new Exception('Could not write file contents');
|
||||
if ($partStorage->instanceOfStorage(Storage\IWriteStreamStorage::class)) {
|
||||
$count = $partStorage->writeStream($internalPartPath, $data);
|
||||
$result = $count > 0;
|
||||
} else {
|
||||
$target = $partStorage->fopen($internalPartPath, 'wb');
|
||||
if ($target === false) {
|
||||
\OC::$server->getLogger()->error('\OC\Files\Filesystem::fopen() failed', ['app' => 'webdav']);
|
||||
// because we have no clue about the cause we can only throw back a 500/Internal Server Error
|
||||
throw new Exception('Could not write file contents');
|
||||
}
|
||||
list($count, $result) = \OC_Helper::streamCopy($data, $target);
|
||||
fclose($target);
|
||||
}
|
||||
list($count, $result) = \OC_Helper::streamCopy($data, $target);
|
||||
fclose($target);
|
||||
|
||||
if ($result === false) {
|
||||
$expected = -1;
|
||||
|
@ -185,7 +190,7 @@ class File extends Node implements IFile {
|
|||
// double check if the file was fully received
|
||||
// compare expected and actual size
|
||||
if (isset($_SERVER['CONTENT_LENGTH']) && $_SERVER['REQUEST_METHOD'] === 'PUT') {
|
||||
$expected = (int) $_SERVER['CONTENT_LENGTH'];
|
||||
$expected = (int)$_SERVER['CONTENT_LENGTH'];
|
||||
if ($count !== $expected) {
|
||||
throw new BadRequest('expected filesize ' . $expected . ' got ' . $count);
|
||||
}
|
||||
|
@ -219,7 +224,7 @@ class File extends Node implements IFile {
|
|||
$renameOkay = $storage->moveFromStorage($partStorage, $internalPartPath, $internalPath);
|
||||
$fileExists = $storage->file_exists($internalPath);
|
||||
if ($renameOkay === false || $fileExists === false) {
|
||||
\OC::$server->getLogger()->error('renaming part file to final file failed ($run: ' . ( $run ? 'true' : 'false' ) . ', $renameOkay: ' . ( $renameOkay ? 'true' : 'false' ) . ', $fileExists: ' . ( $fileExists ? 'true' : 'false' ) . ')', ['app' => 'webdav']);
|
||||
\OC::$server->getLogger()->error('renaming part file to final file failed $renameOkay: ' . ($renameOkay ? 'true' : 'false') . ', $fileExists: ' . ($fileExists ? 'true' : 'false') . ')', ['app' => 'webdav']);
|
||||
throw new Exception('Could not rename part file to final file');
|
||||
}
|
||||
} catch (ForbiddenException $ex) {
|
||||
|
@ -443,7 +448,7 @@ class File extends Node implements IFile {
|
|||
//detect aborted upload
|
||||
if (isset ($_SERVER['REQUEST_METHOD']) && $_SERVER['REQUEST_METHOD'] === 'PUT') {
|
||||
if (isset($_SERVER['CONTENT_LENGTH'])) {
|
||||
$expected = (int) $_SERVER['CONTENT_LENGTH'];
|
||||
$expected = (int)$_SERVER['CONTENT_LENGTH'];
|
||||
if ($bytesWritten !== $expected) {
|
||||
$chunk_handler->remove($info['index']);
|
||||
throw new BadRequest(
|
||||
|
|
|
@ -164,7 +164,7 @@ class FileTest extends \Test\TestCase {
|
|||
public function testSimplePutFails($thrownException, $expectedException, $checkPreviousClass = true) {
|
||||
// setup
|
||||
$storage = $this->getMockBuilder(Local::class)
|
||||
->setMethods(['fopen'])
|
||||
->setMethods(['writeStream'])
|
||||
->setConstructorArgs([['datadir' => \OC::$server->getTempManager()->getTemporaryFolder()]])
|
||||
->getMock();
|
||||
\OC\Files\Filesystem::mount($storage, [], $this->user . '/');
|
||||
|
@ -182,11 +182,11 @@ class FileTest extends \Test\TestCase {
|
|||
|
||||
if ($thrownException !== null) {
|
||||
$storage->expects($this->once())
|
||||
->method('fopen')
|
||||
->method('writeStream')
|
||||
->will($this->throwException($thrownException));
|
||||
} else {
|
||||
$storage->expects($this->once())
|
||||
->method('fopen')
|
||||
->method('writeStream')
|
||||
->will($this->returnValue(false));
|
||||
}
|
||||
|
||||
|
|
|
@ -231,6 +231,7 @@ return array(
|
|||
'OCP\\Files\\Storage\\INotifyStorage' => $baseDir . '/lib/public/Files/Storage/INotifyStorage.php',
|
||||
'OCP\\Files\\Storage\\IStorage' => $baseDir . '/lib/public/Files/Storage/IStorage.php',
|
||||
'OCP\\Files\\Storage\\IStorageFactory' => $baseDir . '/lib/public/Files/Storage/IStorageFactory.php',
|
||||
'OCP\\Files\\Storage\\IWriteStreamStorage' => $baseDir . '/lib/public/Files/Storage/IWriteStreamStorage.php',
|
||||
'OCP\\Files\\UnseekableException' => $baseDir . '/lib/public/Files/UnseekableException.php',
|
||||
'OCP\\Files_FullTextSearch\\Model\\AFilesDocument' => $baseDir . '/lib/public/Files_FullTextSearch/Model/AFilesDocument.php',
|
||||
'OCP\\FullTextSearch\\Exceptions\\FullTextSearchAppNotAvailableException' => $baseDir . '/lib/public/FullTextSearch/Exceptions/FullTextSearchAppNotAvailableException.php',
|
||||
|
@ -821,6 +822,7 @@ return array(
|
|||
'OC\\Files\\Storage\\Wrapper\\PermissionsMask' => $baseDir . '/lib/private/Files/Storage/Wrapper/PermissionsMask.php',
|
||||
'OC\\Files\\Storage\\Wrapper\\Quota' => $baseDir . '/lib/private/Files/Storage/Wrapper/Quota.php',
|
||||
'OC\\Files\\Storage\\Wrapper\\Wrapper' => $baseDir . '/lib/private/Files/Storage/Wrapper/Wrapper.php',
|
||||
'OC\\Files\\Stream\\CountReadStream' => $baseDir . '/lib/private/Files/Stream/CountReadStream.php',
|
||||
'OC\\Files\\Stream\\Encryption' => $baseDir . '/lib/private/Files/Stream/Encryption.php',
|
||||
'OC\\Files\\Stream\\Quota' => $baseDir . '/lib/private/Files/Stream/Quota.php',
|
||||
'OC\\Files\\Type\\Detection' => $baseDir . '/lib/private/Files/Type/Detection.php',
|
||||
|
|
|
@ -261,6 +261,7 @@ class ComposerStaticInit53792487c5a8370acc0b06b1a864ff4c
|
|||
'OCP\\Files\\Storage\\INotifyStorage' => __DIR__ . '/../../..' . '/lib/public/Files/Storage/INotifyStorage.php',
|
||||
'OCP\\Files\\Storage\\IStorage' => __DIR__ . '/../../..' . '/lib/public/Files/Storage/IStorage.php',
|
||||
'OCP\\Files\\Storage\\IStorageFactory' => __DIR__ . '/../../..' . '/lib/public/Files/Storage/IStorageFactory.php',
|
||||
'OCP\\Files\\Storage\\IWriteStreamStorage' => __DIR__ . '/../../..' . '/lib/public/Files/Storage/IWriteStreamStorage.php',
|
||||
'OCP\\Files\\UnseekableException' => __DIR__ . '/../../..' . '/lib/public/Files/UnseekableException.php',
|
||||
'OCP\\Files_FullTextSearch\\Model\\AFilesDocument' => __DIR__ . '/../../..' . '/lib/public/Files_FullTextSearch/Model/AFilesDocument.php',
|
||||
'OCP\\FullTextSearch\\Exceptions\\FullTextSearchAppNotAvailableException' => __DIR__ . '/../../..' . '/lib/public/FullTextSearch/Exceptions/FullTextSearchAppNotAvailableException.php',
|
||||
|
@ -851,6 +852,7 @@ class ComposerStaticInit53792487c5a8370acc0b06b1a864ff4c
|
|||
'OC\\Files\\Storage\\Wrapper\\PermissionsMask' => __DIR__ . '/../../..' . '/lib/private/Files/Storage/Wrapper/PermissionsMask.php',
|
||||
'OC\\Files\\Storage\\Wrapper\\Quota' => __DIR__ . '/../../..' . '/lib/private/Files/Storage/Wrapper/Quota.php',
|
||||
'OC\\Files\\Storage\\Wrapper\\Wrapper' => __DIR__ . '/../../..' . '/lib/private/Files/Storage/Wrapper/Wrapper.php',
|
||||
'OC\\Files\\Stream\\CountReadStream' => __DIR__ . '/../../..' . '/lib/private/Files/Stream/CountReadStream.php',
|
||||
'OC\\Files\\Stream\\Encryption' => __DIR__ . '/../../..' . '/lib/private/Files/Stream/Encryption.php',
|
||||
'OC\\Files\\Stream\\Quota' => __DIR__ . '/../../..' . '/lib/private/Files/Stream/Quota.php',
|
||||
'OC\\Files\\Type\\Detection' => __DIR__ . '/../../..' . '/lib/private/Files/Type/Detection.php',
|
||||
|
|
|
@ -28,6 +28,7 @@ namespace OC\Files\ObjectStore;
|
|||
use Icewind\Streams\CallbackWrapper;
|
||||
use Icewind\Streams\IteratorDirectory;
|
||||
use OC\Files\Cache\CacheEntry;
|
||||
use OC\Files\Stream\CountReadStream;
|
||||
use OCP\Files\ObjectStore\IObjectStore;
|
||||
|
||||
class ObjectStoreStorage extends \OC\Files\Storage\Common {
|
||||
|
@ -382,41 +383,8 @@ class ObjectStoreStorage extends \OC\Files\Storage\Common {
|
|||
}
|
||||
|
||||
public function writeBack($tmpFile, $path) {
|
||||
$stat = $this->stat($path);
|
||||
if (empty($stat)) {
|
||||
// create new file
|
||||
$stat = array(
|
||||
'permissions' => \OCP\Constants::PERMISSION_ALL - \OCP\Constants::PERMISSION_CREATE,
|
||||
);
|
||||
}
|
||||
// update stat with new data
|
||||
$mTime = time();
|
||||
$stat['size'] = filesize($tmpFile);
|
||||
$stat['mtime'] = $mTime;
|
||||
$stat['storage_mtime'] = $mTime;
|
||||
|
||||
// run path based detection first, to use file extension because $tmpFile is only a random string
|
||||
$mimetypeDetector = \OC::$server->getMimeTypeDetector();
|
||||
$mimetype = $mimetypeDetector->detectPath($path);
|
||||
if ($mimetype === 'application/octet-stream') {
|
||||
$mimetype = $mimetypeDetector->detect($tmpFile);
|
||||
}
|
||||
|
||||
$stat['mimetype'] = $mimetype;
|
||||
$stat['etag'] = $this->getETag($path);
|
||||
|
||||
$fileId = $this->getCache()->put($path, $stat);
|
||||
try {
|
||||
//upload to object storage
|
||||
$this->objectStore->writeObject($this->getURN($fileId), fopen($tmpFile, 'r'));
|
||||
} catch (\Exception $ex) {
|
||||
$this->getCache()->remove($path);
|
||||
$this->logger->logException($ex, [
|
||||
'app' => 'objectstore',
|
||||
'message' => 'Could not create object ' . $this->getURN($fileId) . ' for ' . $path,
|
||||
]);
|
||||
throw $ex; // make this bubble up
|
||||
}
|
||||
$size = filesize($tmpFile);
|
||||
$this->writeStream($path, fopen($tmpFile, 'r'), $size);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -433,4 +401,60 @@ class ObjectStoreStorage extends \OC\Files\Storage\Common {
|
|||
public function needsPartFile() {
|
||||
return false;
|
||||
}
|
||||
|
||||
public function file_put_contents($path, $data) {
|
||||
$stream = fopen('php://temp', 'r+');
|
||||
fwrite($stream, $data);
|
||||
rewind($stream);
|
||||
return $this->writeStream($path, $stream, strlen($data)) > 0;
|
||||
}
|
||||
|
||||
public function writeStream(string $path, $stream, int $size = null): int {
|
||||
$stat = $this->stat($path);
|
||||
if (empty($stat)) {
|
||||
// create new file
|
||||
$stat = [
|
||||
'permissions' => \OCP\Constants::PERMISSION_ALL - \OCP\Constants::PERMISSION_CREATE,
|
||||
];
|
||||
}
|
||||
// update stat with new data
|
||||
$mTime = time();
|
||||
$stat['size'] = (int)$size;
|
||||
$stat['mtime'] = $mTime;
|
||||
$stat['storage_mtime'] = $mTime;
|
||||
|
||||
$mimetypeDetector = \OC::$server->getMimeTypeDetector();
|
||||
$mimetype = $mimetypeDetector->detectPath($path);
|
||||
|
||||
$stat['mimetype'] = $mimetype;
|
||||
$stat['etag'] = $this->getETag($path);
|
||||
|
||||
$fileId = $this->getCache()->put($path, $stat);
|
||||
try {
|
||||
//upload to object storage
|
||||
if ($size === null) {
|
||||
$countStream = CountReadStream::wrap($stream, function ($writtenSize) use ($fileId, &$size) {
|
||||
$this->getCache()->update($fileId, [
|
||||
'size' => $writtenSize
|
||||
]);
|
||||
$size = $writtenSize;
|
||||
});
|
||||
$this->objectStore->writeObject($this->getURN($fileId), $countStream);
|
||||
if (is_resource($countStream)) {
|
||||
fclose($countStream);
|
||||
}
|
||||
} else {
|
||||
$this->objectStore->writeObject($this->getURN($fileId), $stream);
|
||||
}
|
||||
} catch (\Exception $ex) {
|
||||
$this->getCache()->remove($path);
|
||||
$this->logger->logException($ex, [
|
||||
'app' => 'objectstore',
|
||||
'message' => 'Could not create object ' . $this->getURN($fileId) . ' for ' . $path,
|
||||
]);
|
||||
throw $ex; // make this bubble up
|
||||
}
|
||||
|
||||
return $size;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -54,6 +54,7 @@ use OCP\Files\InvalidPathException;
|
|||
use OCP\Files\ReservedWordException;
|
||||
use OCP\Files\Storage\ILockingStorage;
|
||||
use OCP\Files\Storage\IStorage;
|
||||
use OCP\Files\Storage\IWriteStreamStorage;
|
||||
use OCP\ILogger;
|
||||
use OCP\Lock\ILockingProvider;
|
||||
use OCP\Lock\LockedException;
|
||||
|
@ -69,7 +70,7 @@ use OCP\Lock\LockedException;
|
|||
* Some \OC\Files\Storage\Common methods call functions which are first defined
|
||||
* in classes which extend it, e.g. $this->stat() .
|
||||
*/
|
||||
abstract class Common implements Storage, ILockingStorage {
|
||||
abstract class Common implements Storage, ILockingStorage, IWriteStreamStorage {
|
||||
|
||||
use LocalTempFileTrait;
|
||||
|
||||
|
@ -809,4 +810,23 @@ abstract class Common implements Storage, ILockingStorage {
|
|||
public function needsPartFile() {
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* fallback implementation
|
||||
*
|
||||
* @param string $path
|
||||
* @param resource $stream
|
||||
* @param int $size
|
||||
* @return int
|
||||
*/
|
||||
public function writeStream(string $path, $stream, int $size = null): int {
|
||||
$target = $this->fopen($path, 'w');
|
||||
if (!$target) {
|
||||
return 0;
|
||||
}
|
||||
list($count, $result) = \OC_Helper::streamCopy($stream, $target);
|
||||
fclose($stream);
|
||||
fclose($target);
|
||||
return $count;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -462,4 +462,8 @@ class Local extends \OC\Files\Storage\Common {
|
|||
return parent::moveFromStorage($sourceStorage, $sourceInternalPath, $targetInternalPath);
|
||||
}
|
||||
}
|
||||
|
||||
public function writeStream(string $path, $stream, int $size = null): int {
|
||||
return (int)file_put_contents($this->getSourcePath($path), $stream);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1029,4 +1029,13 @@ class Encryption extends Wrapper {
|
|||
|
||||
}
|
||||
|
||||
public function writeStream(string $path, $stream, int $size = null): int {
|
||||
// always fall back to fopen
|
||||
$target = $this->fopen($path, 'w');
|
||||
list($count, $result) = \OC_Helper::streamCopy($stream, $target);
|
||||
fclose($stream);
|
||||
fclose($target);
|
||||
return $count;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -29,6 +29,7 @@ use OC\Files\Cache\Wrapper\CacheJail;
|
|||
use OC\Files\Cache\Wrapper\JailPropagator;
|
||||
use OC\Files\Filesystem;
|
||||
use OCP\Files\Storage\IStorage;
|
||||
use OCP\Files\Storage\IWriteStreamStorage;
|
||||
use OCP\Lock\ILockingProvider;
|
||||
|
||||
/**
|
||||
|
@ -515,4 +516,18 @@ class Jail extends Wrapper {
|
|||
$this->propagator = new JailPropagator($storage, \OC::$server->getDatabaseConnection());
|
||||
return $this->propagator;
|
||||
}
|
||||
|
||||
public function writeStream(string $path, $stream, int $size = null): int {
|
||||
$storage = $this->getWrapperStorage();
|
||||
if ($storage->instanceOfStorage(IWriteStreamStorage::class)) {
|
||||
/** @var IWriteStreamStorage $storage */
|
||||
return $storage->writeStream($this->getUnjailedPath($path), $stream, $size);
|
||||
} else {
|
||||
$target = $this->fopen($path, 'w');
|
||||
list($count, $result) = \OC_Helper::streamCopy($stream, $target);
|
||||
fclose($stream);
|
||||
fclose($target);
|
||||
return $count;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -32,9 +32,10 @@ namespace OC\Files\Storage\Wrapper;
|
|||
use OCP\Files\InvalidPathException;
|
||||
use OCP\Files\Storage\ILockingStorage;
|
||||
use OCP\Files\Storage\IStorage;
|
||||
use OCP\Files\Storage\IWriteStreamStorage;
|
||||
use OCP\Lock\ILockingProvider;
|
||||
|
||||
class Wrapper implements \OC\Files\Storage\Storage, ILockingStorage {
|
||||
class Wrapper implements \OC\Files\Storage\Storage, ILockingStorage, IWriteStreamStorage {
|
||||
/**
|
||||
* @var \OC\Files\Storage\Storage $storage
|
||||
*/
|
||||
|
@ -621,4 +622,18 @@ class Wrapper implements \OC\Files\Storage\Storage, ILockingStorage {
|
|||
public function needsPartFile() {
|
||||
return $this->getWrapperStorage()->needsPartFile();
|
||||
}
|
||||
|
||||
public function writeStream(string $path, $stream, int $size = null): int {
|
||||
$storage = $this->getWrapperStorage();
|
||||
if ($storage->instanceOfStorage(IWriteStreamStorage::class)) {
|
||||
/** @var IWriteStreamStorage $storage */
|
||||
return $storage->writeStream($path, $stream, $size);
|
||||
} else {
|
||||
$target = $this->fopen($path, 'w');
|
||||
list($count, $result) = \OC_Helper::streamCopy($stream, $target);
|
||||
fclose($stream);
|
||||
fclose($target);
|
||||
return $count;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,65 @@
|
|||
<?php declare(strict_types=1);
|
||||
/**
|
||||
* @copyright Copyright (c) 2018 Robin Appelman <robin@icewind.nl>
|
||||
*
|
||||
* @license GNU AGPL version 3 or any later version
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as
|
||||
* published by the Free Software Foundation, either version 3 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
|
||||
namespace OC\Files\Stream;
|
||||
|
||||
use Icewind\Streams\Wrapper;
|
||||
|
||||
class CountReadStream extends Wrapper {
|
||||
/** @var int */
|
||||
private $count;
|
||||
|
||||
/** @var callback */
|
||||
private $callback;
|
||||
|
||||
public static function wrap($source, $callback) {
|
||||
$context = stream_context_create(array(
|
||||
'count' => array(
|
||||
'source' => $source,
|
||||
'callback' => $callback,
|
||||
)
|
||||
));
|
||||
return Wrapper::wrapSource($source, $context, 'count', self::class);
|
||||
}
|
||||
|
||||
public function dir_opendir($path, $options) {
|
||||
return false;
|
||||
}
|
||||
|
||||
public function stream_open($path, $mode, $options, &$opened_path) {
|
||||
$context = $this->loadContext('count');
|
||||
|
||||
$this->callback = $context['callback'];
|
||||
return true;
|
||||
}
|
||||
|
||||
public function stream_read($count) {
|
||||
$result = parent::stream_read($count);
|
||||
$this->count += strlen($result);
|
||||
return $result;
|
||||
}
|
||||
|
||||
public function stream_close() {
|
||||
$result = parent::stream_close();
|
||||
call_user_func($this->callback, $this->count);
|
||||
return $result;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,40 @@
|
|||
<?php declare(strict_types=1);
|
||||
/**
|
||||
* @copyright Copyright (c) 2018 Robin Appelman <robin@icewind.nl>
|
||||
*
|
||||
* @license GNU AGPL version 3 or any later version
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as
|
||||
* published by the Free Software Foundation, either version 3 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
|
||||
namespace OCP\Files\Storage;
|
||||
|
||||
/**
|
||||
* Interface that adds the ability to write a stream directly to file
|
||||
*
|
||||
* @since 15.0.0
|
||||
*/
|
||||
interface IWriteStreamStorage extends IStorage {
|
||||
/**
|
||||
* Write the data from a stream to a file
|
||||
*
|
||||
* @param string $path
|
||||
* @param resource $stream
|
||||
* @param int|null $size the size of the stream if known in advance
|
||||
* @return int the number of bytes written
|
||||
* @since 15.0.0
|
||||
*/
|
||||
public function writeStream(string $path, $stream, int $size = null): int;
|
||||
}
|
|
@ -23,6 +23,7 @@
|
|||
namespace Test\Files\Storage;
|
||||
|
||||
use OC\Files\Cache\Watcher;
|
||||
use OCP\Files\Storage\IWriteStreamStorage;
|
||||
|
||||
abstract class Storage extends \Test\TestCase {
|
||||
/**
|
||||
|
@ -628,4 +629,20 @@ abstract class Storage extends \Test\TestCase {
|
|||
$this->instance->rename('bar.txt.part', 'bar.txt');
|
||||
$this->assertEquals('bar', $this->instance->file_get_contents('bar.txt'));
|
||||
}
|
||||
|
||||
public function testWriteStream() {
|
||||
$textFile = \OC::$SERVERROOT . '/tests/data/lorem.txt';
|
||||
|
||||
if (!$this->instance->instanceOfStorage(IWriteStreamStorage::class)) {
|
||||
$this->markTestSkipped('Not a WriteSteamStorage');
|
||||
}
|
||||
/** @var IWriteStreamStorage $storage */
|
||||
$storage = $this->instance;
|
||||
|
||||
$source = fopen($textFile, 'r');
|
||||
|
||||
$storage->writeStream('test.txt', $source);
|
||||
$this->assertTrue($storage->file_exists('test.txt'));
|
||||
$this->assertEquals(file_get_contents($textFile), $storage->file_get_contents('test.txt'));
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,49 @@
|
|||
<?php declare(strict_types=1);
|
||||
/**
|
||||
* @copyright Copyright (c) 2018 Robin Appelman <robin@icewind.nl>
|
||||
*
|
||||
* @license GNU AGPL version 3 or any later version
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as
|
||||
* published by the Free Software Foundation, either version 3 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
|
||||
namespace Test\Files\Stream;
|
||||
|
||||
use OC\Files\Stream\CountReadStream;
|
||||
use Test\TestCase;
|
||||
|
||||
class CountReadStreamTest extends TestCase {
|
||||
private function getStream($data) {
|
||||
$handle = fopen('php://temp', 'w+');
|
||||
fwrite($handle, $data);
|
||||
rewind($handle);
|
||||
return $handle;
|
||||
}
|
||||
|
||||
public function testBasicCount() {
|
||||
$source = $this->getStream('foo');
|
||||
$stream = CountReadStream::wrap($source, function ($size) {
|
||||
$this->assertEquals(3, $size);
|
||||
});
|
||||
stream_get_contents($stream);
|
||||
}
|
||||
|
||||
public function testLarger() {
|
||||
$stream = CountReadStream::wrap(fopen(__DIR__ . '/../../../data/testimage.mp4', 'r'), function ($size) {
|
||||
$this->assertEquals(383631, $size);
|
||||
});
|
||||
stream_get_contents($stream);
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue