mirror of https://github.com/nextcloud/server
Do not set up filesystem on every call
Also remove old Oc_FileChunking logis that produced GC- collectable chunks Signed-off-by: Anna Larch <anna@nextcloud.com>
This commit is contained in:
parent
d6a3ebc79f
commit
5d4efb4d5f
|
@ -48,6 +48,7 @@ use OCP\Files\StorageNotAvailableException;
|
|||
use OCP\Lock\ILockingProvider;
|
||||
use OCP\Lock\LockedException;
|
||||
use Psr\Log\LoggerInterface;
|
||||
use Sabre\DAV\Exception;
|
||||
use Sabre\DAV\Exception\BadRequest;
|
||||
use Sabre\DAV\Exception\Locked;
|
||||
use Sabre\DAV\Exception\NotFound;
|
||||
|
@ -102,33 +103,19 @@ class Directory extends \OCA\DAV\Connector\Sabre\Node implements \Sabre\DAV\ICol
|
|||
* @param string $name Name of the file
|
||||
* @param resource|string $data Initial payload
|
||||
* @return null|string
|
||||
* @throws Exception\EntityTooLarge
|
||||
* @throws Exception\UnsupportedMediaType
|
||||
* @throws FileLocked
|
||||
* @throws InvalidPath
|
||||
* @throws \Sabre\DAV\Exception
|
||||
* @throws \Sabre\DAV\Exception\BadRequest
|
||||
* @throws \Sabre\DAV\Exception\Forbidden
|
||||
* @throws \Sabre\DAV\Exception\ServiceUnavailable
|
||||
* @throws Exception
|
||||
* @throws BadRequest
|
||||
* @throws Exception\Forbidden
|
||||
* @throws ServiceUnavailable
|
||||
*/
|
||||
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();
|
||||
}
|
||||
// For non-chunked upload it is enough to check if we can create a new file
|
||||
if (!$this->fileView->isCreatable($this->path)) {
|
||||
throw new Exception\Forbidden();
|
||||
}
|
||||
|
||||
$this->fileView->verifyPath($this->path, $name);
|
||||
|
@ -153,8 +140,8 @@ class Directory extends \OCA\DAV\Connector\Sabre\Node implements \Sabre\DAV\ICol
|
|||
$this->fileView->unlockFile($path . '.upload.part', ILockingProvider::LOCK_EXCLUSIVE);
|
||||
$node->releaseLock(ILockingProvider::LOCK_SHARED);
|
||||
return $result;
|
||||
} catch (\OCP\Files\StorageNotAvailableException $e) {
|
||||
throw new \Sabre\DAV\Exception\ServiceUnavailable($e->getMessage(), $e->getCode(), $e);
|
||||
} catch (StorageNotAvailableException $e) {
|
||||
throw new ServiceUnavailable($e->getMessage());
|
||||
} catch (InvalidPathException $ex) {
|
||||
throw new InvalidPath($ex->getMessage(), false, $ex);
|
||||
} catch (ForbiddenException $ex) {
|
||||
|
@ -170,22 +157,22 @@ class Directory extends \OCA\DAV\Connector\Sabre\Node implements \Sabre\DAV\ICol
|
|||
* @param string $name
|
||||
* @throws FileLocked
|
||||
* @throws InvalidPath
|
||||
* @throws \Sabre\DAV\Exception\Forbidden
|
||||
* @throws \Sabre\DAV\Exception\ServiceUnavailable
|
||||
* @throws Exception\Forbidden
|
||||
* @throws ServiceUnavailable
|
||||
*/
|
||||
public function createDirectory($name) {
|
||||
try {
|
||||
if (!$this->info->isCreatable()) {
|
||||
throw new \Sabre\DAV\Exception\Forbidden();
|
||||
throw new Exception\Forbidden();
|
||||
}
|
||||
|
||||
$this->fileView->verifyPath($this->path, $name);
|
||||
$newPath = $this->path . '/' . $name;
|
||||
if (!$this->fileView->mkdir($newPath)) {
|
||||
throw new \Sabre\DAV\Exception\Forbidden('Could not create directory ' . $newPath);
|
||||
throw new Exception\Forbidden('Could not create directory ' . $newPath);
|
||||
}
|
||||
} catch (\OCP\Files\StorageNotAvailableException $e) {
|
||||
throw new \Sabre\DAV\Exception\ServiceUnavailable($e->getMessage());
|
||||
} catch (StorageNotAvailableException $e) {
|
||||
throw new ServiceUnavailable($e->getMessage());
|
||||
} catch (InvalidPathException $ex) {
|
||||
throw new InvalidPath($ex->getMessage());
|
||||
} catch (ForbiddenException $ex) {
|
||||
|
@ -203,7 +190,7 @@ class Directory extends \OCA\DAV\Connector\Sabre\Node implements \Sabre\DAV\ICol
|
|||
* @return \Sabre\DAV\INode
|
||||
* @throws InvalidPath
|
||||
* @throws \Sabre\DAV\Exception\NotFound
|
||||
* @throws \Sabre\DAV\Exception\ServiceUnavailable
|
||||
* @throws ServiceUnavailable
|
||||
*/
|
||||
public function getChild($name, $info = null) {
|
||||
if (!$this->info->isReadable()) {
|
||||
|
@ -216,12 +203,12 @@ class Directory extends \OCA\DAV\Connector\Sabre\Node implements \Sabre\DAV\ICol
|
|||
try {
|
||||
$this->fileView->verifyPath($this->path, $name);
|
||||
$info = $this->fileView->getFileInfo($path);
|
||||
} catch (\OCP\Files\StorageNotAvailableException $e) {
|
||||
throw new \Sabre\DAV\Exception\ServiceUnavailable($e->getMessage());
|
||||
} catch (StorageNotAvailableException $e) {
|
||||
throw new ServiceUnavailable($e->getMessage());
|
||||
} catch (InvalidPathException $ex) {
|
||||
throw new InvalidPath($ex->getMessage());
|
||||
} catch (ForbiddenException $e) {
|
||||
throw new \Sabre\DAV\Exception\Forbidden();
|
||||
throw new Exception\Forbidden();
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -298,17 +285,17 @@ class Directory extends \OCA\DAV\Connector\Sabre\Node implements \Sabre\DAV\ICol
|
|||
*
|
||||
* @return void
|
||||
* @throws FileLocked
|
||||
* @throws \Sabre\DAV\Exception\Forbidden
|
||||
* @throws Exception\Forbidden
|
||||
*/
|
||||
public function delete() {
|
||||
if ($this->path === '' || $this->path === '/' || !$this->info->isDeletable()) {
|
||||
throw new \Sabre\DAV\Exception\Forbidden();
|
||||
throw new Exception\Forbidden();
|
||||
}
|
||||
|
||||
try {
|
||||
if (!$this->fileView->rmdir($this->path)) {
|
||||
// assume it wasn't possible to remove due to permission issue
|
||||
throw new \Sabre\DAV\Exception\Forbidden();
|
||||
throw new Exception\Forbidden();
|
||||
}
|
||||
} catch (ForbiddenException $ex) {
|
||||
throw new Forbidden($ex->getMessage(), $ex->getRetry());
|
||||
|
@ -343,7 +330,7 @@ class Directory extends \OCA\DAV\Connector\Sabre\Node implements \Sabre\DAV\ICol
|
|||
} catch (\OCP\Files\NotFoundException $e) {
|
||||
$logger->warning("error while getting quota into", ['exception' => $e]);
|
||||
return [0, 0];
|
||||
} catch (\OCP\Files\StorageNotAvailableException $e) {
|
||||
} catch (StorageNotAvailableException $e) {
|
||||
$logger->warning("error while getting quota into", ['exception' => $e]);
|
||||
return [0, 0];
|
||||
} catch (NotPermittedException $e) {
|
||||
|
@ -375,7 +362,7 @@ class Directory extends \OCA\DAV\Connector\Sabre\Node implements \Sabre\DAV\ICol
|
|||
* @throws ServiceUnavailable
|
||||
* @throws Forbidden
|
||||
* @throws FileLocked
|
||||
* @throws \Sabre\DAV\Exception\Forbidden
|
||||
* @throws Exception\Forbidden
|
||||
*/
|
||||
public function moveInto($targetName, $fullSourcePath, INode $sourceNode) {
|
||||
if (!$sourceNode instanceof Node) {
|
||||
|
@ -399,7 +386,7 @@ class Directory extends \OCA\DAV\Connector\Sabre\Node implements \Sabre\DAV\ICol
|
|||
// at getNodeForPath we also check the path for isForbiddenFileOrDir
|
||||
// with that we have covered both source and destination
|
||||
if ($sourceNode instanceof Directory && $targetNodeExists) {
|
||||
throw new \Sabre\DAV\Exception\Forbidden('Could not copy directory ' . $sourceNode->getName() . ', target exists');
|
||||
throw new Exception\Forbidden('Could not copy directory ' . $sourceNode->getName() . ', target exists');
|
||||
}
|
||||
|
||||
[$sourceDir,] = \Sabre\Uri\split($sourceNode->getPath());
|
||||
|
@ -420,11 +407,11 @@ class Directory extends \OCA\DAV\Connector\Sabre\Node implements \Sabre\DAV\ICol
|
|||
if ($targetNodeExists || $sameFolder) {
|
||||
// note that renaming a share mount point is always allowed
|
||||
if (!$this->fileView->isUpdatable($destinationDir) && !$isMovableMount) {
|
||||
throw new \Sabre\DAV\Exception\Forbidden();
|
||||
throw new Exception\Forbidden();
|
||||
}
|
||||
} else {
|
||||
if (!$this->fileView->isCreatable($destinationDir)) {
|
||||
throw new \Sabre\DAV\Exception\Forbidden();
|
||||
throw new Exception\Forbidden();
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -432,7 +419,7 @@ class Directory extends \OCA\DAV\Connector\Sabre\Node implements \Sabre\DAV\ICol
|
|||
// moving to a different folder, source will be gone, like a deletion
|
||||
// note that moving a share mount point is always allowed
|
||||
if (!$this->fileView->isDeletable($sourcePath) && !$isMovableMount) {
|
||||
throw new \Sabre\DAV\Exception\Forbidden();
|
||||
throw new Exception\Forbidden();
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -445,7 +432,7 @@ class Directory extends \OCA\DAV\Connector\Sabre\Node implements \Sabre\DAV\ICol
|
|||
|
||||
$renameOkay = $this->fileView->rename($sourcePath, $destinationPath);
|
||||
if (!$renameOkay) {
|
||||
throw new \Sabre\DAV\Exception\Forbidden('');
|
||||
throw new Exception\Forbidden('');
|
||||
}
|
||||
} catch (StorageNotAvailableException $e) {
|
||||
throw new ServiceUnavailable($e->getMessage());
|
||||
|
@ -465,7 +452,7 @@ class Directory extends \OCA\DAV\Connector\Sabre\Node implements \Sabre\DAV\ICol
|
|||
$sourcePath = $sourceNode->getPath();
|
||||
|
||||
if (!$this->fileView->isCreatable($this->getPath())) {
|
||||
throw new \Sabre\DAV\Exception\Forbidden();
|
||||
throw new Exception\Forbidden();
|
||||
}
|
||||
|
||||
try {
|
||||
|
|
|
@ -148,15 +148,6 @@ class File extends Node implements IFile {
|
|||
// verify path of the target
|
||||
$this->verifyPath();
|
||||
|
||||
// chunked handling
|
||||
if (isset($_SERVER['HTTP_OC_CHUNKED'])) {
|
||||
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);
|
||||
|
@ -577,132 +568,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 (isset($_SERVER['REQUEST_METHOD']) && $_SERVER['REQUEST_METHOD'] === 'PUT') {
|
||||
if (isset($_SERVER['CONTENT_LENGTH'])) {
|
||||
$expected = (int)$_SERVER['CONTENT_LENGTH'];
|
||||
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
|
||||
if (isset($this->request->server['HTTP_X_OC_MTIME'])) {
|
||||
$mtime = $this->sanitizeMtime($this->request->server['HTTP_X_OC_MTIME']);
|
||||
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);
|
||||
|
||||
if (isset($this->request->server['HTTP_OC_CHECKSUM'])) {
|
||||
$checksum = trim($this->request->server['HTTP_OC_CHECKSUM']);
|
||||
$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
|
||||
*
|
||||
|
|
|
@ -577,15 +577,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;
|
||||
|
|
|
@ -61,7 +61,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 {
|
||||
|
@ -84,7 +84,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 {
|
||||
|
|
|
@ -67,35 +67,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
|
||||
*
|
||||
|
@ -147,9 +118,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);
|
||||
|
@ -159,12 +127,6 @@ class ObjectTree extends CachingTree {
|
|||
}
|
||||
} catch (StorageNotAvailableException $e) {
|
||||
throw new \Sabre\DAV\Exception\ServiceUnavailable('Storage is temporarily not available', 0, $e);
|
||||
} catch (StorageInvalidException $e) {
|
||||
throw new \Sabre\DAV\Exception\NotFound('Storage ' . $path . ' is invalid');
|
||||
} catch (LockedException $e) {
|
||||
throw new \Sabre\DAV\Exception\Locked();
|
||||
} catch (ForbiddenException $e) {
|
||||
throw new \Sabre\DAV\Exception\Forbidden();
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -193,31 +193,14 @@ class QuotaPlugin extends \Sabre\DAV\ServerPlugin {
|
|||
$parentPath = '';
|
||||
}
|
||||
$req = $this->server->httpRequest;
|
||||
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'];
|
||||
}
|
||||
$freeSpace = $this->getFreeSpace($path);
|
||||
if ($freeSpace >= 0 && $length > $freeSpace) {
|
||||
if (isset($chunkHandler)) {
|
||||
$chunkHandler->cleanup();
|
||||
}
|
||||
throw new InsufficientStorage("Insufficient space in $path, $length required, $freeSpace available");
|
||||
}
|
||||
}
|
||||
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');
|
||||
|
|
|
@ -73,7 +73,6 @@ class FileTest extends TestCase {
|
|||
|
||||
protected function setUp(): void {
|
||||
parent::setUp();
|
||||
unset($_SERVER['HTTP_OC_CHUNKED']);
|
||||
unset($_SERVER['CONTENT_LENGTH']);
|
||||
unset($_SERVER['REQUEST_METHOD']);
|
||||
|
||||
|
@ -91,7 +90,6 @@ class FileTest extends TestCase {
|
|||
protected function tearDown(): void {
|
||||
$userManager = \OC::$server->getUserManager();
|
||||
$userManager->get($this->user)->delete();
|
||||
unset($_SERVER['HTTP_OC_CHUNKED']);
|
||||
|
||||
parent::tearDown();
|
||||
}
|
||||
|
@ -234,81 +232,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)
|
||||
->setMethods(['fopen'])
|
||||
->setConstructorArgs([['datadir' => \OC::$server->getTempManager()->getTemporaryFolder()]])
|
||||
->getMock();
|
||||
\OC\Files\Filesystem::mount($storage, [], $this->user . '/');
|
||||
$view = $this->getMockBuilder(View::class)
|
||||
->setMethods(['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);
|
||||
|
||||
$_SERVER['HTTP_OC_CHUNKED'] = true;
|
||||
|
||||
$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);
|
||||
|
||||
// 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);
|
||||
|
||||
// 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.
|
||||
*
|
||||
|
@ -437,41 +360,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' => $requestMtime,
|
||||
]
|
||||
], $this->requestId, $this->config, null);
|
||||
|
||||
$_SERVER['HTTP_OC_CHUNKED'] = true;
|
||||
$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 {
|
||||
$_SERVER['HTTP_OC_CHUNKED'] = true;
|
||||
$this->assertNull($this->doPut('/test.txt-chunking-12345-2-0'));
|
||||
$this->assertNotEmpty($this->doPut('/test.txt-chunking-12345-2-1'));
|
||||
}
|
||||
|
||||
/**
|
||||
* Test that putting a file triggers create hooks
|
||||
*/
|
||||
|
@ -574,75 +462,6 @@ class FileTest extends TestCase {
|
|||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Test that putting a file with chunks triggers create hooks
|
||||
*/
|
||||
public function testPutChunkedFileTriggersHooks(): void {
|
||||
HookHelper::setUpHooks();
|
||||
|
||||
$_SERVER['HTTP_OC_CHUNKED'] = true;
|
||||
$this->assertNull($this->doPut('/foo.txt-chunking-12345-2-0'));
|
||||
$this->assertNotEmpty($this->doPut('/foo.txt-chunking-12345-2-1'));
|
||||
|
||||
$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();
|
||||
|
||||
$_SERVER['HTTP_OC_CHUNKED'] = true;
|
||||
$this->assertNull($this->doPut('/foo.txt-chunking-12345-2-0'));
|
||||
$this->assertNotEmpty($this->doPut('/foo.txt-chunking-12345-2-1'));
|
||||
|
||||
$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,
|
||||
|
@ -755,46 +574,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);
|
||||
|
||||
$_SERVER['HTTP_OC_CHUNKED'] = true;
|
||||
|
||||
$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);
|
||||
$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);
|
||||
|
||||
// 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
|
||||
*/
|
||||
|
|
|
@ -148,13 +148,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();
|
||||
|
@ -191,8 +186,6 @@ class ObjectTreeTest extends \Test\TestCase {
|
|||
} else {
|
||||
$this->assertTrue($node instanceof \OCA\DAV\Connector\Sabre\Directory);
|
||||
}
|
||||
|
||||
unset($_SERVER['HTTP_OC_CHUNKED']);
|
||||
}
|
||||
|
||||
public function nodeForPathProvider() {
|
||||
|
@ -202,94 +195,67 @@ class ObjectTreeTest extends \Test\TestCase {
|
|||
'regularfile.txt',
|
||||
'regularfile.txt',
|
||||
'regularfile.txt',
|
||||
'file',
|
||||
false
|
||||
'file'
|
||||
],
|
||||
// regular directory
|
||||
[
|
||||
'regulardir',
|
||||
'regulardir',
|
||||
'regulardir',
|
||||
'dir',
|
||||
false
|
||||
'dir'
|
||||
],
|
||||
// regular file with chunking
|
||||
[
|
||||
'regularfile.txt',
|
||||
'regularfile.txt',
|
||||
'regularfile.txt',
|
||||
'file',
|
||||
true
|
||||
'file'
|
||||
],
|
||||
// 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
|
||||
'dir'
|
||||
],
|
||||
// regular file in subdir
|
||||
[
|
||||
'subdir/regularfile.txt',
|
||||
'subdir/regularfile.txt',
|
||||
'regularfile.txt',
|
||||
'file',
|
||||
false
|
||||
'file'
|
||||
],
|
||||
// regular directory in subdir
|
||||
[
|
||||
'subdir/regulardir',
|
||||
'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
|
||||
'dir'
|
||||
],
|
||||
];
|
||||
}
|
||||
|
||||
|
||||
public function testGetNodeForPathInvalidPath(): void {
|
||||
$this->expectException(\OCA\DAV\Connector\Sabre\Exception\InvalidPath::class);
|
||||
|
||||
$path = '/foo\bar';
|
||||
|
||||
|
||||
$storage = new Temporary([]);
|
||||
|
||||
$rootNode = $this->getMockBuilder(Directory::class)
|
||||
->disableOriginalConstructor()
|
||||
->getMock();
|
||||
$mountManager = $this->createMock(IMountManager::class);
|
||||
$view = $this->getMockBuilder(View::class)
|
||||
->setMethods(['resolvePath'])
|
||||
->getMock();
|
||||
|
||||
$this->expectException(\OCA\DAV\Connector\Sabre\Exception\InvalidPath::class);
|
||||
$view->expects($this->once())
|
||||
->method('resolvePath')
|
||||
->willReturnCallback(function ($path) use ($storage) {
|
||||
return [$storage, ltrim($path, '/')];
|
||||
});
|
||||
|
||||
$rootNode = $this->getMockBuilder(Directory::class)
|
||||
->disableOriginalConstructor()
|
||||
->getMock();
|
||||
$mountManager = $this->createMock(IMountManager::class);
|
||||
|
||||
$tree = new \OCA\DAV\Connector\Sabre\ObjectTree();
|
||||
$tree->init($rootNode, $view, $mountManager);
|
||||
|
||||
$tree->getNodeForPath($path);
|
||||
}
|
||||
|
||||
|
|
|
@ -52,10 +52,7 @@ class QuotaPluginTest extends TestCase {
|
|||
private function init($quota, $checkedPath = ''): void {
|
||||
$view = $this->buildFileViewMock($quota, $checkedPath);
|
||||
$this->server = new \Sabre\DAV\Server();
|
||||
$this->plugin = $this->getMockBuilder(QuotaPlugin::class)
|
||||
->setConstructorArgs([$view])
|
||||
->setMethods(['getFileChunking'])
|
||||
->getMock();
|
||||
$this->plugin = new QuotaPlugin($view);
|
||||
$this->plugin->initialize($this->server);
|
||||
}
|
||||
|
||||
|
@ -64,8 +61,6 @@ class QuotaPluginTest extends TestCase {
|
|||
*/
|
||||
public function testLength($expected, $headers): void {
|
||||
$this->init(0);
|
||||
$this->plugin->expects($this->never())
|
||||
->method('getFileChunking');
|
||||
$this->server->httpRequest = new \Sabre\HTTP\Request('POST', 'dummy.file', $headers);
|
||||
$length = $this->plugin->getLength();
|
||||
$this->assertEquals($expected, $length);
|
||||
|
@ -76,8 +71,6 @@ class QuotaPluginTest extends TestCase {
|
|||
*/
|
||||
public function testCheckQuota($quota, $headers): void {
|
||||
$this->init($quota);
|
||||
$this->plugin->expects($this->never())
|
||||
->method('getFileChunking');
|
||||
|
||||
$this->server->httpRequest = new \Sabre\HTTP\Request('POST', 'dummy.file', $headers);
|
||||
$result = $this->plugin->checkQuota('');
|
||||
|
@ -91,8 +84,6 @@ class QuotaPluginTest extends TestCase {
|
|||
$this->expectException(\Sabre\DAV\Exception\InsufficientStorage::class);
|
||||
|
||||
$this->init($quota);
|
||||
$this->plugin->expects($this->never())
|
||||
->method('getFileChunking');
|
||||
|
||||
$this->server->httpRequest = new \Sabre\HTTP\Request('POST', 'dummy.file', $headers);
|
||||
$this->plugin->checkQuota('');
|
||||
|
@ -103,8 +94,6 @@ class QuotaPluginTest extends TestCase {
|
|||
*/
|
||||
public function testCheckQuotaOnPath($quota, $headers): void {
|
||||
$this->init($quota, 'sub/test.txt');
|
||||
$this->plugin->expects($this->never())
|
||||
->method('getFileChunking');
|
||||
|
||||
$this->server->httpRequest = new \Sabre\HTTP\Request('POST', 'dummy.file', $headers);
|
||||
$result = $this->plugin->checkQuota('/sub/test.txt');
|
||||
|
@ -154,84 +143,6 @@ class QuotaPluginTest extends TestCase {
|
|||
];
|
||||
}
|
||||
|
||||
public function quotaChunkedOkProvider() {
|
||||
return [
|
||||
[1024, 0, ['X-EXPECTED-ENTITY-LENGTH' => '1024']],
|
||||
[1024, 0, ['CONTENT-LENGTH' => '512']],
|
||||
[1024, 0, ['OC-TOTAL-LENGTH' => '1024', 'CONTENT-LENGTH' => '512']],
|
||||
// with existing chunks (allowed size = total length - chunk total size)
|
||||
[400, 128, ['X-EXPECTED-ENTITY-LENGTH' => '512']],
|
||||
[400, 128, ['CONTENT-LENGTH' => '512']],
|
||||
[400, 128, ['OC-TOTAL-LENGTH' => '512', 'CONTENT-LENGTH' => '500']],
|
||||
// \OCP\Files\FileInfo::SPACE-UNKNOWN = -2
|
||||
[-2, 0, ['X-EXPECTED-ENTITY-LENGTH' => '1024']],
|
||||
[-2, 0, ['CONTENT-LENGTH' => '512']],
|
||||
[-2, 0, ['OC-TOTAL-LENGTH' => '1024', 'CONTENT-LENGTH' => '512']],
|
||||
[-2, 128, ['X-EXPECTED-ENTITY-LENGTH' => '1024']],
|
||||
[-2, 128, ['CONTENT-LENGTH' => '512']],
|
||||
[-2, 128, ['OC-TOTAL-LENGTH' => '1024', 'CONTENT-LENGTH' => '512']],
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* @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']],
|
||||
[400, 0, ['CONTENT-LENGTH' => '512']],
|
||||
[400, 0, ['OC-TOTAL-LENGTH' => '1024', 'CONTENT-LENGTH' => '512']],
|
||||
// with existing chunks (allowed size = total length - chunk total size)
|
||||
[380, 128, ['X-EXPECTED-ENTITY-LENGTH' => '512']],
|
||||
[380, 128, ['CONTENT-LENGTH' => '512']],
|
||||
[380, 128, ['OC-TOTAL-LENGTH' => '512', 'CONTENT-LENGTH' => '500']],
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* @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)
|
||||
|
|
|
@ -57,8 +57,6 @@ abstract class RequestTestCase extends TestCase {
|
|||
protected function setUp(): void {
|
||||
parent::setUp();
|
||||
|
||||
unset($_SERVER['HTTP_OC_CHUNKED']);
|
||||
|
||||
$this->serverFactory = new ServerFactory(
|
||||
\OC::$server->getConfig(),
|
||||
\OC::$server->get(LoggerInterface::class),
|
||||
|
|
|
@ -92,114 +92,4 @@ class UploadTest extends RequestTestCase {
|
|||
$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());
|
||||
}
|
||||
}
|
||||
|
|
|
@ -5,7 +5,7 @@
|
|||
<name>Files</name>
|
||||
<summary>File Management</summary>
|
||||
<description>File Management</description>
|
||||
<version>1.21.1</version>
|
||||
<version>1.21.2</version>
|
||||
<licence>agpl</licence>
|
||||
<author>Robin Appelman</author>
|
||||
<author>Vincent Petry</author>
|
||||
|
@ -22,11 +22,12 @@
|
|||
</dependencies>
|
||||
|
||||
<background-jobs>
|
||||
<job>OCA\Files\BackgroundJob\ScanFiles</job>
|
||||
<job>OCA\Files\BackgroundJob\DeleteOrphanedItems</job>
|
||||
<job>OCA\Files\BackgroundJob\CleanupFileLocks</job>
|
||||
<job>OCA\Files\BackgroundJob\CleanupDirectEditingTokens</job>
|
||||
<job>OCA\Files\BackgroundJob\CleanupFileLocks</job>
|
||||
<job>OCA\Files\BackgroundJob\DeleteExpiredOpenLocalEditor</job>
|
||||
<job>OCA\Files\BackgroundJob\DeleteOrphanedItems</job>
|
||||
<job>OCA\Files\BackgroundJob\FileChunkCleanupJob</job>
|
||||
<job>OCA\Files\BackgroundJob\ScanFiles</job>
|
||||
</background-jobs>
|
||||
|
||||
<commands>
|
||||
|
|
|
@ -429,7 +429,8 @@ class ClassLoader
|
|||
public function loadClass($class)
|
||||
{
|
||||
if ($file = $this->findFile($class)) {
|
||||
(self::$includeFile)($file);
|
||||
$includeFile = self::$includeFile;
|
||||
$includeFile($file);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
@ -560,7 +561,10 @@ class ClassLoader
|
|||
return false;
|
||||
}
|
||||
|
||||
private static function initializeIncludeClosure(): void
|
||||
/**
|
||||
* @return void
|
||||
*/
|
||||
private static function initializeIncludeClosure()
|
||||
{
|
||||
if (self::$includeFile !== null) {
|
||||
return;
|
||||
|
@ -574,8 +578,8 @@ class ClassLoader
|
|||
* @param string $file
|
||||
* @return void
|
||||
*/
|
||||
self::$includeFile = static function($file) {
|
||||
self::$includeFile = \Closure::bind(static function($file) {
|
||||
include $file;
|
||||
};
|
||||
}, null, null);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -22,6 +22,7 @@ return array(
|
|||
'OCA\\Files\\BackgroundJob\\CleanupFileLocks' => $baseDir . '/../lib/BackgroundJob/CleanupFileLocks.php',
|
||||
'OCA\\Files\\BackgroundJob\\DeleteExpiredOpenLocalEditor' => $baseDir . '/../lib/BackgroundJob/DeleteExpiredOpenLocalEditor.php',
|
||||
'OCA\\Files\\BackgroundJob\\DeleteOrphanedItems' => $baseDir . '/../lib/BackgroundJob/DeleteOrphanedItems.php',
|
||||
'OCA\\Files\\BackgroundJob\\FileChunkCleanupJob' => $baseDir . '/../lib/BackgroundJob/FileChunkCleanupJob.php',
|
||||
'OCA\\Files\\BackgroundJob\\ScanFiles' => $baseDir . '/../lib/BackgroundJob/ScanFiles.php',
|
||||
'OCA\\Files\\BackgroundJob\\TransferOwnership' => $baseDir . '/../lib/BackgroundJob/TransferOwnership.php',
|
||||
'OCA\\Files\\Capabilities' => $baseDir . '/../lib/Capabilities.php',
|
||||
|
|
|
@ -37,6 +37,7 @@ class ComposerStaticInitFiles
|
|||
'OCA\\Files\\BackgroundJob\\CleanupFileLocks' => __DIR__ . '/..' . '/../lib/BackgroundJob/CleanupFileLocks.php',
|
||||
'OCA\\Files\\BackgroundJob\\DeleteExpiredOpenLocalEditor' => __DIR__ . '/..' . '/../lib/BackgroundJob/DeleteExpiredOpenLocalEditor.php',
|
||||
'OCA\\Files\\BackgroundJob\\DeleteOrphanedItems' => __DIR__ . '/..' . '/../lib/BackgroundJob/DeleteOrphanedItems.php',
|
||||
'OCA\\Files\\BackgroundJob\\FileChunkCleanupJob' => __DIR__ . '/..' . '/../lib/BackgroundJob/FileChunkCleanupJob.php',
|
||||
'OCA\\Files\\BackgroundJob\\ScanFiles' => __DIR__ . '/..' . '/../lib/BackgroundJob/ScanFiles.php',
|
||||
'OCA\\Files\\BackgroundJob\\TransferOwnership' => __DIR__ . '/..' . '/../lib/BackgroundJob/TransferOwnership.php',
|
||||
'OCA\\Files\\Capabilities' => __DIR__ . '/..' . '/../lib/Capabilities.php',
|
||||
|
|
|
@ -3,7 +3,7 @@
|
|||
'name' => '__root__',
|
||||
'pretty_version' => 'dev-master',
|
||||
'version' => 'dev-master',
|
||||
'reference' => 'd51429a47232bbf46a2be832ecfa711f102da802',
|
||||
'reference' => '3e452cfe8d80995d1657c617f887a9ee422e6ab1',
|
||||
'type' => 'library',
|
||||
'install_path' => __DIR__ . '/../',
|
||||
'aliases' => array(),
|
||||
|
@ -13,7 +13,7 @@
|
|||
'__root__' => array(
|
||||
'pretty_version' => 'dev-master',
|
||||
'version' => 'dev-master',
|
||||
'reference' => 'd51429a47232bbf46a2be832ecfa711f102da802',
|
||||
'reference' => '3e452cfe8d80995d1657c617f887a9ee422e6ab1',
|
||||
'type' => 'library',
|
||||
'install_path' => __DIR__ . '/../',
|
||||
'aliases' => array(),
|
||||
|
|
|
@ -0,0 +1,62 @@
|
|||
<?php
|
||||
declare(strict_types=1);
|
||||
|
||||
/**
|
||||
* @copyright 2023 Anna Larch <anna.larch@gmx.net>
|
||||
*
|
||||
* @author Anna Larch <anna.larch@gmx.net>
|
||||
*
|
||||
* @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 OCA\Files\BackgroundJob;
|
||||
|
||||
use OC\Cache\File;
|
||||
use OCP\AppFramework\Utility\ITimeFactory;
|
||||
use OCP\BackgroundJob\Job;
|
||||
use OCP\BackgroundJob\TimedJob;
|
||||
use OCP\Files\IRootFolder;
|
||||
use OCP\IUser;
|
||||
use OCP\IUserManager;
|
||||
use Psr\Log\LoggerInterface;
|
||||
|
||||
class FileChunkCleanupJob extends TimedJob {
|
||||
private IUserManager $userManager;
|
||||
private IRootFolder $rootFolder;
|
||||
private LoggerInterface $logger;
|
||||
|
||||
public function __construct(IUserManager $userManager, IRootFolder $rootFolder, LoggerInterface $logger, ITimeFactory $timeFactory) {
|
||||
parent::__construct($timeFactory);
|
||||
$this->setInterval(3600*24);
|
||||
$this->setTimeSensitivity(Job::TIME_INSENSITIVE);
|
||||
$this->userManager = $userManager;
|
||||
$this->rootFolder = $rootFolder;
|
||||
$this->logger = $logger;
|
||||
}
|
||||
|
||||
/**
|
||||
* This job cleans up all backups except the latest 3 from the updaters backup directory
|
||||
*/
|
||||
public function run($argument): void {
|
||||
$this->userManager->callForSeenUsers(function (IUser $user): void {
|
||||
$this->logger->debug('Running chunk cleanup job for user '. $user->getUID());
|
||||
$fileCache = new File();
|
||||
$fileCache->setUpStorage($user->getUID());
|
||||
$fileCache->gc();
|
||||
$this->logger->debug('Finished running chunk cleanup job for user '. $user->getUID());
|
||||
});
|
||||
}
|
||||
}
|
17
lib/base.php
17
lib/base.php
|
@ -855,23 +855,6 @@ class OC {
|
|||
$throttler = Server::get(\OC\Security\Bruteforce\Throttler::class);
|
||||
$throttler->resetDelay($request->getRemoteAddress(), 'login', ['user' => $uid]);
|
||||
}
|
||||
|
||||
try {
|
||||
$cache = new \OC\Cache\File();
|
||||
$cache->gc();
|
||||
} catch (\OC\ServerNotAvailableException $e) {
|
||||
// not a GC exception, pass it on
|
||||
throw $e;
|
||||
} catch (\OC\ForbiddenException $e) {
|
||||
// filesystem blocked for this request, ignore
|
||||
} catch (\Exception $e) {
|
||||
// a GC exception should not prevent users from using OC,
|
||||
// so log the exception
|
||||
Server::get(LoggerInterface::class)->warning('Exception when running cache gc.', [
|
||||
'app' => 'core',
|
||||
'exception' => $e,
|
||||
]);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1620,7 +1620,6 @@ return array(
|
|||
'OC_App' => $baseDir . '/lib/private/legacy/OC_App.php',
|
||||
'OC_Defaults' => $baseDir . '/lib/private/legacy/OC_Defaults.php',
|
||||
'OC_EventSource' => $baseDir . '/lib/private/legacy/OC_EventSource.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',
|
||||
|
|
|
@ -1653,7 +1653,6 @@ class ComposerStaticInit749170dad3f5e7f9ca158f5a9f04f6a2
|
|||
'OC_App' => __DIR__ . '/../../..' . '/lib/private/legacy/OC_App.php',
|
||||
'OC_Defaults' => __DIR__ . '/../../..' . '/lib/private/legacy/OC_Defaults.php',
|
||||
'OC_EventSource' => __DIR__ . '/../../..' . '/lib/private/legacy/OC_EventSource.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',
|
||||
|
|
|
@ -35,10 +35,26 @@ use OCP\ICache;
|
|||
use OCP\Security\ISecureRandom;
|
||||
use Psr\Log\LoggerInterface;
|
||||
|
||||
/**
|
||||
* @deprecated 26.0.0
|
||||
*/
|
||||
class File implements ICache {
|
||||
/** @var View */
|
||||
protected $storage;
|
||||
|
||||
/**
|
||||
* Set the cache storage for a user
|
||||
*/
|
||||
public function setUpStorage(string $userId) {
|
||||
Filesystem::initMountPoints($userId);
|
||||
$rootView = new View();
|
||||
if (!$rootView->file_exists('/' . $userId . '/cache')) {
|
||||
$rootView->mkdir('/' . $userId . '/cache');
|
||||
}
|
||||
$this->storage = new View('/' . $userId . '/cache');
|
||||
return $this->storage;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the cache storage for the logged in user
|
||||
*
|
||||
|
@ -51,14 +67,8 @@ class File implements ICache {
|
|||
return $this->storage;
|
||||
}
|
||||
if (\OC::$server->getUserSession()->isLoggedIn()) {
|
||||
$rootView = new View();
|
||||
$user = \OC::$server->getUserSession()->getUser();
|
||||
Filesystem::initMountPoints($user->getUID());
|
||||
if (!$rootView->file_exists('/' . $user->getUID() . '/cache')) {
|
||||
$rootView->mkdir('/' . $user->getUID() . '/cache');
|
||||
}
|
||||
$this->storage = new View('/' . $user->getUID() . '/cache');
|
||||
return $this->storage;
|
||||
return $this->setUpStorage($user->getUID());
|
||||
} else {
|
||||
\OC::$server->get(LoggerInterface::class)->error('Can\'t get cache storage, user not logged in', ['app' => 'core']);
|
||||
throw new \OC\ForbiddenException('Can\t get cache storage, user not logged in');
|
||||
|
|
|
@ -708,6 +708,7 @@ class Server extends ServerContainer implements IServerContainer {
|
|||
$this->registerService(ICache::class, function ($c) {
|
||||
return new Cache\File();
|
||||
});
|
||||
|
||||
/** @deprecated 19.0.0 */
|
||||
$this->registerDeprecatedAlias('UserCache', ICache::class);
|
||||
|
||||
|
|
|
@ -1,184 +0,0 @@
|
|||
<?php
|
||||
/**
|
||||
* @copyright Copyright (c) 2016, ownCloud, Inc.
|
||||
*
|
||||
* @author Bart Visscher <bartv@thisnet.nl>
|
||||
* @author Christoph Wurst <christoph@winzerhof-wurst.at>
|
||||
* @author Felix Moeller <mail@felixmoeller.de>
|
||||
* @author Jörn Friedrich Dreyer <jfd@butonic.de>
|
||||
* @author Morris Jobke <hey@morrisjobke.de>
|
||||
* @author Robin Appelman <robin@icewind.nl>
|
||||
* @author Roeland Jago Douma <roeland@famdouma.nl>
|
||||
* @author Thomas Müller <thomas.mueller@tmit.eu>
|
||||
* @author Thomas Tanghus <thomas@tanghus.net>
|
||||
* @author Vincent Petry <vincent@nextcloud.com>
|
||||
*
|
||||
* @license AGPL-3.0
|
||||
*
|
||||
* This code is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License, version 3,
|
||||
* as published by the Free Software Foundation.
|
||||
*
|
||||
* 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, version 3,
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>
|
||||
*
|
||||
*/
|
||||
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()->getSystemValue('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,72 +0,0 @@
|
|||
<?php
|
||||
/**
|
||||
* @author Roeland Jago Douma <rullzer@owncloud.com>
|
||||
*
|
||||
* @copyright Copyright (c) 2016, ownCloud, Inc.
|
||||
* @license AGPL-3.0
|
||||
*
|
||||
* This code is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License, version 3,
|
||||
* as published by the Free Software Foundation.
|
||||
*
|
||||
* 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, version 3,
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>
|
||||
*
|
||||
*/
|
||||
|
||||
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());
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue