Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions apps/dav/lib/Capabilities.php
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ public function getCapabilities() {
return [
'dav' => [
'chunking' => '1.0',
'zsync' => '1.0',
]
];
}
Expand Down
3 changes: 2 additions & 1 deletion apps/dav/lib/Connector/Sabre/Directory.php
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@
use OCA\DAV\Connector\Sabre\Exception\Forbidden;
use OCA\DAV\Connector\Sabre\Exception\InvalidPath;
use OCA\DAV\Upload\FutureFile;
use OCA\DAV\Upload\FutureFileZsync;
use OCP\Files\ForbiddenException;
use OCP\Files\InvalidPathException;
use OCP\Files\StorageNotAvailableException;
Expand Down Expand Up @@ -147,7 +148,7 @@ public function createFile($name, $data = null) {
) {
throw new SabreForbidden();
}
} else if (FutureFile::isFutureFile()) {
} else if (FutureFile::isFutureFile() or FutureFileZsync::isFutureFile()) {

@mrow4a mrow4a Nov 20, 2017

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hmm, it is just style thing probably, but I think in the rest of the codebase we use ||. @DeepDiver1975 .

About logic - makes sense as it is a similar scenario as in chunking.

// Future file (chunked upload) requires fileinfo
$info = $this->fileView->getFileInfo($this->path . '/' . $name);
} else {
Expand Down
147 changes: 147 additions & 0 deletions apps/dav/lib/Files/ZsyncPlugin.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,147 @@
<?php
/*
* Copyright (C) by Ahmed Ammar <ahmed.a.ammar@gmail.com>
*
* Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated
* documentation files (the "Software"), to deal in the Software without restriction, including without limitation the
* rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to
* permit persons to whom the Software is furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all copies or substantial portions of the
* Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
* WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
* COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
* OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*
*/
namespace OCA\DAV\Files;

use OC\AppFramework\Http;
use OCP\Files\NotFoundException;
use OCP\Files\NotPermittedException;
use Sabre\DAV\Server;
use Sabre\DAV\ServerPlugin;
use Sabre\DAV\PropFind;
use Sabre\HTTP\RequestInterface;
use Sabre\HTTP\ResponseInterface;
use OC\Files\View;
use \Exception;

class ZsyncPlugin extends ServerPlugin {

// namespace
const ZSYNC_PROPERTYNAME = '{http://owncloud.org/ns}zsync';

/** @var OC\Files\View */
private $view;

public function __construct(View $view) {
$this->view = $view;
$this->view->mkdir('files_zsync');
}

/**
* Initializes the plugin and registers event handlers
*
* @param Server $server
* @return void
*/
function initialize(Server $server) {
$server->on('method:GET', [$this, 'httpGet'], 90);
$server->on('method:DELETE', [$this, 'httpDelete'], 90);
$server->on('propFind', [$this, 'handleGetProperties']);
}

/**
* Intercepts GET requests on file urls ending with ?zsync.
*
* @param RequestInterface $request
* @param ResponseInterface $response
*/
function httpGet(RequestInterface $request, ResponseInterface $response) {

$queryParams = $request->getQueryParameters();
if (!array_key_exists('zsync', $queryParams)) {
return true;
}

$path = ltrim($request->getPath(), '/');
/* remove files/$user */
$path = implode('/', array_slice(explode('/', $path), 2));
/* If basefile not found this is an error */
if (!$this->view->file_exists('files/'.$path)) {
$response->setStatus(Http::STATUS_NOT_FOUND);
return false;
}

$info = $this->view->getFileInfo('files/'.$path);
$zsyncMetadataFile = 'files_zsync/'.$info->getId();
if ($this->view->file_exists($zsyncMetadataFile)) {
$content = $this->view->file_get_contents($zsyncMetadataFile);
$response->setHeader('OC-ETag', $info->getEtag());
$response->setStatus(Http::STATUS_OK);
$response->setBody($content);
} else {
$response->setStatus(Http::STATUS_NOT_FOUND);
}

return false;
}

/**
* Intercepts DELETE requests on file urls ending with ?zsync.
*
* @param RequestInterface $request
* @param ResponseInterface $response
*/
function httpDelete(RequestInterface $request, ResponseInterface $response) {

@mrow4a mrow4a Feb 22, 2018

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would love to see at some point some relations here.. without integrity contraints it can be a mess and corruption on corruption. I need to test what happens when I terminate script in very bad moment.


$queryParams = $request->getQueryParameters();
if (!array_key_exists('zsync', $queryParams)) {
return true;
}

$path = ltrim($request->getPath(), '/');
/* remove files/$user */
$path = implode('/', array_slice(explode('/', $path), 2));
/* If basefile not found this is an error */
if (!$this->view->file_exists('files/'.$path)) {
$response->setStatus(Http::STATUS_NOT_FOUND);
return false;
}

$info = $this->view->getFileInfo('files/'.$path);
$zsyncMetadataFile = 'files_zsync/'.$info->getId();
if ($this->view->file_exists($zsyncMetadataFile)) {
$this->view->unlink($zsyncMetadataFile);
$response->setStatus(Http::STATUS_OK);
} else {
$response->setStatus(Http::STATUS_NOT_FOUND);
}

return false;
}

/**
* Adds zsync property if metadata exists
*
* @param PropFind $propFind
* @param \Sabre\DAV\INode $node
* @return void
*/
public function handleGetProperties(PropFind $propFind, \Sabre\DAV\INode $node) {
if ($node instanceof \OCA\DAV\Connector\Sabre\File) {
if (!$this->view->is_file('files/'.$node->getPath()))
return;
$info = $this->view->getFileInfo('files/'.$node->getPath());
$zsyncMetadataFile = 'files_zsync/'.$info->getId();
if ($this->view->file_exists($zsyncMetadataFile)) {
$propFind->handle(self::ZSYNC_PROPERTYNAME, function() use ($node) {
return 'true';
});
}
}
}
}
81 changes: 81 additions & 0 deletions apps/dav/lib/HookManager.php
Original file line number Diff line number Diff line change
Expand Up @@ -27,8 +27,10 @@
use OCA\DAV\CardDAV\SyncService;
use OCP\IL10N;
use OCP\IUser;
use OCP\User;
use OCP\IUserManager;
use OCP\Util;
use OC\Files\View;

class HookManager {

Expand Down Expand Up @@ -85,6 +87,23 @@ public function setup() {
'changeUser',
$this,
'changeUser');

Util::connectHook('OC_Filesystem',
'post_copy',
$this,
'copyZsyncMetadata');
Util::connectHook('OC_Filesystem',
'write',
$this,
'deleteZsyncMetadata');

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

wait wait.. ok this PR needs hard performance review...

Util::connectHook('OC_Filesystem',
'delete',
$this,
'deleteZsyncMetadata');
Util::connectHook('\OCP\Versions',
'rollback',
$this,
'deleteZsyncMetadata');
}

public function postCreateUser($params) {
Expand Down Expand Up @@ -144,4 +163,66 @@ public function firstLogin(IUser $user = null) {
}
}
}

public function deleteZsyncMetadata($params) {
$view = new View('/'.User::getUser());
$path = $params[\OC\Files\Filesystem::signal_param_path];
$path = 'files/' . ltrim($path, '/');

/* if a file then just delete zsync metadata for file */
if ($view->is_file($path)) {
$info = $view->getFileInfo($path);
if ($view->file_exists('files_zsync/'.$info->getId()))
$view->unlink('files_zsync/'.$info->getId());
} else if ($view->is_dir($path)) {
/* if a folder then iteratively delete all zsync metadata for all files in folder, including subdirs */
$array[] = $path;
while (count($array)) {
$current = array_pop($array);
$handle = $view->opendir($current);
while (($entry = readdir($handle)) !== false) {
if($entry[0]!='.' and $view->is_dir($current.'/'.$entry)) {
$array[] = $current.'/'.$entry;
} else if ($view->is_file($current.'/'.$entry)) {
$info = $view->getFileInfo($current.'/'.$entry);
if ($view->file_exists('files_zsync/'.$info->getId()))
$view->unlink('files_zsync/'.$info->getId());
}
}
}
}
}

public function copyZsyncMetadata($params) {
$view = new View('/'.User::getUser());
$from = $params[\OC\Files\Filesystem::signal_param_oldpath];
$from = 'files/' . ltrim($from, '/');
$to = $params[\OC\Files\Filesystem::signal_param_newpath];
$to = 'files/' . ltrim($to, '/');

/* if a file then just copy zsync metadata for file */
if ($view->is_file($from)) {
$info_from = $view->getFileInfo($from);
$info_to = $view->getFileInfo($to);
if ($view->file_exists('files_zsync/'.$info_from->getId()))
$view->copy('files_zsync/'.$info_from->getId(), 'files_zsync/'.$info_to->getId());
} else if ($view->is_dir($from)) {
/* if a folder then iteratively copy all zsync metadata for all files in folder, including subdirs */
$array[] = [$from, $to];
while (count($array)) {
list($from_current, $to_current) = array_pop($array);
$handle = $view->opendir($from_current);
while (($entry = readdir($handle)) !== false) {
if($entry[0]!='.' and $view->is_dir($from_current.'/'.$entry)) {
$array[] = [$from_current.'/'.$entry, $to_current.'/'.$entry];
} else if ($view->is_file($from_current.'/'.$entry)) {
$info_from = $view->getFileInfo($from_current.'/'.$entry);
$info_to = $view->getFileInfo($to_current.'/'.$entry);
if ($view->file_exists('files_zsync/'.$info_from->getId()))
$view->copy('files_zsync/'.$info_from->getId(), 'files_zsync/'.$info_to->getId());
}
}
}
}
}
}
13 changes: 12 additions & 1 deletion apps/dav/lib/Server.php
Original file line number Diff line number Diff line change
Expand Up @@ -50,8 +50,10 @@
use OCA\DAV\DAV\PublicAuth;
use OCA\DAV\Files\BrowserErrorPagePlugin;
use OCA\DAV\Files\PreviewPlugin;
use OCA\DAV\Files\ZsyncPlugin;
use OCA\DAV\SystemTag\SystemTagPlugin;
use OCA\DAV\Upload\ChunkingPlugin;
use OCA\DAV\Upload\ChunkingPluginZsync;
use OCP\IRequest;
use OCP\SabrePluginEvent;
use Sabre\CardDAV\VCFExportPlugin;
Expand Down Expand Up @@ -191,7 +193,16 @@ public function __construct(IRequest $request, $baseUri) {
$userSession = \OC::$server->getUserSession();
$user = $userSession->getUser();
if (!is_null($user)) {
$view = Filesystem::getView();
$userHomeView = new \OC\Files\View('/'.$user->getUID());
$this->server->addPlugin(
new ChunkingPluginZsync($userHomeView)
);

$this->server->addPlugin(
new ZsyncPlugin($userHomeView)
);

$view = \OC\Files\Filesystem::getView();
$this->server->addPlugin(
new FilesPlugin(
$this->server->tree,
Expand Down
21 changes: 12 additions & 9 deletions apps/dav/lib/Upload/AssemblyStream.php
Original file line number Diff line number Diff line change
Expand Up @@ -37,22 +37,22 @@
class AssemblyStream implements \Icewind\Streams\File {

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

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

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

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

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

/** @var resource */
private $currentStream = null;
protected $currentStream = null;

/**
* @param string $path
Expand All @@ -78,6 +78,9 @@ public function stream_open($path, $mode, $options, &$opened_path) {
foreach($this->nodes as $node) {
$size = $node->getSize();
$name = $node->getName();
// ignore .zsync metadata file
if (!strcmp($name,".zsync"))
continue;
$this->sortedNodes[$name] = ['node' => $node, 'start' => $start, 'end' => $start + $size];
$start += $size;
$this->size = $start;
Expand Down Expand Up @@ -233,7 +236,7 @@ protected function loadContext($name) {
*
* @throws \BadMethodCallException
*/
public static function wrap(array $nodes) {
public static function wrap(array $nodes, IFile $backingFile = null, $length = null) {
$context = stream_context_create([
'assembly' => [
'nodes' => $nodes]
Expand All @@ -253,7 +256,7 @@ public static function wrap(array $nodes) {
* @param $pos
* @return IFile | null
*/
private function getNodeForPosition($pos) {
protected function getNodeForPosition($pos) {
foreach($this->sortedNodes as $node) {
if ($pos >= $node['start'] && $pos < $node['end']) {
return [$node['node'], $pos - $node['start']];
Expand All @@ -266,7 +269,7 @@ private function getNodeForPosition($pos) {
* @param IFile $node
* @return resource
*/
private function getStream(IFile $node) {
protected function getStream(IFile $node) {
$data = $node->get();
if (is_resource($data)) {
return $data;
Expand Down
Loading