Skip to content

Commit

Permalink
Merge pull request #29404 from ahmedammar/master
Browse files Browse the repository at this point in the history
Implementation of delta-sync support on server-side.
  • Loading branch information
DeepDiver1975 authored Mar 12, 2018
2 parents ec6e3af + 0e0ab09 commit a32d5dd
Show file tree
Hide file tree
Showing 16 changed files with 1,770 additions and 13 deletions.
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()) {
// 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) {

$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');
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

0 comments on commit a32d5dd

Please sign in to comment.