Skip to content

Commit

Permalink
introduction of interface IChunkHandler and a very first stupid imple…
Browse files Browse the repository at this point in the history
…mentation reusing the existing chunking mechanism
  • Loading branch information
DeepDiver1975 committed Nov 13, 2014
1 parent 5ca869c commit a663692
Show file tree
Hide file tree
Showing 7 changed files with 147 additions and 47 deletions.
35 changes: 21 additions & 14 deletions lib/private/connector/sabre/file.php
Original file line number Diff line number Diff line change
Expand Up @@ -238,45 +238,52 @@ private function createFileChunked($data)
if (empty($info)) {
throw new \Sabre\DAV\Exception\NotImplemented();
}
$chunk_handler = new OC_FileChunking($info);
$bytesWritten = $chunk_handler->store($info['index'], $data);

// we first assembly the target file as a part file
$targetPath = $path . '/' . $info['name'];
if (isset($_SERVER['CONTENT_LENGTH'])) {
$expected = $_SERVER['CONTENT_LENGTH'];
} else {
$expected = -1;
}
$partFilePath = $path . '/' . $info['name'] . '.ocTransferId' . $info['transferid'] . '.part';
/** @var \OC\Files\Storage\Storage $storage */
list($storage,) = $this->fileView->resolvePath($partFilePath);
$storeData = $storage->getChunkHandler()->storeChunk($partFilePath, $info['index'], $info['chunkcount'], $expected, $data, $info['transferid']);
$bytesWritten = $storeData['bytesWritten'];

//detect aborted upload
if (isset ($_SERVER['REQUEST_METHOD']) && $_SERVER['REQUEST_METHOD'] === 'PUT' ) {
if (isset($_SERVER['CONTENT_LENGTH'])) {
$expected = $_SERVER['CONTENT_LENGTH'];
if ($bytesWritten != $expected) {
$chunk_handler->remove($info['index']);
throw new \Sabre\DAV\Exception\BadRequest(
'expected filesize ' . $expected . ' got ' . $bytesWritten);
}
}
}

if ($chunk_handler->isComplete()) {
if ($storeData['complete']) {

try {
// we first assembly the target file as a part file
$partFile = $path . '/' . $info['name'] . '.ocTransferId' . $info['transferid'] . '.part';
$chunk_handler->file_assemble($partFile);

// here is the final atomic rename
$targetPath = $path . '/' . $info['name'];
$renameOkay = $this->fileView->rename($partFile, $targetPath);
$fileExists = $this->fileView->file_exists($targetPath);
// TODO: will this properly trigger all hooks
$renameOkay = $storage->rename('/files' . $partFilePath, '/files' . $targetPath);
$fileExists = $storage->file_exists('/files' . $targetPath);
if ($renameOkay === false || $fileExists === false) {
\OC_Log::write('webdav', '\OC\Files\Filesystem::rename() failed', \OC_Log::ERROR);
\OC::$server->getLogger()->error('\OC\Files\Filesystem::rename() failed', array('app'=>'webdav'));
// only delete if an error occurred and the target file was already created
if ($fileExists) {
$this->fileView->unlink($targetPath);
$storage->unlink('/files' . $targetPath);
}
throw new \Sabre\DAV\Exception('Could not rename part file assembled from chunks');
}

// allow sync clients to send the mtime along in a header
$mtime = OC_Request::hasModificationTime();
if ($mtime !== false) {
if($this->fileView->touch($targetPath, $mtime)) {
// TODO: will this update the cache properly - e.g. smb where we cannot change the mtime ???
if($storage->touch('/files' . $targetPath, $mtime)) {
header('X-OC-MTime: accepted');
}
}
Expand Down
37 changes: 4 additions & 33 deletions lib/private/filechunking.php
Original file line number Diff line number Diff line change
Expand Up @@ -25,9 +25,9 @@ public function __construct($info) {

public function getPrefix() {
$name = $this->info['name'];
$transferid = $this->info['transferid'];
$transferId = $this->info['transferid'];

return $name.'-chunking-'.$transferid.'-';
return $name.'-chunking-'.$transferId.'-';
}

protected function getCache() {
Expand Down Expand Up @@ -72,7 +72,7 @@ public function isComplete() {
*
* @return integer assembled file size
*
* @throws \OC\InsufficientStorageException when file could not be fully
* @throws \OCP\Files\NotEnoughSpaceException when file could not be fully
* assembled due to lack of free space
*/
public function assemble($f) {
Expand Down Expand Up @@ -124,35 +124,6 @@ public function remove($index) {
$cache->remove($prefix.$index);
}

public function signature_split($orgfile, $input) {
$info = unpack('n', fread($input, 2));
$blocksize = $info[1];
$this->info['transferid'] = mt_rand();
$count = 0;
$needed = array();
$cache = $this->getCache();
$prefix = $this->getPrefix();
while (!feof($orgfile)) {
$new_md5 = fread($input, 16);
if (feof($input)) {
break;
}
$data = fread($orgfile, $blocksize);
$org_md5 = md5($data, true);
if ($org_md5 == $new_md5) {
$cache->set($prefix.$count, $data);
} else {
$needed[] = $count;
}
$count++;
}
return array(
'transferid' => $this->info['transferid'],
'needed' => $needed,
'count' => $count,
);
}

/**
* Assembles the chunks into the file specified by the path.
* Also triggers the relevant hooks and proxies.
Expand All @@ -161,7 +132,7 @@ public function signature_split($orgfile, $input) {
*
* @return boolean assembled file size or false if file could not be created
*
* @throws \OC\InsufficientStorageException when file could not be fully
* @throws \OCP\Files\NotEnoughSpaceException when file could not be fully
* assembled due to lack of free space
*/
public function file_assemble($path) {
Expand Down
62 changes: 62 additions & 0 deletions lib/private/files/cachingchunkhandler.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
<?php
/**
* @author Thomas Müller
* @copyright 2014 Thomas Müller [email protected]
*
* This file is licensed under the Affero General Public License version 3 or
* later.
* See the COPYING-README file.
*/

namespace OC\Files;

use OCP\Files\Storage;

class CachingChunkHandler implements \OCP\Files\IChunkHandler {

/**
* @var Storage
*/
private $storage;

public function __construct(Storage $storage) {
$this->storage = $storage;
}

/**
* Write a chunk to a give file.
*
* @param string $fileName
* @param int $index
* @param int $numberOfChunk
* @param int $chunkSize
* @param string $data
* @return array
*/
function storeChunk($fileName, $index, $numberOfChunk, $chunkSize, $data, $transferId) {
$info = array(
'name' => $transferId,
'transferid' => $transferId,
'chunkcount' => $numberOfChunk,
''
);
$chunkHandler = new \OC_FileChunking($info);
$bytesWritten = $chunkHandler->store($index, $data);
if ($bytesWritten != $chunkSize) {
$chunkHandler->remove($index);
}
$complete = false;
if ($chunkHandler->isComplete()) {
$complete = true;
$f = $this->storage->fopen("/files" . $fileName, 'w');
$chunkHandler->assemble($f);
fclose($f);
}

return array(
'complete' => $complete,
'bytesWritten' => $bytesWritten,
'actualSize' => $chunkHandler->getCurrentSize()
);
}
}
12 changes: 12 additions & 0 deletions lib/private/files/storage/common.php
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,10 @@

namespace OC\Files\Storage;

use OC\Files\CachingChunkHandler;
use OC\Files\Filesystem;
use OC\Files\Cache\Watcher;
use OCP\Files\IChunkHandler;

/**
* Storage backend class for providing common filesystem operation methods
Expand Down Expand Up @@ -437,4 +439,14 @@ protected function removeCachedFile($path) {
public function instanceOfStorage($class) {
return is_a($this, $class);
}

/**
* Returns the storage specific chunk handler
*
* @return IChunkHandler
*/
public function getChunkHandler() {
return new CachingChunkHandler($this);
}

}
11 changes: 11 additions & 0 deletions lib/private/files/storage/wrapper/wrapper.php
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@

namespace OC\Files\Storage\Wrapper;

use OCP\Files\IChunkHandler;

class Wrapper implements \OC\Files\Storage\Storage {
/**
* @var \OC\Files\Storage\Storage $storage
Expand Down Expand Up @@ -465,4 +467,13 @@ public function instanceOfStorage($class) {
public function __call($method, $args) {
return call_user_func_array(array($this->storage, $method), $args);
}

/**
* Returns the storage specific chunk handler
*
* @return IChunkHandler
*/
public function getChunkHandler() {
return $this->storage->getChunkHandler();
}
}
30 changes: 30 additions & 0 deletions lib/public/files/ichunkhandler.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
<?php
/**
* @author Thomas Müller
* @copyright 2014 Thomas Müller [email protected]
*
* This file is licensed under the Affero General Public License version 3 or
* later.
* See the COPYING-README file.
*/
namespace OCP\Files;

interface IChunkHandler {

/**
* Write a chunk to a give file. The returned array will holds following properties:
* - bytesWritten - the number of bytes written with the current call
* - complete - indicator if the file si complete and has been uploaded/stored in the storage,
* - actualSize - current size of the file - equivalent to the sum of all stored chunks
*
* @param string $fileName
* @param int $index
* @param int $numberOfChunk
* @param int $chunkSize the size of the current chunk
* @param resource $data
* @param string $transferId
* @return array
*/
public function storeChunk($fileName, $index, $numberOfChunk, $chunkSize, $data, $transferId);

}
7 changes: 7 additions & 0 deletions lib/public/files/storage.php
Original file line number Diff line number Diff line change
Expand Up @@ -335,6 +335,13 @@ public function isLocal();
* @return bool
*/
public function instanceOfStorage($class);

/**
* Returns the storage specific chunk handler
*
* @return IChunkHandler
*/
public function getChunkHandler();
}

interface IHomeStorage {
Expand Down

0 comments on commit a663692

Please sign in to comment.