зеркало из https://github.com/nextcloud/server.git
chore: remove chunking-v1
Signed-off-by: Robin Appelman <robin@icewind.nl>
This commit is contained in:
Родитель
441dfd6646
Коммит
957a00b9de
|
@ -86,21 +86,8 @@ class Directory extends \OCA\DAV\Connector\Sabre\Node implements \Sabre\DAV\ICol
|
|||
*/
|
||||
public function createFile($name, $data = null) {
|
||||
try {
|
||||
// for chunked upload also updating a existing file is a "createFile"
|
||||
// because we create all the chunks before re-assemble them to the existing file.
|
||||
if (isset($_SERVER['HTTP_OC_CHUNKED'])) {
|
||||
// exit if we can't create a new file and we don't updatable existing file
|
||||
$chunkInfo = \OC_FileChunking::decodeName($name);
|
||||
if (!$this->fileView->isCreatable($this->path) &&
|
||||
!$this->fileView->isUpdatable($this->path . '/' . $chunkInfo['name'])
|
||||
) {
|
||||
throw new \Sabre\DAV\Exception\Forbidden();
|
||||
}
|
||||
} else {
|
||||
// For non-chunked upload it is enough to check if we can create a new file
|
||||
if (!$this->fileView->isCreatable($this->path)) {
|
||||
throw new \Sabre\DAV\Exception\Forbidden();
|
||||
}
|
||||
if (!$this->fileView->isCreatable($this->path)) {
|
||||
throw new \Sabre\DAV\Exception\Forbidden();
|
||||
}
|
||||
|
||||
$this->fileView->verifyPath($this->path, $name);
|
||||
|
|
|
@ -41,7 +41,6 @@ use Sabre\DAV\Exception;
|
|||
use Sabre\DAV\Exception\BadRequest;
|
||||
use Sabre\DAV\Exception\Forbidden;
|
||||
use Sabre\DAV\Exception\NotFound;
|
||||
use Sabre\DAV\Exception\NotImplemented;
|
||||
use Sabre\DAV\Exception\ServiceUnavailable;
|
||||
use Sabre\DAV\IFile;
|
||||
|
||||
|
@ -118,16 +117,6 @@ class File extends Node implements IFile {
|
|||
// verify path of the target
|
||||
$this->verifyPath();
|
||||
|
||||
// chunked handling
|
||||
$chunkedHeader = $this->request->getHeader('oc-chunked');
|
||||
if ($chunkedHeader) {
|
||||
try {
|
||||
return $this->createFileChunked($data);
|
||||
} catch (\Exception $e) {
|
||||
$this->convertToSabreException($e);
|
||||
}
|
||||
}
|
||||
|
||||
/** @var Storage $partStorage */
|
||||
[$partStorage] = $this->fileView->resolvePath($this->path);
|
||||
$needsPartFile = $partStorage->needsPartFile() && (strlen($this->path) > 1);
|
||||
|
@ -555,135 +544,6 @@ class File extends Node implements IFile {
|
|||
return $storage->getDirectDownload($internalPath);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param resource $data
|
||||
* @return null|string
|
||||
* @throws Exception
|
||||
* @throws BadRequest
|
||||
* @throws NotImplemented
|
||||
* @throws ServiceUnavailable
|
||||
*/
|
||||
private function createFileChunked($data) {
|
||||
[$path, $name] = \Sabre\Uri\split($this->path);
|
||||
|
||||
$info = \OC_FileChunking::decodeName($name);
|
||||
if (empty($info)) {
|
||||
throw new NotImplemented($this->l10n->t('Invalid chunk name'));
|
||||
}
|
||||
|
||||
$chunk_handler = new \OC_FileChunking($info);
|
||||
$bytesWritten = $chunk_handler->store($info['index'], $data);
|
||||
|
||||
//detect aborted upload
|
||||
if ($this->request->getMethod() === 'PUT') {
|
||||
$lengthHeader = $this->request->getHeader('content-length');
|
||||
if ($lengthHeader) {
|
||||
$expected = (int)$lengthHeader;
|
||||
if ($bytesWritten !== $expected) {
|
||||
$chunk_handler->remove($info['index']);
|
||||
throw new BadRequest(
|
||||
$this->l10n->t(
|
||||
'Expected filesize of %1$s but read (from Nextcloud client) and wrote (to Nextcloud storage) %2$s. Could either be a network problem on the sending side or a problem writing to the storage on the server side.',
|
||||
[
|
||||
$this->l10n->n('%n byte', '%n bytes', $expected),
|
||||
$this->l10n->n('%n byte', '%n bytes', $bytesWritten),
|
||||
],
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if ($chunk_handler->isComplete()) {
|
||||
/** @var Storage $storage */
|
||||
[$storage,] = $this->fileView->resolvePath($path);
|
||||
$needsPartFile = $storage->needsPartFile();
|
||||
$partFile = null;
|
||||
|
||||
$targetPath = $path . '/' . $info['name'];
|
||||
/** @var \OC\Files\Storage\Storage $targetStorage */
|
||||
[$targetStorage, $targetInternalPath] = $this->fileView->resolvePath($targetPath);
|
||||
|
||||
$exists = $this->fileView->file_exists($targetPath);
|
||||
|
||||
try {
|
||||
$this->fileView->lockFile($targetPath, ILockingProvider::LOCK_SHARED);
|
||||
|
||||
$this->emitPreHooks($exists, $targetPath);
|
||||
$this->fileView->changeLock($targetPath, ILockingProvider::LOCK_EXCLUSIVE);
|
||||
/** @var \OC\Files\Storage\Storage $targetStorage */
|
||||
[$targetStorage, $targetInternalPath] = $this->fileView->resolvePath($targetPath);
|
||||
|
||||
if ($needsPartFile) {
|
||||
// we first assembly the target file as a part file
|
||||
$partFile = $this->getPartFileBasePath($path . '/' . $info['name']) . '.ocTransferId' . $info['transferid'] . '.part';
|
||||
/** @var \OC\Files\Storage\Storage $targetStorage */
|
||||
[$partStorage, $partInternalPath] = $this->fileView->resolvePath($partFile);
|
||||
|
||||
|
||||
$chunk_handler->file_assemble($partStorage, $partInternalPath);
|
||||
|
||||
// here is the final atomic rename
|
||||
$renameOkay = $targetStorage->moveFromStorage($partStorage, $partInternalPath, $targetInternalPath);
|
||||
$fileExists = $targetStorage->file_exists($targetInternalPath);
|
||||
if ($renameOkay === false || $fileExists === false) {
|
||||
\OC::$server->get(LoggerInterface::class)->error('\OC\Files\Filesystem::rename() failed', ['app' => 'webdav']);
|
||||
// only delete if an error occurred and the target file was already created
|
||||
if ($fileExists) {
|
||||
// set to null to avoid double-deletion when handling exception
|
||||
// stray part file
|
||||
$partFile = null;
|
||||
$targetStorage->unlink($targetInternalPath);
|
||||
}
|
||||
$this->fileView->changeLock($targetPath, ILockingProvider::LOCK_SHARED);
|
||||
throw new Exception($this->l10n->t('Could not rename part file assembled from chunks'));
|
||||
}
|
||||
} else {
|
||||
// assemble directly into the final file
|
||||
$chunk_handler->file_assemble($targetStorage, $targetInternalPath);
|
||||
}
|
||||
|
||||
// allow sync clients to send the mtime along in a header
|
||||
$mtimeHeader = $this->request->getHeader('x-oc-mtime');
|
||||
if ($mtimeHeader !== '') {
|
||||
$mtime = $this->sanitizeMtime($mtimeHeader);
|
||||
if ($targetStorage->touch($targetInternalPath, $mtime)) {
|
||||
$this->header('X-OC-MTime: accepted');
|
||||
}
|
||||
}
|
||||
|
||||
// since we skipped the view we need to scan and emit the hooks ourselves
|
||||
$targetStorage->getUpdater()->update($targetInternalPath);
|
||||
|
||||
$this->fileView->changeLock($targetPath, ILockingProvider::LOCK_SHARED);
|
||||
|
||||
$this->emitPostHooks($exists, $targetPath);
|
||||
|
||||
// FIXME: should call refreshInfo but can't because $this->path is not the of the final file
|
||||
$info = $this->fileView->getFileInfo($targetPath);
|
||||
|
||||
$checksumHeader = $this->request->getHeader('oc-checksum');
|
||||
if ($checksumHeader) {
|
||||
$checksum = trim($checksumHeader);
|
||||
$this->fileView->putFileInfo($targetPath, ['checksum' => $checksum]);
|
||||
} elseif ($info->getChecksum() !== null && $info->getChecksum() !== '') {
|
||||
$this->fileView->putFileInfo($this->path, ['checksum' => '']);
|
||||
}
|
||||
|
||||
$this->fileView->unlockFile($targetPath, ILockingProvider::LOCK_SHARED);
|
||||
|
||||
return $info->getEtag();
|
||||
} catch (\Exception $e) {
|
||||
if ($partFile !== null) {
|
||||
$targetStorage->unlink($targetInternalPath);
|
||||
}
|
||||
$this->convertToSabreException($e);
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert the given exception to a SabreException instance
|
||||
*
|
||||
|
|
|
@ -642,15 +642,6 @@ class FilesPlugin extends ServerPlugin {
|
|||
* @throws \Sabre\DAV\Exception\BadRequest
|
||||
*/
|
||||
public function sendFileIdHeader($filePath, ?\Sabre\DAV\INode $node = null) {
|
||||
// chunked upload handling
|
||||
if (isset($_SERVER['HTTP_OC_CHUNKED'])) {
|
||||
[$path, $name] = \Sabre\Uri\split($filePath);
|
||||
$info = \OC_FileChunking::decodeName($name);
|
||||
if (!empty($info)) {
|
||||
$filePath = $path . '/' . $info['name'];
|
||||
}
|
||||
}
|
||||
|
||||
// we get the node for the given $filePath here because in case of afterCreateFile $node is the parent folder
|
||||
if (!$this->server->tree->nodeExists($filePath)) {
|
||||
return;
|
||||
|
|
|
@ -42,7 +42,7 @@ class LockPlugin extends ServerPlugin {
|
|||
public function getLock(RequestInterface $request) {
|
||||
// we can't listen on 'beforeMethod:PUT' due to order of operations with setting up the tree
|
||||
// so instead we limit ourselves to the PUT method manually
|
||||
if ($request->getMethod() !== 'PUT' || isset($_SERVER['HTTP_OC_CHUNKED'])) {
|
||||
if ($request->getMethod() !== 'PUT') {
|
||||
return;
|
||||
}
|
||||
try {
|
||||
|
@ -65,7 +65,7 @@ class LockPlugin extends ServerPlugin {
|
|||
if ($this->isLocked === false) {
|
||||
return;
|
||||
}
|
||||
if ($request->getMethod() !== 'PUT' || isset($_SERVER['HTTP_OC_CHUNKED'])) {
|
||||
if ($request->getMethod() !== 'PUT') {
|
||||
return;
|
||||
}
|
||||
try {
|
||||
|
|
|
@ -46,35 +46,6 @@ class ObjectTree extends CachingTree {
|
|||
$this->mountManager = $mountManager;
|
||||
}
|
||||
|
||||
/**
|
||||
* If the given path is a chunked file name, converts it
|
||||
* to the real file name. Only applies if the OC-CHUNKED header
|
||||
* is present.
|
||||
*
|
||||
* @param string $path chunk file path to convert
|
||||
*
|
||||
* @return string path to real file
|
||||
*/
|
||||
private function resolveChunkFile($path) {
|
||||
if (isset($_SERVER['HTTP_OC_CHUNKED'])) {
|
||||
// resolve to real file name to find the proper node
|
||||
[$dir, $name] = \Sabre\Uri\split($path);
|
||||
if ($dir === '/' || $dir === '.') {
|
||||
$dir = '';
|
||||
}
|
||||
|
||||
$info = \OC_FileChunking::decodeName($name);
|
||||
// only replace path if it was really the chunked file
|
||||
if (isset($info['transferid'])) {
|
||||
// getNodePath is called for multiple nodes within a chunk
|
||||
// upload call
|
||||
$path = $dir . '/' . $info['name'];
|
||||
$path = ltrim($path, '/');
|
||||
}
|
||||
}
|
||||
return $path;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the INode object for the requested path
|
||||
*
|
||||
|
@ -126,9 +97,6 @@ class ObjectTree extends CachingTree {
|
|||
$info = null;
|
||||
}
|
||||
} else {
|
||||
// resolve chunk file name to real name, if applicable
|
||||
$path = $this->resolveChunkFile($path);
|
||||
|
||||
// read from cache
|
||||
try {
|
||||
$info = $this->fileView->getFileInfo($path);
|
||||
|
|
|
@ -187,26 +187,11 @@ class QuotaPlugin extends \Sabre\DAV\ServerPlugin {
|
|||
}
|
||||
$req = $this->server->httpRequest;
|
||||
|
||||
// If LEGACY chunked upload
|
||||
if ($req->getHeader('OC-Chunked')) {
|
||||
$info = \OC_FileChunking::decodeName($newName);
|
||||
$chunkHandler = $this->getFileChunking($info);
|
||||
// subtract the already uploaded size to see whether
|
||||
// there is still enough space for the remaining chunks
|
||||
$length -= $chunkHandler->getCurrentSize();
|
||||
// use target file name for free space check in case of shared files
|
||||
$path = rtrim($parentPath, '/') . '/' . $info['name'];
|
||||
}
|
||||
|
||||
// Strip any duplicate slashes
|
||||
$path = str_replace('//', '/', $path);
|
||||
|
||||
$freeSpace = $this->getFreeSpace($path);
|
||||
if ($freeSpace >= 0 && $length > $freeSpace) {
|
||||
// If LEGACY chunked upload, clean up
|
||||
if (isset($chunkHandler)) {
|
||||
$chunkHandler->cleanup();
|
||||
}
|
||||
throw new InsufficientStorage("Insufficient space in $path, $length required, $freeSpace available");
|
||||
}
|
||||
}
|
||||
|
@ -214,11 +199,6 @@ class QuotaPlugin extends \Sabre\DAV\ServerPlugin {
|
|||
return true;
|
||||
}
|
||||
|
||||
public function getFileChunking($info) {
|
||||
// FIXME: need a factory for better mocking support
|
||||
return new \OC_FileChunking($info);
|
||||
}
|
||||
|
||||
public function getLength() {
|
||||
$req = $this->server->httpRequest;
|
||||
$length = $req->getHeader('X-Expected-Entity-Length');
|
||||
|
|
|
@ -210,86 +210,6 @@ class FileTest extends TestCase {
|
|||
$this->assertEmpty($this->listPartFiles($view, ''), 'No stray part files');
|
||||
}
|
||||
|
||||
/**
|
||||
* Test putting a file using chunking
|
||||
*
|
||||
* @dataProvider fopenFailuresProvider
|
||||
*/
|
||||
public function testChunkedPutFails($thrownException, $expectedException, $checkPreviousClass = false): void {
|
||||
// setup
|
||||
$storage = $this->getMockBuilder(Local::class)
|
||||
->onlyMethods(['fopen'])
|
||||
->setConstructorArgs([['datadir' => \OCP\Server::get(ITempManager::class)->getTemporaryFolder()]])
|
||||
->getMock();
|
||||
\OC\Files\Filesystem::mount($storage, [], $this->user . '/');
|
||||
/** @var View|MockObject */
|
||||
$view = $this->getMockBuilder(View::class)
|
||||
->onlyMethods(['getRelativePath', 'resolvePath'])
|
||||
->getMock();
|
||||
$view->expects($this->atLeastOnce())
|
||||
->method('resolvePath')
|
||||
->willReturnCallback(
|
||||
function ($path) use ($storage) {
|
||||
return [$storage, $path];
|
||||
}
|
||||
);
|
||||
|
||||
if ($thrownException !== null) {
|
||||
$storage->expects($this->once())
|
||||
->method('fopen')
|
||||
->will($this->throwException($thrownException));
|
||||
} else {
|
||||
$storage->expects($this->once())
|
||||
->method('fopen')
|
||||
->willReturn(false);
|
||||
}
|
||||
|
||||
$view->expects($this->any())
|
||||
->method('getRelativePath')
|
||||
->willReturnArgument(0);
|
||||
|
||||
$request = new Request([
|
||||
'server' => [
|
||||
'HTTP_OC_CHUNKED' => 'true'
|
||||
]
|
||||
], $this->requestId, $this->config, null);
|
||||
|
||||
$info = new \OC\Files\FileInfo('/test.txt-chunking-12345-2-0', $this->getMockStorage(), null, [
|
||||
'permissions' => \OCP\Constants::PERMISSION_ALL,
|
||||
'type' => FileInfo::TYPE_FOLDER,
|
||||
], null);
|
||||
$file = new \OCA\DAV\Connector\Sabre\File($view, $info, null, $request);
|
||||
|
||||
// put first chunk
|
||||
$file->acquireLock(ILockingProvider::LOCK_SHARED);
|
||||
$this->assertNull($file->put('test data one'));
|
||||
$file->releaseLock(ILockingProvider::LOCK_SHARED);
|
||||
|
||||
$info = new \OC\Files\FileInfo('/test.txt-chunking-12345-2-1', $this->getMockStorage(), null, [
|
||||
'permissions' => \OCP\Constants::PERMISSION_ALL,
|
||||
'type' => FileInfo::TYPE_FOLDER,
|
||||
], null);
|
||||
$file = new \OCA\DAV\Connector\Sabre\File($view, $info, null, $request);
|
||||
|
||||
// action
|
||||
$caughtException = null;
|
||||
try {
|
||||
// last chunk
|
||||
$file->acquireLock(ILockingProvider::LOCK_SHARED);
|
||||
$file->put('test data two');
|
||||
$file->releaseLock(ILockingProvider::LOCK_SHARED);
|
||||
} catch (\Exception $e) {
|
||||
$caughtException = $e;
|
||||
}
|
||||
|
||||
$this->assertInstanceOf($expectedException, $caughtException);
|
||||
if ($checkPreviousClass) {
|
||||
$this->assertInstanceOf(get_class($thrownException), $caughtException->getPrevious());
|
||||
}
|
||||
|
||||
$this->assertEmpty($this->listPartFiles($view, ''), 'No stray part files');
|
||||
}
|
||||
|
||||
/**
|
||||
* Simulate putting a file to the given path.
|
||||
*
|
||||
|
@ -418,45 +338,6 @@ class FileTest extends TestCase {
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Test putting a file with string Mtime using chunking
|
||||
* @dataProvider legalMtimeProvider
|
||||
*/
|
||||
public function testChunkedPutLegalMtime($requestMtime, $resultMtime): void {
|
||||
$request = new Request([
|
||||
'server' => [
|
||||
'HTTP_X_OC_MTIME' => (string)$requestMtime,
|
||||
'HTTP_OC_CHUNKED' => 'true'
|
||||
]
|
||||
], $this->requestId, $this->config, null);
|
||||
|
||||
$file = 'foo.txt';
|
||||
|
||||
if ($resultMtime === null) {
|
||||
$this->expectException(\Sabre\DAV\Exception::class);
|
||||
}
|
||||
|
||||
$this->doPut($file.'-chunking-12345-2-0', null, $request);
|
||||
$this->doPut($file.'-chunking-12345-2-1', null, $request);
|
||||
|
||||
if ($resultMtime !== null) {
|
||||
$this->assertEquals($resultMtime, $this->getFileInfos($file)['mtime']);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Test putting a file using chunking
|
||||
*/
|
||||
public function testChunkedPut(): void {
|
||||
$request = new Request([
|
||||
'server' => [
|
||||
'HTTP_OC_CHUNKED' => 'true'
|
||||
]
|
||||
], $this->requestId, $this->config, null);
|
||||
$this->assertNull($this->doPut('/test.txt-chunking-12345-2-0', null, $request));
|
||||
$this->assertNotEmpty($this->doPut('/test.txt-chunking-12345-2-1', null, $request));
|
||||
}
|
||||
|
||||
/**
|
||||
* Test that putting a file triggers create hooks
|
||||
*/
|
||||
|
@ -559,83 +440,6 @@ class FileTest extends TestCase {
|
|||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Test that putting a file with chunks triggers create hooks
|
||||
*/
|
||||
public function testPutChunkedFileTriggersHooks(): void {
|
||||
HookHelper::setUpHooks();
|
||||
|
||||
$request = new Request([
|
||||
'server' => [
|
||||
'HTTP_OC_CHUNKED' => 'true'
|
||||
]
|
||||
], $this->requestId, $this->config, null);
|
||||
$this->assertNull($this->doPut('/foo.txt-chunking-12345-2-0', null, $request));
|
||||
$this->assertNotEmpty($this->doPut('/foo.txt-chunking-12345-2-1', null, $request));
|
||||
|
||||
$this->assertCount(4, HookHelper::$hookCalls);
|
||||
$this->assertHookCall(
|
||||
HookHelper::$hookCalls[0],
|
||||
Filesystem::signal_create,
|
||||
'/foo.txt'
|
||||
);
|
||||
$this->assertHookCall(
|
||||
HookHelper::$hookCalls[1],
|
||||
Filesystem::signal_write,
|
||||
'/foo.txt'
|
||||
);
|
||||
$this->assertHookCall(
|
||||
HookHelper::$hookCalls[2],
|
||||
Filesystem::signal_post_create,
|
||||
'/foo.txt'
|
||||
);
|
||||
$this->assertHookCall(
|
||||
HookHelper::$hookCalls[3],
|
||||
Filesystem::signal_post_write,
|
||||
'/foo.txt'
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Test that putting a chunked file triggers update hooks
|
||||
*/
|
||||
public function testPutOverwriteChunkedFileTriggersHooks(): void {
|
||||
$view = \OC\Files\Filesystem::getView();
|
||||
$view->file_put_contents('/foo.txt', 'some content that will be replaced');
|
||||
|
||||
HookHelper::setUpHooks();
|
||||
|
||||
$request = new Request([
|
||||
'server' => [
|
||||
'HTTP_OC_CHUNKED' => 'true'
|
||||
]
|
||||
], $this->requestId, $this->config, null);
|
||||
$this->assertNull($this->doPut('/foo.txt-chunking-12345-2-0', null, $request));
|
||||
$this->assertNotEmpty($this->doPut('/foo.txt-chunking-12345-2-1', null, $request));
|
||||
|
||||
$this->assertCount(4, HookHelper::$hookCalls);
|
||||
$this->assertHookCall(
|
||||
HookHelper::$hookCalls[0],
|
||||
Filesystem::signal_update,
|
||||
'/foo.txt'
|
||||
);
|
||||
$this->assertHookCall(
|
||||
HookHelper::$hookCalls[1],
|
||||
Filesystem::signal_write,
|
||||
'/foo.txt'
|
||||
);
|
||||
$this->assertHookCall(
|
||||
HookHelper::$hookCalls[2],
|
||||
Filesystem::signal_post_update,
|
||||
'/foo.txt'
|
||||
);
|
||||
$this->assertHookCall(
|
||||
HookHelper::$hookCalls[3],
|
||||
Filesystem::signal_post_write,
|
||||
'/foo.txt'
|
||||
);
|
||||
}
|
||||
|
||||
public static function cancellingHook($params): void {
|
||||
self::$hookCalls[] = [
|
||||
'signal' => Filesystem::signal_post_create,
|
||||
|
@ -753,50 +557,6 @@ class FileTest extends TestCase {
|
|||
$this->assertEmpty($this->listPartFiles($view, ''), 'No stray part files');
|
||||
}
|
||||
|
||||
/**
|
||||
* Test exception during final rename in chunk upload mode
|
||||
*/
|
||||
public function testChunkedPutFailsFinalRename(): void {
|
||||
$view = new \OC\Files\View('/' . $this->user . '/files');
|
||||
|
||||
// simulate situation where the target file is locked
|
||||
$view->lockFile('/test.txt', ILockingProvider::LOCK_EXCLUSIVE);
|
||||
|
||||
$request = new Request([
|
||||
'server' => [
|
||||
'HTTP_OC_CHUNKED' => 'true'
|
||||
]
|
||||
], $this->requestId, $this->config, null);
|
||||
|
||||
$info = new \OC\Files\FileInfo('/' . $this->user . '/files/test.txt-chunking-12345-2-0', $this->getMockStorage(), null, [
|
||||
'permissions' => \OCP\Constants::PERMISSION_ALL,
|
||||
'type' => FileInfo::TYPE_FOLDER,
|
||||
], null);
|
||||
$file = new \OCA\DAV\Connector\Sabre\File($view, $info, null, $request);
|
||||
$file->acquireLock(ILockingProvider::LOCK_SHARED);
|
||||
$this->assertNull($file->put('test data one'));
|
||||
$file->releaseLock(ILockingProvider::LOCK_SHARED);
|
||||
|
||||
$info = new \OC\Files\FileInfo('/' . $this->user . '/files/test.txt-chunking-12345-2-1', $this->getMockStorage(), null, [
|
||||
'permissions' => \OCP\Constants::PERMISSION_ALL,
|
||||
'type' => FileInfo::TYPE_FOLDER,
|
||||
], null);
|
||||
$file = new \OCA\DAV\Connector\Sabre\File($view, $info, null, $request);
|
||||
|
||||
// action
|
||||
$thrown = false;
|
||||
try {
|
||||
$file->acquireLock(ILockingProvider::LOCK_SHARED);
|
||||
$file->put($this->getStream('test data'));
|
||||
$file->releaseLock(ILockingProvider::LOCK_SHARED);
|
||||
} catch (\OCA\DAV\Connector\Sabre\Exception\FileLocked $e) {
|
||||
$thrown = true;
|
||||
}
|
||||
|
||||
$this->assertTrue($thrown);
|
||||
$this->assertEmpty($this->listPartFiles($view, ''), 'No stray part files');
|
||||
}
|
||||
|
||||
/**
|
||||
* Test put file with invalid chars
|
||||
*/
|
||||
|
|
|
@ -127,13 +127,8 @@ class ObjectTreeTest extends \Test\TestCase {
|
|||
$inputFileName,
|
||||
$fileInfoQueryPath,
|
||||
$outputFileName,
|
||||
$type,
|
||||
$enableChunkingHeader
|
||||
$type
|
||||
): void {
|
||||
if ($enableChunkingHeader) {
|
||||
$_SERVER['HTTP_OC_CHUNKED'] = true;
|
||||
}
|
||||
|
||||
$rootNode = $this->getMockBuilder(Directory::class)
|
||||
->disableOriginalConstructor()
|
||||
->getMock();
|
||||
|
@ -170,8 +165,6 @@ class ObjectTreeTest extends \Test\TestCase {
|
|||
} else {
|
||||
$this->assertTrue($node instanceof \OCA\DAV\Connector\Sabre\Directory);
|
||||
}
|
||||
|
||||
unset($_SERVER['HTTP_OC_CHUNKED']);
|
||||
}
|
||||
|
||||
public function nodeForPathProvider() {
|
||||
|
@ -182,7 +175,6 @@ class ObjectTreeTest extends \Test\TestCase {
|
|||
'regularfile.txt',
|
||||
'regularfile.txt',
|
||||
'file',
|
||||
false
|
||||
],
|
||||
// regular directory
|
||||
[
|
||||
|
@ -190,31 +182,6 @@ class ObjectTreeTest extends \Test\TestCase {
|
|||
'regulardir',
|
||||
'regulardir',
|
||||
'dir',
|
||||
false
|
||||
],
|
||||
// regular file with chunking
|
||||
[
|
||||
'regularfile.txt',
|
||||
'regularfile.txt',
|
||||
'regularfile.txt',
|
||||
'file',
|
||||
true
|
||||
],
|
||||
// regular directory with chunking
|
||||
[
|
||||
'regulardir',
|
||||
'regulardir',
|
||||
'regulardir',
|
||||
'dir',
|
||||
true
|
||||
],
|
||||
// file with chunky file name
|
||||
[
|
||||
'regularfile.txt-chunking-123566789-10-1',
|
||||
'regularfile.txt',
|
||||
'regularfile.txt',
|
||||
'file',
|
||||
true
|
||||
],
|
||||
// regular file in subdir
|
||||
[
|
||||
|
@ -222,7 +189,6 @@ class ObjectTreeTest extends \Test\TestCase {
|
|||
'subdir/regularfile.txt',
|
||||
'regularfile.txt',
|
||||
'file',
|
||||
false
|
||||
],
|
||||
// regular directory in subdir
|
||||
[
|
||||
|
@ -230,15 +196,6 @@ class ObjectTreeTest extends \Test\TestCase {
|
|||
'subdir/regulardir',
|
||||
'regulardir',
|
||||
'dir',
|
||||
false
|
||||
],
|
||||
// file with chunky file name in subdir
|
||||
[
|
||||
'subdir/regularfile.txt-chunking-123566789-10-1',
|
||||
'subdir/regularfile.txt',
|
||||
'regularfile.txt',
|
||||
'file',
|
||||
true
|
||||
],
|
||||
];
|
||||
}
|
||||
|
|
|
@ -143,29 +143,6 @@ class QuotaPluginTest extends TestCase {
|
|||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* @dataProvider quotaChunkedOkProvider
|
||||
*/
|
||||
public function testCheckQuotaChunkedOk($quota, $chunkTotalSize, $headers): void {
|
||||
$this->init($quota, 'sub/test.txt');
|
||||
|
||||
$mockChunking = $this->getMockBuilder(\OC_FileChunking::class)
|
||||
->disableOriginalConstructor()
|
||||
->getMock();
|
||||
$mockChunking->expects($this->once())
|
||||
->method('getCurrentSize')
|
||||
->willReturn($chunkTotalSize);
|
||||
|
||||
$this->plugin->expects($this->once())
|
||||
->method('getFileChunking')
|
||||
->willReturn($mockChunking);
|
||||
|
||||
$headers['OC-CHUNKED'] = 1;
|
||||
$this->server->httpRequest = new \Sabre\HTTP\Request('POST', 'dummy.file', $headers);
|
||||
$result = $this->plugin->checkQuota('/sub/test.txt-chunking-12345-3-1');
|
||||
$this->assertTrue($result);
|
||||
}
|
||||
|
||||
public function quotaChunkedFailProvider() {
|
||||
return [
|
||||
[400, 0, ['X-EXPECTED-ENTITY-LENGTH' => '1024']],
|
||||
|
@ -178,30 +155,6 @@ class QuotaPluginTest extends TestCase {
|
|||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* @dataProvider quotaChunkedFailProvider
|
||||
*/
|
||||
public function testCheckQuotaChunkedFail($quota, $chunkTotalSize, $headers): void {
|
||||
$this->expectException(\Sabre\DAV\Exception\InsufficientStorage::class);
|
||||
|
||||
$this->init($quota, 'sub/test.txt');
|
||||
|
||||
$mockChunking = $this->getMockBuilder(\OC_FileChunking::class)
|
||||
->disableOriginalConstructor()
|
||||
->getMock();
|
||||
$mockChunking->expects($this->once())
|
||||
->method('getCurrentSize')
|
||||
->willReturn($chunkTotalSize);
|
||||
|
||||
$this->plugin->expects($this->once())
|
||||
->method('getFileChunking')
|
||||
->willReturn($mockChunking);
|
||||
|
||||
$headers['OC-CHUNKED'] = 1;
|
||||
$this->server->httpRequest = new \Sabre\HTTP\Request('POST', 'dummy.file', $headers);
|
||||
$this->plugin->checkQuota('/sub/test.txt-chunking-12345-3-1');
|
||||
}
|
||||
|
||||
private function buildFileViewMock($quota, $checkedPath) {
|
||||
// mock filesysten
|
||||
$view = $this->getMockBuilder(View::class)
|
||||
|
|
|
@ -74,115 +74,4 @@ class UploadTest extends RequestTestCase {
|
|||
$result = $this->request($view, $user, 'pass', 'PUT', '/foo.txt', 'asd');
|
||||
$this->assertEquals(Http::STATUS_LOCKED, $result->getStatus());
|
||||
}
|
||||
|
||||
public function testChunkedUpload(): void {
|
||||
$user = $this->getUniqueID();
|
||||
$view = $this->setupUser($user, 'pass');
|
||||
|
||||
$this->assertFalse($view->file_exists('foo.txt'));
|
||||
$response = $this->request($view, $user, 'pass', 'PUT', '/foo.txt-chunking-123-2-0', 'asd', ['OC-Chunked' => '1']);
|
||||
|
||||
$this->assertEquals(201, $response->getStatus());
|
||||
$this->assertFalse($view->file_exists('foo.txt'));
|
||||
|
||||
$response = $this->request($view, $user, 'pass', 'PUT', '/foo.txt-chunking-123-2-1', 'bar', ['OC-Chunked' => '1']);
|
||||
|
||||
$this->assertEquals(Http::STATUS_CREATED, $response->getStatus());
|
||||
$this->assertTrue($view->file_exists('foo.txt'));
|
||||
|
||||
$this->assertEquals('asdbar', $view->file_get_contents('foo.txt'));
|
||||
|
||||
$info = $view->getFileInfo('foo.txt');
|
||||
$this->assertInstanceOf('\OC\Files\FileInfo', $info);
|
||||
$this->assertEquals(6, $info->getSize());
|
||||
}
|
||||
|
||||
public function testChunkedUploadOverWrite(): void {
|
||||
$user = $this->getUniqueID();
|
||||
$view = $this->setupUser($user, 'pass');
|
||||
|
||||
$view->file_put_contents('foo.txt', 'bar');
|
||||
$response = $this->request($view, $user, 'pass', 'PUT', '/foo.txt-chunking-123-2-0', 'asd', ['OC-Chunked' => '1']);
|
||||
|
||||
$this->assertEquals(Http::STATUS_CREATED, $response->getStatus());
|
||||
$this->assertEquals('bar', $view->file_get_contents('foo.txt'));
|
||||
|
||||
$response = $this->request($view, $user, 'pass', 'PUT', '/foo.txt-chunking-123-2-1', 'bar', ['OC-Chunked' => '1']);
|
||||
|
||||
$this->assertEquals(Http::STATUS_CREATED, $response->getStatus());
|
||||
|
||||
$this->assertEquals('asdbar', $view->file_get_contents('foo.txt'));
|
||||
|
||||
$info = $view->getFileInfo('foo.txt');
|
||||
$this->assertInstanceOf('\OC\Files\FileInfo', $info);
|
||||
$this->assertEquals(6, $info->getSize());
|
||||
}
|
||||
|
||||
public function testChunkedUploadOutOfOrder(): void {
|
||||
$user = $this->getUniqueID();
|
||||
$view = $this->setupUser($user, 'pass');
|
||||
|
||||
$this->assertFalse($view->file_exists('foo.txt'));
|
||||
$response = $this->request($view, $user, 'pass', 'PUT', '/foo.txt-chunking-123-2-1', 'bar', ['OC-Chunked' => '1']);
|
||||
|
||||
$this->assertEquals(Http::STATUS_CREATED, $response->getStatus());
|
||||
$this->assertFalse($view->file_exists('foo.txt'));
|
||||
|
||||
$response = $this->request($view, $user, 'pass', 'PUT', '/foo.txt-chunking-123-2-0', 'asd', ['OC-Chunked' => '1']);
|
||||
|
||||
$this->assertEquals(201, $response->getStatus());
|
||||
$this->assertTrue($view->file_exists('foo.txt'));
|
||||
|
||||
$this->assertEquals('asdbar', $view->file_get_contents('foo.txt'));
|
||||
|
||||
$info = $view->getFileInfo('foo.txt');
|
||||
$this->assertInstanceOf('\OC\Files\FileInfo', $info);
|
||||
$this->assertEquals(6, $info->getSize());
|
||||
}
|
||||
|
||||
public function testChunkedUploadOutOfOrderReadLocked(): void {
|
||||
$user = $this->getUniqueID();
|
||||
$view = $this->setupUser($user, 'pass');
|
||||
|
||||
$this->assertFalse($view->file_exists('foo.txt'));
|
||||
|
||||
$view->lockFile('/foo.txt', ILockingProvider::LOCK_SHARED);
|
||||
|
||||
try {
|
||||
$response = $this->request($view, $user, 'pass', 'PUT', '/foo.txt-chunking-123-2-1', 'bar', ['OC-Chunked' => '1']);
|
||||
} catch (\OCA\DAV\Connector\Sabre\Exception\FileLocked $e) {
|
||||
$this->fail('Didn\'t expect locked error for the first chunk on read lock');
|
||||
return;
|
||||
}
|
||||
|
||||
$this->assertEquals(Http::STATUS_CREATED, $response->getStatus());
|
||||
$this->assertFalse($view->file_exists('foo.txt'));
|
||||
|
||||
// last chunk should trigger the locked error since it tries to assemble
|
||||
$result = $this->request($view, $user, 'pass', 'PUT', '/foo.txt-chunking-123-2-0', 'asd', ['OC-Chunked' => '1']);
|
||||
$this->assertEquals(Http::STATUS_LOCKED, $result->getStatus());
|
||||
}
|
||||
|
||||
public function testChunkedUploadOutOfOrderWriteLocked(): void {
|
||||
$user = $this->getUniqueID();
|
||||
$view = $this->setupUser($user, 'pass');
|
||||
|
||||
$this->assertFalse($view->file_exists('foo.txt'));
|
||||
|
||||
$view->lockFile('/foo.txt', ILockingProvider::LOCK_EXCLUSIVE);
|
||||
|
||||
try {
|
||||
$response = $this->request($view, $user, 'pass', 'PUT', '/foo.txt-chunking-123-2-1', 'bar', ['OC-Chunked' => '1']);
|
||||
} catch (\OCA\DAV\Connector\Sabre\Exception\FileLocked $e) {
|
||||
$this->fail('Didn\'t expect locked error for the first chunk on write lock'); // maybe forbid this in the future for write locks only?
|
||||
return;
|
||||
}
|
||||
|
||||
$this->assertEquals(Http::STATUS_CREATED, $response->getStatus());
|
||||
$this->assertFalse($view->file_exists('foo.txt'));
|
||||
|
||||
// last chunk should trigger the locked error since it tries to assemble
|
||||
$result = $this->request($view, $user, 'pass', 'PUT', '/foo.txt-chunking-123-2-0', 'asd', ['OC-Chunked' => '1']);
|
||||
$this->assertEquals(Http::STATUS_LOCKED, $result->getStatus());
|
||||
}
|
||||
}
|
||||
|
|
|
@ -279,33 +279,6 @@ Feature: webdav-related
|
|||
When Sending a "PROPFIND" to "/remote.php/webdav/welcome.txt" with requesttoken
|
||||
Then the HTTP status code should be "207"
|
||||
|
||||
Scenario: Upload chunked file asc
|
||||
Given user "user0" exists
|
||||
And user "user0" uploads chunk file "1" of "3" with "AAAAA" to "/myChunkedFile.txt"
|
||||
And user "user0" uploads chunk file "2" of "3" with "BBBBB" to "/myChunkedFile.txt"
|
||||
And user "user0" uploads chunk file "3" of "3" with "CCCCC" to "/myChunkedFile.txt"
|
||||
When As an "user0"
|
||||
And Downloading file "/myChunkedFile.txt"
|
||||
Then Downloaded content should be "AAAAABBBBBCCCCC"
|
||||
|
||||
Scenario: Upload chunked file desc
|
||||
Given user "user0" exists
|
||||
And user "user0" uploads chunk file "3" of "3" with "CCCCC" to "/myChunkedFile.txt"
|
||||
And user "user0" uploads chunk file "2" of "3" with "BBBBB" to "/myChunkedFile.txt"
|
||||
And user "user0" uploads chunk file "1" of "3" with "AAAAA" to "/myChunkedFile.txt"
|
||||
When As an "user0"
|
||||
And Downloading file "/myChunkedFile.txt"
|
||||
Then Downloaded content should be "AAAAABBBBBCCCCC"
|
||||
|
||||
Scenario: Upload chunked file random
|
||||
Given user "user0" exists
|
||||
And user "user0" uploads chunk file "2" of "3" with "BBBBB" to "/myChunkedFile.txt"
|
||||
And user "user0" uploads chunk file "3" of "3" with "CCCCC" to "/myChunkedFile.txt"
|
||||
And user "user0" uploads chunk file "1" of "3" with "AAAAA" to "/myChunkedFile.txt"
|
||||
When As an "user0"
|
||||
And Downloading file "/myChunkedFile.txt"
|
||||
Then Downloaded content should be "AAAAABBBBBCCCCC"
|
||||
|
||||
Scenario: A file that is not shared does not have a share-types property
|
||||
Given user "user0" exists
|
||||
And user "user0" created a folder "/test"
|
||||
|
|
|
@ -211,31 +211,4 @@ class ChecksumsContext implements \Behat\Behat\Context\Context {
|
|||
throw new \Exception("Expected no checksum header but got ".$this->response->getHeader('OC-Checksum')[0]);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @Given user :user uploads chunk file :num of :total with :data to :destination with checksum :checksum
|
||||
* @param string $user
|
||||
* @param int $num
|
||||
* @param int $total
|
||||
* @param string $data
|
||||
* @param string $destination
|
||||
* @param string $checksum
|
||||
*/
|
||||
public function userUploadsChunkFileOfWithToWithChecksum($user, $num, $total, $data, $destination, $checksum) {
|
||||
$num -= 1;
|
||||
$this->response = $this->client->put(
|
||||
$this->baseUrl . '/remote.php/webdav' . $destination . '-chunking-42-'.$total.'-'.$num,
|
||||
[
|
||||
'auth' => [
|
||||
$user,
|
||||
$this->getPasswordForUser($user)
|
||||
],
|
||||
'body' => $data,
|
||||
'headers' => [
|
||||
'OC-Checksum' => $checksum,
|
||||
'OC-Chunked' => '1',
|
||||
]
|
||||
]
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -683,21 +683,6 @@ trait WebDav {
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @Given user :user uploads chunk file :num of :total with :data to :destination
|
||||
* @param string $user
|
||||
* @param int $num
|
||||
* @param int $total
|
||||
* @param string $data
|
||||
* @param string $destination
|
||||
*/
|
||||
public function userUploadsChunkFileOfWithToWithChecksum($user, $num, $total, $data, $destination) {
|
||||
$num -= 1;
|
||||
$data = \GuzzleHttp\Psr7\Utils::streamFor($data);
|
||||
$file = $destination . '-chunking-42-' . $total . '-' . $num;
|
||||
$this->makeDavRequest($user, 'PUT', $file, ['OC-Chunked' => '1'], $data, "uploads");
|
||||
}
|
||||
|
||||
/**
|
||||
* @Given user :user uploads bulked files :name1 with :content1 and :name2 with :content2 and :name3 with :content3
|
||||
* @param string $user
|
||||
|
|
|
@ -247,40 +247,6 @@ Feature: federated
|
|||
#And Downloading file "/PARENT (2)/textfile0.txt" with range "bytes=0-8"
|
||||
#Then Downloaded content should be "BLABLABLA"
|
||||
|
||||
Scenario: Overwrite a federated shared file as recipient using old chunking
|
||||
Given Using server "REMOTE"
|
||||
And user "user1" exists
|
||||
And user "user2" exists
|
||||
And Using server "LOCAL"
|
||||
And user "user0" exists
|
||||
And User "user0" from server "LOCAL" shares "/textfile0.txt" with user "user1" from server "REMOTE"
|
||||
And User "user1" from server "REMOTE" accepts last pending share
|
||||
And Using server "REMOTE"
|
||||
And As an "user1"
|
||||
#And user "user1" uploads chunk file "1" of "3" with "AAAAA" to "/textfile0 (2).txt"
|
||||
#And user "user1" uploads chunk file "2" of "3" with "BBBBB" to "/textfile0 (2).txt"
|
||||
#And user "user1" uploads chunk file "3" of "3" with "CCCCC" to "/textfile0 (2).txt"
|
||||
#When Downloading file "/textfile0 (2).txt" with range "bytes=0-4"
|
||||
#Then Downloaded content should be "AAAAA"
|
||||
|
||||
Scenario: Overwrite a federated shared folder as recipient using old chunking
|
||||
Given Using server "REMOTE"
|
||||
And user "user1" exists
|
||||
And user "user2" exists
|
||||
And Using server "LOCAL"
|
||||
And user "user0" exists
|
||||
And User "user0" from server "LOCAL" shares "/PARENT" with user "user1" from server "REMOTE"
|
||||
And User "user1" from server "REMOTE" accepts last pending share
|
||||
And Using server "REMOTE"
|
||||
And As an "user1"
|
||||
#And user "user1" uploads chunk file "1" of "3" with "AAAAA" to "/PARENT (2)/textfile0.txt"
|
||||
#And user "user1" uploads chunk file "2" of "3" with "BBBBB" to "/PARENT (2)/textfile0.txt"
|
||||
#And user "user1" uploads chunk file "3" of "3" with "CCCCC" to "/PARENT (2)/textfile0.txt"
|
||||
#When Downloading file "/PARENT (2)/textfile0.txt" with range "bytes=3-13"
|
||||
#Then Downloaded content should be "AABBBBBCCCC"
|
||||
|
||||
|
||||
|
||||
Scenario: List federated share from another server not accepted yet
|
||||
Given Using server "LOCAL"
|
||||
And user "user0" exists
|
||||
|
|
|
@ -61,19 +61,3 @@ Feature: checksums
|
|||
When user "user0" uploads file "data/textfile.txt" to "/myChecksumFile.txt"
|
||||
And user "user0" downloads the file "/myChecksumFile.txt"
|
||||
Then The OC-Checksum header should not be there
|
||||
|
||||
Scenario: Uploading a chunked file with checksum should return the checksum in the propfind
|
||||
Given user "user0" exists
|
||||
And user "user0" uploads chunk file "1" of "3" with "AAAAA" to "/myChecksumFile.txt" with checksum "MD5:e892fdd61a74bc89cd05673cc2e22f88"
|
||||
And user "user0" uploads chunk file "2" of "3" with "BBBBB" to "/myChecksumFile.txt" with checksum "MD5:e892fdd61a74bc89cd05673cc2e22f88"
|
||||
And user "user0" uploads chunk file "3" of "3" with "CCCCC" to "/myChecksumFile.txt" with checksum "MD5:e892fdd61a74bc89cd05673cc2e22f88"
|
||||
When user "user0" request the checksum of "/myChecksumFile.txt" via propfind
|
||||
Then The webdav checksum should match "MD5:e892fdd61a74bc89cd05673cc2e22f88"
|
||||
|
||||
Scenario: Uploading a chunked file with checksum should return the checksum in the download header
|
||||
Given user "user0" exists
|
||||
And user "user0" uploads chunk file "1" of "3" with "AAAAA" to "/myChecksumFile.txt" with checksum "MD5:e892fdd61a74bc89cd05673cc2e22f88"
|
||||
And user "user0" uploads chunk file "2" of "3" with "BBBBB" to "/myChecksumFile.txt" with checksum "MD5:e892fdd61a74bc89cd05673cc2e22f88"
|
||||
And user "user0" uploads chunk file "3" of "3" with "CCCCC" to "/myChecksumFile.txt" with checksum "MD5:e892fdd61a74bc89cd05673cc2e22f88"
|
||||
When user "user0" downloads the file "/myChecksumFile.txt"
|
||||
Then The header checksum should match "MD5:e892fdd61a74bc89cd05673cc2e22f88"
|
||||
|
|
|
@ -1909,7 +1909,6 @@ return array(
|
|||
'OC_API' => $baseDir . '/lib/private/legacy/OC_API.php',
|
||||
'OC_App' => $baseDir . '/lib/private/legacy/OC_App.php',
|
||||
'OC_Defaults' => $baseDir . '/lib/private/legacy/OC_Defaults.php',
|
||||
'OC_FileChunking' => $baseDir . '/lib/private/legacy/OC_FileChunking.php',
|
||||
'OC_Files' => $baseDir . '/lib/private/legacy/OC_Files.php',
|
||||
'OC_Helper' => $baseDir . '/lib/private/legacy/OC_Helper.php',
|
||||
'OC_Hook' => $baseDir . '/lib/private/legacy/OC_Hook.php',
|
||||
|
|
|
@ -1942,7 +1942,6 @@ class ComposerStaticInit749170dad3f5e7f9ca158f5a9f04f6a2
|
|||
'OC_API' => __DIR__ . '/../../..' . '/lib/private/legacy/OC_API.php',
|
||||
'OC_App' => __DIR__ . '/../../..' . '/lib/private/legacy/OC_App.php',
|
||||
'OC_Defaults' => __DIR__ . '/../../..' . '/lib/private/legacy/OC_Defaults.php',
|
||||
'OC_FileChunking' => __DIR__ . '/../../..' . '/lib/private/legacy/OC_FileChunking.php',
|
||||
'OC_Files' => __DIR__ . '/../../..' . '/lib/private/legacy/OC_Files.php',
|
||||
'OC_Helper' => __DIR__ . '/../../..' . '/lib/private/legacy/OC_Helper.php',
|
||||
'OC_Hook' => __DIR__ . '/../../..' . '/lib/private/legacy/OC_Hook.php',
|
||||
|
|
|
@ -1,161 +0,0 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* SPDX-FileCopyrightText: 2016-2024 Nextcloud GmbH and Nextcloud contributors
|
||||
* SPDX-FileCopyrightText: 2016 ownCloud, Inc.
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
class OC_FileChunking {
|
||||
protected $info;
|
||||
protected $cache;
|
||||
|
||||
/**
|
||||
* TTL of chunks
|
||||
*
|
||||
* @var int
|
||||
*/
|
||||
protected $ttl;
|
||||
|
||||
public static function decodeName($name) {
|
||||
preg_match('/(?P<name>.*)-chunking-(?P<transferid>\d+)-(?P<chunkcount>\d+)-(?P<index>\d+)/', $name, $matches);
|
||||
return $matches;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string[] $info
|
||||
*/
|
||||
public function __construct($info) {
|
||||
$this->info = $info;
|
||||
$this->ttl = \OC::$server->getConfig()->getSystemValueInt('cache_chunk_gc_ttl', 86400);
|
||||
}
|
||||
|
||||
public function getPrefix() {
|
||||
$name = $this->info['name'];
|
||||
$transferid = $this->info['transferid'];
|
||||
|
||||
return $name.'-chunking-'.$transferid.'-';
|
||||
}
|
||||
|
||||
protected function getCache() {
|
||||
if (!isset($this->cache)) {
|
||||
$this->cache = new \OC\Cache\File();
|
||||
}
|
||||
return $this->cache;
|
||||
}
|
||||
|
||||
/**
|
||||
* Stores the given $data under the given $key - the number of stored bytes is returned
|
||||
*
|
||||
* @param string $index
|
||||
* @param resource $data
|
||||
* @return int
|
||||
*/
|
||||
public function store($index, $data) {
|
||||
$cache = $this->getCache();
|
||||
$name = $this->getPrefix().$index;
|
||||
$cache->set($name, $data, $this->ttl);
|
||||
|
||||
return $cache->size($name);
|
||||
}
|
||||
|
||||
public function isComplete() {
|
||||
$prefix = $this->getPrefix();
|
||||
$cache = $this->getCache();
|
||||
$chunkcount = (int)$this->info['chunkcount'];
|
||||
|
||||
for ($i = ($chunkcount - 1); $i >= 0; $i--) {
|
||||
if (!$cache->hasKey($prefix.$i)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Assembles the chunks into the file specified by the path.
|
||||
* Chunks are deleted afterwards.
|
||||
*
|
||||
* @param resource $f target path
|
||||
*
|
||||
* @return integer assembled file size
|
||||
*
|
||||
* @throws \OC\InsufficientStorageException when file could not be fully
|
||||
* assembled due to lack of free space
|
||||
*/
|
||||
public function assemble($f) {
|
||||
$cache = $this->getCache();
|
||||
$prefix = $this->getPrefix();
|
||||
$count = 0;
|
||||
for ($i = 0; $i < $this->info['chunkcount']; $i++) {
|
||||
$chunk = $cache->get($prefix.$i);
|
||||
// remove after reading to directly save space
|
||||
$cache->remove($prefix.$i);
|
||||
$count += fwrite($f, $chunk);
|
||||
// let php release the memory to work around memory exhausted error with php 5.6
|
||||
$chunk = null;
|
||||
}
|
||||
|
||||
return $count;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the size of the chunks already present
|
||||
* @return integer size in bytes
|
||||
*/
|
||||
public function getCurrentSize() {
|
||||
$cache = $this->getCache();
|
||||
$prefix = $this->getPrefix();
|
||||
$total = 0;
|
||||
for ($i = 0; $i < $this->info['chunkcount']; $i++) {
|
||||
$total += $cache->size($prefix.$i);
|
||||
}
|
||||
return $total;
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes all chunks which belong to this transmission
|
||||
*/
|
||||
public function cleanup() {
|
||||
$cache = $this->getCache();
|
||||
$prefix = $this->getPrefix();
|
||||
for ($i = 0; $i < $this->info['chunkcount']; $i++) {
|
||||
$cache->remove($prefix.$i);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes one specific chunk
|
||||
* @param string $index
|
||||
*/
|
||||
public function remove($index) {
|
||||
$cache = $this->getCache();
|
||||
$prefix = $this->getPrefix();
|
||||
$cache->remove($prefix.$index);
|
||||
}
|
||||
|
||||
/**
|
||||
* Assembles the chunks into the file specified by the path.
|
||||
* Also triggers the relevant hooks and proxies.
|
||||
*
|
||||
* @param \OC\Files\Storage\Storage $storage storage
|
||||
* @param string $path target path relative to the storage
|
||||
* @return bool true on success or false if file could not be created
|
||||
*
|
||||
* @throws \OC\ServerNotAvailableException
|
||||
*/
|
||||
public function file_assemble($storage, $path) {
|
||||
// use file_put_contents as method because that best matches what this function does
|
||||
if (\OC\Files\Filesystem::isValidPath($path)) {
|
||||
$target = $storage->fopen($path, 'w');
|
||||
if ($target) {
|
||||
$count = $this->assemble($target);
|
||||
fclose($target);
|
||||
return $count > 0;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
|
@ -1,58 +0,0 @@
|
|||
<?php
|
||||
/**
|
||||
* SPDX-FileCopyrightText: 2016-2024 Nextcloud GmbH and Nextcloud contributors
|
||||
* SPDX-FileCopyrightText: 2016 ownCloud, Inc.
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
|
||||
namespace Test;
|
||||
|
||||
use OCP\ICache;
|
||||
|
||||
class FileChunkingTest extends \Test\TestCase {
|
||||
public function dataIsComplete() {
|
||||
return [
|
||||
[1, [], false],
|
||||
[1, [0], true],
|
||||
[2, [], false],
|
||||
[2, [0], false],
|
||||
[2, [1], false],
|
||||
[2, [0,1], true],
|
||||
[10, [], false],
|
||||
[10, [0,1,2,3,4,5,6,7,8], false],
|
||||
[10, [1,2,3,4,5,6,7,8,9], false],
|
||||
[10, [0,1,2,3,5,6,7,8,9], false],
|
||||
[10, [0,1,2,3,4,5,6,7,8,9], true],
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* @dataProvider dataIsComplete
|
||||
* @param $total
|
||||
* @param array $present
|
||||
* @param $expected
|
||||
*/
|
||||
public function testIsComplete($total, array $present, $expected) {
|
||||
$fileChunking = $this->getMockBuilder(\OC_FileChunking::class)
|
||||
->setMethods(['getCache'])
|
||||
->setConstructorArgs([[
|
||||
'name' => 'file',
|
||||
'transferid' => '42',
|
||||
'chunkcount' => $total,
|
||||
]])
|
||||
->getMock();
|
||||
|
||||
$cache = $this->createMock(ICache::class);
|
||||
|
||||
$cache->expects($this->atLeastOnce())
|
||||
->method('hasKey')
|
||||
->willReturnCallback(function ($key) use ($present) {
|
||||
$data = explode('-', $key);
|
||||
return in_array($data[3], $present);
|
||||
});
|
||||
|
||||
$fileChunking->method('getCache')->willReturn($cache);
|
||||
|
||||
$this->assertEquals($expected, $fileChunking->isComplete());
|
||||
}
|
||||
}
|
Загрузка…
Ссылка в новой задаче