Skip to content

Commit

Permalink
Initial implementation of the new chunked upload - as specified in ht…
Browse files Browse the repository at this point in the history
  • Loading branch information
DeepDiver1975 committed Oct 28, 2015
1 parent 8ad3df9 commit d137941
Show file tree
Hide file tree
Showing 7 changed files with 514 additions and 0 deletions.
3 changes: 3 additions & 0 deletions apps/dav/lib/rootcollection.php
Original file line number Diff line number Diff line change
Expand Up @@ -22,10 +22,13 @@ public function __construct() {
$principalCollection->disableListing = $disableListing;
$filesCollection = new Files\RootCollection($principalBackend);
$filesCollection->disableListing = $disableListing;
$uploadCollection = new Upload\RootCollection($principalBackend);
$uploadCollection->disableListing = $disableListing;

$children = [
$principalCollection,
$filesCollection,
$uploadCollection,
];

parent::__construct('root', $children);
Expand Down
223 changes: 223 additions & 0 deletions apps/dav/lib/upload/assemblystream.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,223 @@
<?php

namespace OCA\DAV\Upload;

use Sabre\DAV\IFile;

class AssemblyStream implements \Icewind\Streams\File {

/** @var resource */
private $context;

/** @var IFile[] */
private $nodes;

/** @var int */
private $pos = 0;

/** @var array */
private $sortedNodes;

/** @var int */
private $size;

/**
* @param string $path
* @param string $mode
* @param int $options
* @param string &$opened_path
* @return bool
*/
public function stream_open($path, $mode, $options, &$opened_path) {
$this->loadContext('assembly');

// sort the nodes
$nodes = $this->nodes;
@usort($nodes, function(IFile $a, IFile $b) {
return strcmp($a->getName(), $b->getName());
});
$this->nodes = $nodes;

// build additional information
$this->sortedNodes = [];
$start = 0;
foreach($this->nodes as $node) {
$size = $node->getSize();
$name = $node->getName();
$this->sortedNodes[$name] = ['node' => $node, 'start' => $start, 'end' => $start + $size];
$start += $size;
$this->size = $start;
}
return true;
}

/**
* @param string $offset
* @param int $whence
* @return bool
*/
public function stream_seek($offset, $whence = SEEK_SET) {
return false;
}

/**
* @return int
*/
public function stream_tell() {
return $this->pos;
}

/**
* @param int $count
* @return string
*/
public function stream_read($count) {

list($node, $posInNode) = $this->getNodeForPosition($this->pos);
if (is_null($node)) {
return null;
}
$stream = $this->getStream($node);

fseek($stream, $posInNode);
$data = fread($stream, $count);
$read = strlen($data);

// update position
$this->pos += $read;
return $data;
}

/**
* @param string $data
* @return int
*/
public function stream_write($data) {
return false;
}

/**
* @param int $option
* @param int $arg1
* @param int $arg2
* @return bool
*/
public function stream_set_option($option, $arg1, $arg2) {
return false;
}

/**
* @param int $size
* @return bool
*/
public function stream_truncate($size) {
return false;
}

/**
* @return array
*/
public function stream_stat() {
return [];
}

/**
* @param int $operation
* @return bool
*/
public function stream_lock($operation) {
return false;
}

/**
* @return bool
*/
public function stream_flush() {
return false;
}

/**
* @return bool
*/
public function stream_eof() {
return $this->pos >= $this->size;
}

/**
* @return bool
*/
public function stream_close() {
}


/**
* Load the source from the stream context and return the context options
*
* @param string $name
* @return array
* @throws \Exception
*/
protected function loadContext($name) {
$context = stream_context_get_options($this->context);
if (isset($context[$name])) {
$context = $context[$name];
} else {
throw new \BadMethodCallException('Invalid context, "' . $name . '" options not set');
}
if (isset($context['nodes']) and is_array($context['nodes'])) {
$this->nodes = $context['nodes'];
} else {
throw new \BadMethodCallException('Invalid context, nodes not set');
}
return $context;
}

/**
* @param IFile[] $nodes
* @return resource
*
* @throws \BadMethodCallException
*/
public static function wrap(array $nodes) {
$context = stream_context_create([
'assembly' => [
'nodes' => $nodes]
]);
stream_wrapper_register('assembly', '\OCA\DAV\Upload\AssemblyStream');
try {
$wrapped = fopen('assembly://', 'r', null, $context);
} catch (\BadMethodCallException $e) {
stream_wrapper_unregister('assembly');
throw $e;
}
stream_wrapper_unregister('assembly');
return $wrapped;
}

/**
* @param $pos
* @return IFile | null
*/
private function getNodeForPosition($pos) {
foreach($this->sortedNodes as $node) {
if ($pos >= $node['start'] && $pos < $node['end']) {
return [$node['node'], $pos - $node['start']];
}
}
return null;
}

/**
* @param IFile $node
* @return mixed
*/
private function getStream(IFile $node) {
$data = $node->get();
if (is_resource($data)) {
return $data;
}

return fopen('data://text/plain,' . $data,'r');
}

}
81 changes: 81 additions & 0 deletions apps/dav/lib/upload/futurefile.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
<?php

namespace OCA\DAV\Upload;

use OCA\DAV\Connector\Sabre\Directory;
use Sabre\DAV\Exception\Forbidden;
use Sabre\DAV\IFile;

class FutureFile implements \Sabre\DAV\IFile {

function __construct(Directory $root, $name) {
$this->root = $root;
$this->name = $name;
}

function put($data) {
throw new Forbidden('Permission denied to put into this file');
}

function get() {
$nodes = $this->root->getChildren();
return \OCA\DAV\Upload\AssemblyStream::wrap($nodes);
}

function getContentType() {
return 'application/octet-stream';
}

function getETag() {
return $this->root->getETag();
}

function getSize() {
$children = $this->root->getChildren();
$sizes = array_map(function($node) {
/** @var IFile $node */
return $node->getSize();
}, $children);

return array_sum($sizes);
}

/**
* Deleted the current node
*
* @throws Forbidden
*/
function delete() {
throw new Forbidden('Permission denied to delete this file');
}

/**
* Returns the name of the node.
*
* This is used to generate the url.
*
* @return string
*/
function getName() {
return $this->name;
}

/**
* Renames the node
*
* @param string $name The new name
* @return void
*/
function setName($name) {
throw new Forbidden('Permission denied to rename this file');
}

/**
* Returns the last modification time, as a unix timestamp
*
* @return int
*/
function getLastModified() {
return $this->root->getLastModified();
}
}
28 changes: 28 additions & 0 deletions apps/dav/lib/upload/rootcollection.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
<?php

namespace OCA\DAV\Upload;

use Sabre\DAVACL\AbstractPrincipalCollection;
use Sabre\DAVACL\IPrincipal;

class RootCollection extends AbstractPrincipalCollection {

/**
* This method returns a node for a principal.
*
* The passed array contains principal information, and is guaranteed to
* at least contain a uri item. Other properties may or may not be
* supplied by the authentication backend.
*
* @param array $principalInfo
* @return IPrincipal
*/
function getChildForPrincipal(array $principalInfo) {
return new UploadHome($principalInfo);
}

function getName() {
return 'upload';
}

}
Loading

0 comments on commit d137941

Please sign in to comment.