Skip to content

Commit

Permalink
Make ZipFolderPlugin actually work
Browse files Browse the repository at this point in the history
  • Loading branch information
Vincent Petry authored and DeepDiver1975 committed Jul 4, 2017
1 parent 4216d4e commit 36021fd
Show file tree
Hide file tree
Showing 2 changed files with 125 additions and 24 deletions.
126 changes: 108 additions & 18 deletions apps/dav/lib/ZipFolderPlugin.php
Original file line number Diff line number Diff line change
Expand Up @@ -22,16 +22,28 @@
namespace OCA\DAV;

use OCA\DAV\Connector\Sabre\Directory;
use Sabre\DAV\Server;
use Sabre\DAV\ServerPlugin;
use Sabre\HTTP\RequestInterface;
use Sabre\HTTP\ResponseInterface;
use OC\Streamer;
use Sabre\DAV\Exception\Locked;
use Sabre\DAV\Exception\Forbidden;
use OCP\Lock\ILockingProvider;

class ZipFolderPlugin extends ServerPlugin {

/** @var Server */
private $server;

/** @var string */
private $format;

/** @var bool */
private $ready;

/** @var Node */
private $node;

/**
* This initializes the plugin.
*
Expand All @@ -43,34 +55,112 @@ class ZipFolderPlugin extends ServerPlugin {
* @param Server $server
* @return void
*/
function initialize(Server $server) {
function initialize(\Sabre\DAV\Server $server) {
$this->server = $server;
$server->on('beforeMethod', array($this, 'downloadFolderAsZip'), 30);
$server->on('method:GET', array($this, 'httpGet'), 30);
$server->on('afterMethod:GET', array($this, 'doStream'), 30);
}

function downloadFolderAsZip(RequestInterface $request, ResponseInterface $response) {
if ($request->getMethod() !== 'GET') {
return;
}
function httpGet(RequestInterface $request, ResponseInterface $response) {
$path = $request->getPath();
if ($this->server->tree->nodeExists($path))
if (!$this->server->tree->nodeExists($path)) {
return;
}

$elements = pathinfo($path);
$ext = isset($elements['extension']) ? $elements['extension'] : null;
if (is_null($ext) || !in_array($ext, ['zip', 'tar'])) {
$node = $this->server->tree->getNodeForPath($path);
if (!$node instanceof Directory) {
return;
}

$pathToFolder = substr($path, 0, -4);
$node = $this->server->tree->getNodeForPath($pathToFolder);
$this->format = $this->getRequestedFormat($request);
$this->node = $node;

$response->setBody('');
$this->ready = true;
}

if (!$node instanceof Directory)
function doStream(RequestInterface $request, ResponseInterface $response) {
if (!$this->ready) {
return;
}

$this->getZipFolder($this->node, $this->format, $response);

// return false to prevent Sabre to send its own response
return false;
}

private function getRequestedFormat(RequestInterface $request) {
$acceptHeaders = explode(',', $request->getHeader('Accept'));
$formats = [];

foreach ($acceptHeaders as $acceptHeader) {
if (trim($acceptHeader) === 'application/x-tar') {
$formats['tar'] = true;
} else if (trim($acceptHeader) === 'application/zip') {
$formats['zip'] = true;
}
}

if (isset($formats['zip'])) {
return 'zip';
}
if (isset($formats['tar'])) {
return 'tar';
}
return null;
}

private function getZipFolder(Directory $node, $format, ResponseInterface $response) {
$view = \OC\Files\Filesystem::getView();
$dir = $node->getPath();

try {
$name = $node->getName();

$streamer = new Streamer($format);

// pre-lock all files
$this->lockFiles($view, $dir);

$streamer->sendHeaders($name);

//
// TODO: build a Tar and a Zip Streamer which use php streams
//
$response->setBody();
// TODO: inject
$executionTime = intval(\OC::$server->getIniWrapper()->getNumeric('max_execution_time'));
set_time_limit(0);

$streamer->addDirRecursive($node->getPath(), '', function($file) use ($view) {
// unlock file as soon as it was added
$view->unlockFile($file, ILockingProvider::LOCK_SHARED);
});
$streamer->finalize();
set_time_limit($executionTime);
$view->unlockFile($dir, ILockingProvider::LOCK_SHARED);
} catch (\OCP\Lock\LockedException $ex) {
$view->unlockFile($dir, ILockingProvider::LOCK_SHARED);
throw new Locked('File is currently busy, please try again later');
} catch (\OCP\Files\ForbiddenException $ex) {
$view->unlockFile($dir, ILockingProvider::LOCK_SHARED);
throw new Forbidden();
}
}

/**
* @param View $view
* @param string $dir
*/
private function lockFiles($view, $dir) {
$contents = $view->getDirectoryContent($dir);
$contents = array_map(function($fileInfo) use ($dir) {
/** @var \OCP\Files\FileInfo $fileInfo */
return $dir . '/' . $fileInfo->getName();
}, $contents);
foreach ($contents as $content) {
$view->lockFile($content, ILockingProvider::LOCK_SHARED);
if ($view->is_dir($content)) {
$this->lockFiles($view, $content);
}
}
}
}

23 changes: 17 additions & 6 deletions lib/private/Streamer.php
Original file line number Diff line number Diff line change
Expand Up @@ -32,18 +32,22 @@ class Streamer {

// streamer instance
private $streamerInstance;
public function __construct(){

public function __construct($format = null){
/** @var \OCP\IRequest */
$request = \OC::$server->getRequest();

if ($request->isUserAgent($this->preferTarFor)) {

if ($format === 'tar') {
$this->streamerInstance = new TarStreamer();
} else if ($format === 'zip') {
$this->streamerInstance = new ZipStreamer();
} else if ($request->isUserAgent($this->preferTarFor)) {
$this->streamerInstance = new TarStreamer();
} else {
$this->streamerInstance = new ZipStreamer(['zip64' => PHP_INT_SIZE !== 4]);
}
}

/**
* Send HTTP headers
* @param string $name
Expand All @@ -58,8 +62,9 @@ public function sendHeaders($name){
* Stream directory recursively
* @param string $dir
* @param string $internalDir
* @param callable $postCallback callback after processing every entry
*/
public function addDirRecursive($dir, $internalDir='') {
public function addDirRecursive($dir, $internalDir='', callable $postCallback) {
$dirname = basename($dir);
$rootDir = $internalDir . $dirname;
if (!empty($rootDir)) {
Expand All @@ -78,8 +83,14 @@ public function addDirRecursive($dir, $internalDir='') {
$fh = \OC\Files\Filesystem::fopen($file, 'r');
$this->addFileFromStream($fh, $internalDir . $filename, $filesize);
fclose($fh);
if (is_callable($postCallback)) {
$postCallback($file);
}
}elseif(\OC\Files\Filesystem::is_dir($file)) {
$this->addDirRecursive($file, $internalDir);
if (is_callable($postCallback)) {
$postCallback($file);
}
}
}
}
Expand Down

0 comments on commit 36021fd

Please sign in to comment.