summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--apps/dav/lib/Connector/Sabre/FilesPlugin.php51
-rw-r--r--apps/dav/lib/Connector/Sabre/IFrameTransportPlugin.php188
-rw-r--r--apps/dav/lib/Connector/Sabre/ServerFactory.php1
-rw-r--r--apps/dav/tests/unit/Connector/Sabre/FilesPluginTest.php85
-rw-r--r--apps/dav/tests/unit/Connector/Sabre/IFrameTransportPluginTest.php164
-rw-r--r--apps/files/ajax/upload.php283
-rw-r--r--apps/files/appinfo/routes.php12
-rw-r--r--apps/files/js/app.js1
-rw-r--r--apps/files/js/file-upload.js790
-rw-r--r--apps/files/js/filelist.js314
-rw-r--r--apps/files/js/files.js11
-rw-r--r--apps/files/templates/list.php3
-rw-r--r--apps/files/tests/js/fileUploadSpec.js141
-rw-r--r--apps/files/tests/js/filelistSpec.js208
-rw-r--r--apps/files_sharing/js/public.js43
-rw-r--r--apps/files_sharing/tests/js/publicAppSpec.js23
16 files changed, 1516 insertions, 802 deletions
diff --git a/apps/dav/lib/Connector/Sabre/FilesPlugin.php b/apps/dav/lib/Connector/Sabre/FilesPlugin.php
index aa5bacea5bb..39d15e0c6e9 100644
--- a/apps/dav/lib/Connector/Sabre/FilesPlugin.php
+++ b/apps/dav/lib/Connector/Sabre/FilesPlugin.php
@@ -46,6 +46,8 @@ use \Sabre\HTTP\ResponseInterface;
use OCP\Files\StorageNotAvailableException;
use OCP\IConfig;
use OCP\IRequest;
+use Sabre\DAV\Exception\BadRequest;
+use OCA\DAV\Connector\Sabre\Directory;
class FilesPlugin extends ServerPlugin {
@@ -170,6 +172,8 @@ class FilesPlugin extends ServerPlugin {
$this->server = $server;
$this->server->on('propFind', array($this, 'handleGetProperties'));
$this->server->on('propPatch', array($this, 'handleUpdateProperties'));
+ // RFC5995 to add file to the collection with a suggested name
+ $this->server->on('method:POST', [$this, 'httpPost']);
$this->server->on('afterBind', array($this, 'sendFileIdHeader'));
$this->server->on('afterWriteContent', array($this, 'sendFileIdHeader'));
$this->server->on('afterMethod:GET', [$this,'httpGet']);
@@ -432,4 +436,51 @@ class FilesPlugin extends ServerPlugin {
}
}
+ /**
+ * POST operation on directories to create a new file
+ * with suggested name
+ *
+ * @param RequestInterface $request request object
+ * @param ResponseInterface $response response object
+ * @return null|false
+ */
+ public function httpPost(RequestInterface $request, ResponseInterface $response) {
+ // TODO: move this to another plugin ?
+ if (!\OC::$CLI && !\OC::$server->getRequest()->passesCSRFCheck()) {
+ throw new BadRequest('Invalid CSRF token');
+ }
+
+ list($parentPath, $name) = \Sabre\HTTP\URLUtil::splitPath($request->getPath());
+
+ // Making sure the parent node exists and is a directory
+ $node = $this->tree->getNodeForPath($parentPath);
+
+ if ($node instanceof Directory) {
+ // no Add-Member found
+ if (empty($name) || $name[0] !== '&') {
+ // suggested name required
+ throw new BadRequest('Missing suggested file name');
+ }
+
+ $name = substr($name, 1);
+
+ if (empty($name)) {
+ // suggested name required
+ throw new BadRequest('Missing suggested file name');
+ }
+
+ // make sure the name is unique
+ $name = basename(\OC_Helper::buildNotExistingFileNameForView($parentPath, $name, $this->fileView));
+
+ $node->createFile($name, $request->getBodyAsStream());
+
+ list($parentUrl, ) = \Sabre\HTTP\URLUtil::splitPath($request->getUrl());
+
+ $response->setHeader('Content-Location', $parentUrl . '/' . rawurlencode($name));
+
+ // created
+ $response->setStatus(201);
+ return false;
+ }
+ }
}
diff --git a/apps/dav/lib/Connector/Sabre/IFrameTransportPlugin.php b/apps/dav/lib/Connector/Sabre/IFrameTransportPlugin.php
new file mode 100644
index 00000000000..af6e5a62a5e
--- /dev/null
+++ b/apps/dav/lib/Connector/Sabre/IFrameTransportPlugin.php
@@ -0,0 +1,188 @@
+<?php
+/**
+ * @author Vincent Petry <pvince81@owncloud.com>
+ *
+ * @copyright Copyright (c) 2015, ownCloud, Inc.
+ * @license AGPL-3.0
+ *
+ * This code is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License, version 3,
+ * as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License, version 3,
+ * along with this program. If not, see <http://www.gnu.org/licenses/>
+ *
+ */
+
+namespace OCA\DAV\Connector\Sabre;
+
+use Sabre\DAV\IFile;
+use Sabre\HTTP\RequestInterface;
+use Sabre\HTTP\ResponseInterface;
+use Sabre\DAV\Exception\BadRequest;
+
+/**
+ * Plugin to receive Webdav PUT through POST,
+ * mostly used as a workaround for browsers that
+ * do not support PUT upload.
+ */
+class IFrameTransportPlugin extends \Sabre\DAV\ServerPlugin {
+
+ /**
+ * @var \Sabre\DAV\Server $server
+ */
+ private $server;
+
+ /**
+ * This initializes the plugin.
+ *
+ * @param \Sabre\DAV\Server $server
+ * @return void
+ */
+ public function initialize(\Sabre\DAV\Server $server) {
+ $this->server = $server;
+ $this->server->on('method:POST', [$this, 'handlePost']);
+ }
+
+ /**
+ * POST operation
+ *
+ * @param RequestInterface $request request object
+ * @param ResponseInterface $response response object
+ * @return null|false
+ */
+ public function handlePost(RequestInterface $request, ResponseInterface $response) {
+ try {
+ return $this->processUpload($request, $response);
+ } catch (\Sabre\DAV\Exception $e) {
+ $response->setStatus($e->getHTTPCode());
+ $response->setBody(['message' => $e->getMessage()]);
+ $this->convertResponse($response);
+ return false;
+ }
+ }
+
+ /**
+ * Wrap and send response in JSON format
+ *
+ * @param ResponseInterface $response response object
+ */
+ private function convertResponse(ResponseInterface $response) {
+ if (is_resource($response->getBody())) {
+ throw new BadRequest('Cannot request binary data with iframe transport');
+ }
+
+ $responseData = json_encode([
+ 'status' => $response->getStatus(),
+ 'headers' => $response->getHeaders(),
+ 'data' => $response->getBody(),
+ ]);
+
+ // IE needs this content type
+ $response->setHeader('Content-Type', 'text/plain');
+ $response->setHeader('Content-Length', strlen($responseData));
+ $response->setStatus(200);
+ $response->setBody($responseData);
+ }
+
+ /**
+ * Process upload
+ *
+ * @param RequestInterface $request request object
+ * @param ResponseInterface $response response object
+ * @return null|false
+ */
+ private function processUpload(RequestInterface $request, ResponseInterface $response) {
+ $queryParams = $request->getQueryParameters();
+
+ if (!isset($queryParams['_method'])) {
+ return null;
+ }
+
+ $method = $queryParams['_method'];
+ if ($method !== 'PUT' && $method !== 'POST') {
+ return null;
+ }
+
+ $contentType = $request->getHeader('Content-Type');
+ list($contentType) = explode(';', $contentType);
+ if ($contentType !== 'application/x-www-form-urlencoded'
+ && $contentType !== 'multipart/form-data'
+ ) {
+ return null;
+ }
+
+ if (!isset($_FILES['files'])) {
+ return null;
+ }
+
+ // TODO: move this to another plugin ?
+ if (!\OC::$CLI && !\OC::$server->getRequest()->passesCSRFCheck()) {
+ throw new BadRequest('Invalid CSRF token');
+ }
+
+ if ($_FILES) {
+ $file = current($_FILES);
+ } else {
+ return null;
+ }
+
+ if ($file['error'][0] !== 0) {
+ throw new BadRequest('Error during upload, code ' . $file['error'][0]);
+ }
+
+ if (!\OC::$CLI && !is_uploaded_file($file['tmp_name'][0])) {
+ return null;
+ }
+
+ if (count($file['tmp_name']) > 1) {
+ throw new BadRequest('Only a single file can be uploaded');
+ }
+
+ $postData = $request->getPostData();
+ if (isset($postData['headers'])) {
+ $headers = json_decode($postData['headers'], true);
+
+ // copy safe headers into the request
+ $allowedHeaders = [
+ 'If',
+ 'If-Match',
+ 'If-None-Match',
+ 'If-Modified-Since',
+ 'If-Unmodified-Since',
+ 'Authorization',
+ ];
+
+ foreach ($allowedHeaders as $allowedHeader) {
+ if (isset($headers[$allowedHeader])) {
+ $request->setHeader($allowedHeader, $headers[$allowedHeader]);
+ }
+ }
+ }
+
+ // MEGAHACK, because the Sabre File impl reads this property directly
+ $_SERVER['CONTENT_LENGTH'] = $file['size'][0];
+ $request->setHeader('Content-Length', $file['size'][0]);
+
+ $tmpFile = $file['tmp_name'][0];
+ $resource = fopen($tmpFile, 'r');
+
+ $request->setBody($resource);
+ $request->setMethod($method);
+
+ $this->server->invokeMethod($request, $response, false);
+
+ fclose($resource);
+ unlink($tmpFile);
+
+ $this->convertResponse($response);
+
+ return false;
+ }
+
+}
diff --git a/apps/dav/lib/Connector/Sabre/ServerFactory.php b/apps/dav/lib/Connector/Sabre/ServerFactory.php
index 6d9f9b1bc8b..da541abc199 100644
--- a/apps/dav/lib/Connector/Sabre/ServerFactory.php
+++ b/apps/dav/lib/Connector/Sabre/ServerFactory.php
@@ -114,6 +114,7 @@ class ServerFactory {
// FIXME: The following line is a workaround for legacy components relying on being able to send a GET to /
$server->addPlugin(new \OCA\DAV\Connector\Sabre\DummyGetResponsePlugin());
$server->addPlugin(new \OCA\DAV\Connector\Sabre\ExceptionLoggerPlugin('webdav', $this->logger));
+ $server->addPlugin(new \OCA\DAV\Connector\Sabre\IFrameTransportPlugin());
$server->addPlugin(new \OCA\DAV\Connector\Sabre\LockPlugin());
// Some WebDAV clients do require Class 2 WebDAV support (locking), since
// we do not provide locking we emulate it using a fake locking plugin.
diff --git a/apps/dav/tests/unit/Connector/Sabre/FilesPluginTest.php b/apps/dav/tests/unit/Connector/Sabre/FilesPluginTest.php
index 282a5b2f626..43ca119abff 100644
--- a/apps/dav/tests/unit/Connector/Sabre/FilesPluginTest.php
+++ b/apps/dav/tests/unit/Connector/Sabre/FilesPluginTest.php
@@ -123,7 +123,7 @@ class FilesPluginTest extends TestCase {
* @param string $class
* @return \PHPUnit_Framework_MockObject_MockObject
*/
- private function createTestNode($class) {
+ private function createTestNode($class, $path = '/dummypath') {
$node = $this->getMockBuilder($class)
->disableOriginalConstructor()
->getMock();
@@ -134,7 +134,7 @@ class FilesPluginTest extends TestCase {
$this->tree->expects($this->any())
->method('getNodeForPath')
- ->with('/dummypath')
+ ->with($path)
->will($this->returnValue($node));
$node->expects($this->any())
@@ -547,4 +547,85 @@ class FilesPluginTest extends TestCase {
$this->assertEquals("false", $propFind->get(self::HAS_PREVIEW_PROPERTYNAME));
}
+
+ public function postCreateFileProvider() {
+ $baseUrl = 'http://example.com/owncloud/remote.php/webdav/subdir/';
+ return [
+ ['test.txt', 'some file.txt', 'some file.txt', $baseUrl . 'some%20file.txt'],
+ ['some file.txt', 'some file.txt', 'some file (2).txt', $baseUrl . 'some%20file%20%282%29.txt'],
+ ];
+ }
+
+ /**
+ * @dataProvider postCreateFileProvider
+ */
+ public function testPostWithAddMember($existingFile, $wantedName, $deduplicatedName, $expectedLocation) {
+ $request = $this->getMock('Sabre\HTTP\RequestInterface');
+ $response = $this->getMock('Sabre\HTTP\ResponseInterface');
+
+ $request->expects($this->any())
+ ->method('getUrl')
+ ->will($this->returnValue('http://example.com/owncloud/remote.php/webdav/subdir/&' . $wantedName));
+
+ $request->expects($this->any())
+ ->method('getPath')
+ ->will($this->returnValue('/subdir/&' . $wantedName));
+
+ $request->expects($this->once())
+ ->method('getBodyAsStream')
+ ->will($this->returnValue(fopen('data://text/plain,hello', 'r')));
+
+ $this->view->expects($this->any())
+ ->method('file_exists')
+ ->will($this->returnCallback(function($path) use ($existingFile) {
+ return ($path === '/subdir/' . $existingFile);
+ }));
+
+ $node = $this->createTestNode('\OCA\DAV\Connector\Sabre\Directory', '/subdir');
+
+ $node->expects($this->once())
+ ->method('createFile')
+ ->with($deduplicatedName, $this->isType('resource'));
+
+ $response->expects($this->once())
+ ->method('setStatus')
+ ->with(201);
+ $response->expects($this->once())
+ ->method('setHeader')
+ ->with('Content-Location', $expectedLocation);
+
+ $this->assertFalse($this->plugin->httpPost($request, $response));
+ }
+
+ public function testPostOnNonDirectory() {
+ $request = $this->getMock('Sabre\HTTP\RequestInterface');
+ $response = $this->getMock('Sabre\HTTP\ResponseInterface');
+
+ $request->expects($this->any())
+ ->method('getPath')
+ ->will($this->returnValue('/subdir/test.txt/&abc'));
+
+ $this->createTestNode('\OCA\DAV\Connector\Sabre\File', '/subdir/test.txt');
+
+ $this->assertNull($this->plugin->httpPost($request, $response));
+ }
+
+ /**
+ * @expectedException \Sabre\DAV\Exception\BadRequest
+ */
+ public function testPostWithoutAddMember() {
+ $request = $this->getMock('Sabre\HTTP\RequestInterface');
+ $response = $this->getMock('Sabre\HTTP\ResponseInterface');
+
+ $request->expects($this->any())
+ ->method('getPath')
+ ->will($this->returnValue('/subdir/&'));
+
+ $node = $this->createTestNode('\OCA\DAV\Connector\Sabre\Directory', '/subdir');
+
+ $node->expects($this->never())
+ ->method('createFile');
+
+ $this->plugin->httpPost($request, $response);
+ }
}
diff --git a/apps/dav/tests/unit/Connector/Sabre/IFrameTransportPluginTest.php b/apps/dav/tests/unit/Connector/Sabre/IFrameTransportPluginTest.php
new file mode 100644
index 00000000000..485dd1b779e
--- /dev/null
+++ b/apps/dav/tests/unit/Connector/Sabre/IFrameTransportPluginTest.php
@@ -0,0 +1,164 @@
+<?php
+
+namespace OCA\DAV\Tests\Unit\Connector\Sabre;
+
+/**
+ * Copyright (c) 2015 Vincent Petry <pvince81@owncloud.com>
+ * This file is licensed under the Affero General Public License version 3 or
+ * later.
+ * See the COPYING-README file.
+ */
+class IFrameTransportPluginTest extends \Test\TestCase {
+
+ /**
+ * @var \Sabre\DAV\Server
+ */
+ private $server;
+
+ /**
+ * @var \OCA\DAV\Connector\Sabre\IFrameTransportPlugin
+ */
+ private $plugin;
+
+ public function setUp() {
+ parent::setUp();
+ $this->server = $this->getMockBuilder('\Sabre\DAV\Server')
+ ->disableOriginalConstructor()
+ ->getMock();
+
+ $this->plugin = new \OCA\DAV\Connector\Sabre\IFrameTransportPlugin();
+ $this->plugin->initialize($this->server);
+ }
+
+ public function tearDown() {
+ $_FILES = null;
+ unset($_SERVER['CONTENT_LENGTH']);
+ }
+
+ public function testPutConversion() {
+ $request = $this->getMock('Sabre\HTTP\RequestInterface');
+ $response = $this->getMock('Sabre\HTTP\ResponseInterface');
+
+ $request->expects($this->once())
+ ->method('getQueryParameters')
+ ->will($this->returnValue(['_method' => 'PUT']));
+
+ $postData = [
+ 'headers' => json_encode([
+ 'If-None-Match' => '*',
+ 'Disallowed-Header' => 'test',
+ ]),
+ ];
+
+ $request->expects($this->once())
+ ->method('getPostData')
+ ->will($this->returnValue($postData));
+
+ $request->expects($this->once())
+ ->method('getHeader')
+ ->with('Content-Type')
+ ->will($this->returnValue('multipart/form-data'));
+
+ $tmpFileName = tempnam(sys_get_temp_dir(), 'tmpfile');
+ $fh = fopen($tmpFileName, 'w');
+ fwrite($fh, 'hello');
+ fclose($fh);
+
+ $_FILES = ['files' => [
+ 'error' => [0],
+ 'tmp_name' => [$tmpFileName],
+ 'size' => [5],
+ ]];
+
+ $request->expects($this->any())
+ ->method('setHeader')
+ ->withConsecutive(
+ ['If-None-Match', '*'],
+ ['Content-Length', 5]
+ );
+
+ $request->expects($this->once())
+ ->method('setMethod')
+ ->with('PUT');
+
+ $this->server->expects($this->once())
+ ->method('invokeMethod')
+ ->with($request, $response);
+
+ // response data before conversion
+ $response->expects($this->once())
+ ->method('getHeaders')
+ ->will($this->returnValue(['Test-Response-Header' => [123]]));
+
+ $response->expects($this->any())
+ ->method('getBody')
+ ->will($this->returnValue('test'));
+
+ $response->expects($this->once())
+ ->method('getStatus')
+ ->will($this->returnValue(201));
+
+ $responseBody = json_encode([
+ 'status' => 201,
+ 'headers' => ['Test-Response-Header' => [123]],
+ 'data' => 'test',
+ ]);
+
+ // response data after conversion
+ $response->expects($this->once())
+ ->method('setBody')
+ ->with($responseBody);
+
+ $response->expects($this->once())
+ ->method('setStatus')
+ ->with(200);
+
+ $response->expects($this->any())
+ ->method('setHeader')
+ ->withConsecutive(
+ ['Content-Type', 'text/plain'],
+ ['Content-Length', strlen($responseBody)]
+ );
+
+ $this->assertFalse($this->plugin->handlePost($request, $response));
+
+ $this->assertEquals(5, $_SERVER['CONTENT_LENGTH']);
+
+ $this->assertFalse(file_exists($tmpFileName));
+ }
+
+ public function testIgnoreNonPut() {
+ $request = $this->getMock('Sabre\HTTP\RequestInterface');
+ $response = $this->getMock('Sabre\HTTP\ResponseInterface');
+
+ $request->expects($this->once())
+ ->method('getQueryParameters')
+ ->will($this->returnValue(['_method' => 'PROPFIND']));
+
+ $this->server->expects($this->never())
+ ->method('invokeMethod')
+ ->with($request, $response);
+
+ $this->assertNull($this->plugin->handlePost($request, $response));
+ }
+
+ public function testIgnoreMismatchedContentType() {
+ $request = $this->getMock('Sabre\HTTP\RequestInterface');
+ $response = $this->getMock('Sabre\HTTP\ResponseInterface');
+
+ $request->expects($this->once())
+ ->method('getQueryParameters')
+ ->will($this->returnValue(['_method' => 'PUT']));
+
+ $request->expects($this->once())
+ ->method('getHeader')
+ ->with('Content-Type')
+ ->will($this->returnValue('text/plain'));
+
+ $this->server->expects($this->never())
+ ->method('invokeMethod')
+ ->with($request, $response);
+
+ $this->assertNull($this->plugin->handlePost($request, $response));
+ }
+}
diff --git a/apps/files/ajax/upload.php b/apps/files/ajax/upload.php
deleted file mode 100644
index 9de5c0bce39..00000000000
--- a/apps/files/ajax/upload.php
+++ /dev/null
@@ -1,283 +0,0 @@
-<?php
-/**
- * @copyright Copyright (c) 2016, ownCloud, Inc.
- *
- * @author Arthur Schiwon <blizzz@arthur-schiwon.de>
- * @author Bart Visscher <bartv@thisnet.nl>
- * @author Björn Schießle <bjoern@schiessle.org>
- * @author Clark Tomlinson <fallen013@gmail.com>
- * @author Florian Pritz <bluewind@xinu.at>
- * @author Frank Karlitschek <frank@karlitschek.de>
- * @author Individual IT Services <info@individual-it.net>
- * @author Joas Schilling <coding@schilljs.com>
- * @author Jörn Friedrich Dreyer <jfd@butonic.de>
- * @author Lukas Reschke <lukas@statuscode.ch>
- * @author Luke Policinski <lpolicinski@gmail.com>
- * @author Robin Appelman <robin@icewind.nl>
- * @author Roman Geber <rgeber@owncloudapps.com>
- * @author TheSFReader <TheSFReader@gmail.com>
- * @author Thomas Müller <thomas.mueller@tmit.eu>
- * @author Vincent Petry <pvince81@owncloud.com>
- *
- * @license AGPL-3.0
- *
- * This code is free software: you can redistribute it and/or modify
- * it under the terms of the GNU Affero General Public License, version 3,
- * as published by the Free Software Foundation.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU Affero General Public License for more details.
- *
- * You should have received a copy of the GNU Affero General Public License, version 3,
- * along with this program. If not, see <http://www.gnu.org/licenses/>
- *
- */
-\OC::$server->getSession()->close();
-
-// Firefox and Konqueror tries to download application/json for me. --Arthur
-OCP\JSON::setContentTypeHeader('text/plain');
-
-// If a directory token is sent along check if public upload is permitted.
-// If not, check the login.
-// If no token is sent along, rely on login only
-
-$errorCode = null;
-$errorFileName = null;
-
-$l = \OC::$server->getL10N('files');
-if (empty($_POST['dirToken'])) {
- // The standard case, files are uploaded through logged in users :)
- OCP\JSON::checkLoggedIn();
- $dir = isset($_POST['dir']) ? (string)$_POST['dir'] : '';
- if (!$dir || empty($dir) || $dir === false) {
- OCP\JSON::error(array('data' => array_merge(array('message' => $l->t('Unable to set upload directory.')))));
- die();
- }
-} else {
- // TODO: ideally this code should be in files_sharing/ajax/upload.php
- // and the upload/file transfer code needs to be refactored into a utility method
- // that could be used there
-
- \OC_User::setIncognitoMode(true);
-
- $publicDirectory = !empty($_POST['subdir']) ? (string)$_POST['subdir'] : '/';
-
- $linkItem = OCP\Share::getShareByToken((string)$_POST['dirToken']);
- if ($linkItem === false) {
- OCP\JSON::error(array('data' => array_merge(array('message' => $l->t('Invalid Token')))));
- die();
- }
-
- if (!($linkItem['permissions'] & \OCP\Constants::PERMISSION_CREATE)) {
- OCP\JSON::checkLoggedIn();
- } else {
- // resolve reshares
- $rootLinkItem = OCP\Share::resolveReShare($linkItem);
-
- OCP\JSON::checkUserExists($rootLinkItem['uid_owner']);
- // Setup FS with owner
- OC_Util::tearDownFS();
- OC_Util::setupFS($rootLinkItem['uid_owner']);
-
- // The token defines the target directory (security reasons)
- $path = \OC\Files\Filesystem::getPath($linkItem['file_source']);
- if($path === null) {
- OCP\JSON::error(array('data' => array_merge(array('message' => $l->t('Unable to set upload directory.')))));
- die();
- }
- $dir = sprintf(
- "/%s/%s",
- $path,
- $publicDirectory
- );
-
- if (!$dir || empty($dir) || $dir === false) {
- OCP\JSON::error(array('data' => array_merge(array('message' => $l->t('Unable to set upload directory.')))));
- die();
- }
-
- $dir = rtrim($dir, '/');
- }
-}
-
-OCP\JSON::callCheck();
-
-// get array with current storage stats (e.g. max file size)
-$storageStats = \OCA\Files\Helper::buildFileStorageStatistics($dir);
-
-if (!isset($_FILES['files'])) {
- OCP\JSON::error(array('data' => array_merge(array('message' => $l->t('No file was uploaded. Unknown error')), $storageStats)));
- exit();
-}
-
-foreach ($_FILES['files']['error'] as $error) {
- if ($error != 0) {
- $errors = array(
- UPLOAD_ERR_OK => $l->t('There is no error, the file uploaded with success'),
- UPLOAD_ERR_INI_SIZE => $l->t('The uploaded file exceeds the upload_max_filesize directive in php.ini: ')
- . OC::$server->getIniWrapper()->getNumeric('upload_max_filesize'),
- UPLOAD_ERR_FORM_SIZE => $l->t('The uploaded file exceeds the MAX_FILE_SIZE directive that was specified in the HTML form'),
- UPLOAD_ERR_PARTIAL => $l->t('The uploaded file was only partially uploaded'),
- UPLOAD_ERR_NO_FILE => $l->t('No file was uploaded'),
- UPLOAD_ERR_NO_TMP_DIR => $l->t('Missing a temporary folder'),
- UPLOAD_ERR_CANT_WRITE => $l->t('Failed to write to disk'),
- );
- $errorMessage = $errors[$error];
- \OC::$server->getLogger()->alert("Upload error: $error - $errorMessage", array('app' => 'files'));
- OCP\JSON::error(array('data' => array_merge(array('message' => $errorMessage), $storageStats)));
- exit();
- }
-}
-$files = $_FILES['files'];
-
-$error = false;
-
-$maxUploadFileSize = $storageStats['uploadMaxFilesize'];
-$maxHumanFileSize = OCP\Util::humanFileSize($maxUploadFileSize);
-
-$totalSize = 0;
-$isReceivedShare = \OC::$server->getRequest()->getParam('isReceivedShare', false) === 'true';
-// defer quota check for received shares
-if (!$isReceivedShare && $storageStats['freeSpace'] >= 0) {
- foreach ($files['size'] as $size) {
- $totalSize += $size;
- }
-}
-if ($maxUploadFileSize >= 0 and $totalSize > $maxUploadFileSize) {
- OCP\JSON::error(array('data' => array('message' => $l->t('Not enough storage available'),
- 'uploadMaxFilesize' => $maxUploadFileSize,
- 'maxHumanFilesize' => $maxHumanFileSize)));
- exit();
-}
-
-$result = array();
-if (\OC\Files\Filesystem::isValidPath($dir) === true) {
- $fileCount = count($files['name']);
- for ($i = 0; $i < $fileCount; $i++) {
-
- if (isset($_POST['resolution'])) {
- $resolution = $_POST['resolution'];
- } else {
- $resolution = null;
- }
-
- if(isset($_POST['dirToken'])) {
- // If it is a read only share the resolution will always be autorename
- $shareManager = \OC::$server->getShareManager();
- $share = $shareManager->getShareByToken((string)$_POST['dirToken']);
- if (!($share->getPermissions() & \OCP\Constants::PERMISSION_READ)) {
- $resolution = 'autorename';
- }
- }
-
- // target directory for when uploading folders
- $relativePath = '';
- if(!empty($_POST['file_directory'])) {
- $relativePath = '/'.$_POST['file_directory'];
- }
-
- // $path needs to be normalized - this failed within drag'n'drop upload to a sub-folder
- if ($resolution === 'autorename') {
- // append a number in brackets like 'filename (2).ext'
- $target = OCP\Files::buildNotExistingFileName($dir . $relativePath, $files['name'][$i]);
- } else {
- $target = \OC\Files\Filesystem::normalizePath($dir . $relativePath.'/'.$files['name'][$i]);
- }
-
- // relative dir to return to the client
- if (isset($publicDirectory)) {
- // path relative to the public root
- $returnedDir = $publicDirectory . $relativePath;
- } else {
- // full path
- $returnedDir = $dir . $relativePath;
- }
- $returnedDir = \OC\Files\Filesystem::normalizePath($returnedDir);
-
-
- $exists = \OC\Files\Filesystem::file_exists($target);
- if ($exists) {
- $updatable = \OC\Files\Filesystem::isUpdatable($target);
- }
- if ( ! $exists || ($updatable && $resolution === 'replace' ) ) {
- // upload and overwrite file
- try
- {
- if (is_uploaded_file($files['tmp_name'][$i]) and \OC\Files\Filesystem::fromTmpFile($files['tmp_name'][$i], $target)) {
-
- // updated max file size after upload
- $storageStats = \OCA\Files\Helper::buildFileStorageStatistics($dir);
-
- $meta = \OC\Files\Filesystem::getFileInfo($target);
- if ($meta === false) {
- $error = $l->t('The target folder has been moved or deleted.');
- $errorCode = 'targetnotfound';
- } else {
- $data = \OCA\Files\Helper::formatFileInfo($meta);
- $data['status'] = 'success';
- $data['originalname'] = $files['name'][$i];
- $data['uploadMaxFilesize'] = $maxUploadFileSize;
- $data['maxHumanFilesize'] = $maxHumanFileSize;
- $data['permissions'] = $meta['permissions'];
- $data['directory'] = $returnedDir;
- $result[] = $data;
- }
-
- } else {
- $error = $l->t('Upload failed. Could not find uploaded file');
- $errorFileName = $files['name'][$i];
- }
- } catch(Exception $ex) {
- $error = $ex->getMessage();
- }
-
- } else {
- // file already exists
- $meta = \OC\Files\Filesystem::getFileInfo($target);
- if ($meta === false) {
- $error = $l->t('Upload failed. Could not get file info.');
- } else {
- $data = \OCA\Files\Helper::formatFileInfo($meta);
- if ($updatable) {
- $data['status'] = 'existserror';
- } else {
- $data['status'] = 'readonly';
- }
- $data['originalname'] = $files['name'][$i];
- $data['uploadMaxFilesize'] = $maxUploadFileSize;
- $data['maxHumanFilesize'] = $maxHumanFileSize;
- $data['permissions'] = $meta['permissions'];
- $data['directory'] = $returnedDir;
- $result[] = $data;
- }
- }
- }
-} else {
- $error = $l->t('Invalid directory.');
-}
-
-if ($error === false) {
- // Do not leak file information if it is a read-only share
- if(isset($_POST['dirToken'])) {
- $shareManager = \OC::$server->getShareManager();
- $share = $shareManager->getShareByToken((string)$_POST['dirToken']);
- if (!($share->getPermissions() & \OCP\Constants::PERMISSION_READ)) {
- $newResults = [];
- foreach($result as $singleResult) {
- $fileName = $singleResult['originalname'];
- $newResults['filename'] = $fileName;
- $newResults['mimetype'] = \OC::$server->getMimeTypeDetector()->detectPath($fileName);
- }
- $result = $newResults;
- }
- }
- OCP\JSON::encodedPrint($result);
-} else {
- OCP\JSON::error(array(array('data' => array_merge(array(
- 'message' => $error,
- 'code' => $errorCode,
- 'filename' => $errorFileName
- ), $storageStats))));
-}
diff --git a/apps/files/appinfo/routes.php b/apps/files/appinfo/routes.php
index 49dbe553435..06d8d39585f 100644
--- a/apps/files/appinfo/routes.php
+++ b/apps/files/appinfo/routes.php
@@ -75,24 +75,12 @@ $application->registerRoutes(
/** @var $this \OC\Route\Router */
-$this->create('files_ajax_delete', 'ajax/delete.php')
- ->actionInclude('files/ajax/delete.php');
$this->create('files_ajax_download', 'ajax/download.php')
->actionInclude('files/ajax/download.php');
$this->create('files_ajax_getstoragestats', 'ajax/getstoragestats.php')
->actionInclude('files/ajax/getstoragestats.php');
$this->create('files_ajax_list', 'ajax/list.php')
->actionInclude('files/ajax/list.php');
-$this->create('files_ajax_move', 'ajax/move.php')
- ->actionInclude('files/ajax/move.php');
-$this->create('files_ajax_newfile', 'ajax/newfile.php')
- ->actionInclude('files/ajax/newfile.php');
-$this->create('files_ajax_newfolder', 'ajax/newfolder.php')
- ->actionInclude('files/ajax/newfolder.php');
-$this->create('files_ajax_rename', 'ajax/rename.php')
- ->actionInclude('files/ajax/rename.php');
-$this->create('files_ajax_upload', 'ajax/upload.php')
- ->actionInclude('files/ajax/upload.php');
$this->create('download', 'download{file}')
->requirements(array('file' => '.*'))
diff --git a/apps/files/js/app.js b/apps/files/js/app.js
index fbfa510e07e..17e92de90dd 100644
--- a/apps/files/js/app.js
+++ b/apps/files/js/app.js
@@ -93,6 +93,7 @@
direction: $('#defaultFileSortingDirection').val()
},
config: this._filesConfig,
+ enableUpload: true
}
);
this.files.initialize();
diff --git a/apps/files/js/file-upload.js b/apps/files/js/file-upload.js
index 56ea384c9e0..0d45623ce65 100644
--- a/apps/files/js/file-upload.js
+++ b/apps/files/js/file-upload.js
@@ -18,83 +18,448 @@
* - TODO music upload button
*/
-/* global jQuery, oc_requesttoken, humanFileSize, FileList */
+/* global jQuery, humanFileSize */
/**
- * Function that will allow us to know if Ajax uploads are supported
- * @link https://github.com/New-Bamboo/example-ajax-upload/blob/master/public/index.html
- * also see article @link http://blog.new-bamboo.co.uk/2012/01/10/ridiculously-simple-ajax-uploads-with-formdata
+ * File upload object
+ *
+ * @class OC.FileUpload
+ * @classdesc
+ *
+ * Represents a file upload
+ *
+ * @param {OC.Uploader} uploader uploader
+ * @param {Object} data blueimp data
*/
-function supportAjaxUploadWithProgress() {
- return supportFileAPI() && supportAjaxUploadProgressEvents() && supportFormData();
-
- // Is the File API supported?
- function supportFileAPI() {
- var fi = document.createElement('INPUT');
- fi.type = 'file';
- return 'files' in fi;
- }
+OC.FileUpload = function(uploader, data) {
+ this.uploader = uploader;
+ this.data = data;
+};
+OC.FileUpload.CONFLICT_MODE_DETECT = 0;
+OC.FileUpload.CONFLICT_MODE_OVERWRITE = 1;
+OC.FileUpload.CONFLICT_MODE_AUTORENAME = 2;
+OC.FileUpload.prototype = {
- // Are progress events supported?
- function supportAjaxUploadProgressEvents() {
- var xhr = new XMLHttpRequest();
- return !! (xhr && ('upload' in xhr) && ('onprogress' in xhr.upload));
- }
+ /**
+ * Upload element
+ *
+ * @type Object
+ */
+ $uploadEl: null,
- // Is FormData supported?
- function supportFormData() {
- return !! window.FormData;
- }
-}
+ /**
+ * Target folder
+ *
+ * @type string
+ */
+ _targetFolder: '',
-/**
- * Add form data into the given form data
- *
- * @param {Array|Object} formData form data which can either be an array or an object
- * @param {Object} newData key-values to add to the form data
- *
- * @return updated form data
- */
-function addFormData(formData, newData) {
- // in IE8, formData is an array instead of object
- if (_.isArray(formData)) {
- _.each(newData, function(value, key) {
- formData.push({name: key, value: value});
+ /**
+ * @type int
+ */
+ _conflictMode: OC.FileUpload.CONFLICT_MODE_DETECT,
+
+ /**
+ * New name from server after autorename
+ *
+ * @type String
+ */
+ _newName: null,
+
+ /**
+ * Returns the file to be uploaded
+ *
+ * @return {File} file
+ */
+ getFile: function() {
+ return this.data.files[0];
+ },
+
+ /**
+ * Return the final filename.
+ * Either this is the original file name or the file name
+ * after an autorename.
+ *
+ * @return {String} file name
+ */
+ getFileName: function() {
+ // in case of autorename
+ if (this._newName) {
+ return this._newName;
+ }
+
+ if (this._conflictMode === OC.FileUpload.CONFLICT_MODE_AUTORENAME) {
+
+ var locationUrl = this.getResponseHeader('Content-Location');
+ if (locationUrl) {
+ this._newName = decodeURIComponent(OC.basename(locationUrl));
+ return this._newName;
+ }
+ }
+
+ return this.getFile().name;
+ },
+
+ setTargetFolder: function(targetFolder) {
+ this._targetFolder = targetFolder;
+ },
+
+ getTargetFolder: function() {
+ return this._targetFolder;
+ },
+
+ /**
+ * Get full path for the target file, including relative path,
+ * without the file name.
+ *
+ * @return {String} full path
+ */
+ getFullPath: function() {
+ return OC.joinPaths(this._targetFolder, this.getFile().relativePath || '');
+ },
+
+ /**
+ * Set conflict resolution mode.
+ * See CONFLICT_MODE_* constants.
+ */
+ setConflictMode: function(mode) {
+ this._conflictMode = mode;
+ },
+
+ /**
+ * Returns whether the upload is in progress
+ *
+ * @return {bool}
+ */
+ isPending: function() {
+ return this.data.state() === 'pending';
+ },
+
+ deleteUpload: function() {
+ delete this.data.jqXHR;
+ },
+
+ /**
+ * Submit the upload
+ */
+ submit: function() {
+ var data = this.data;
+ var file = this.getFile();
+
+ // it was a folder upload, so make sure the parent directory exists alrady
+ var folderPromise;
+ if (file.relativePath) {
+ folderPromise = this.uploader.ensureFolderExists(this.getFullPath());
+ } else {
+ folderPromise = $.Deferred().resolve().promise();
+ }
+
+ if (this.uploader.fileList) {
+ this.data.url = this.uploader.fileList.getUploadUrl(file.name, this.getFullPath());
+ }
+
+ if (!this.data.headers) {
+ this.data.headers = {};
+ }
+
+ // webdav without multipart
+ this.data.multipart = false;
+ this.data.type = 'PUT';
+
+ delete this.data.headers['If-None-Match'];
+ if (this._conflictMode === OC.FileUpload.CONFLICT_MODE_DETECT) {
+ this.data.headers['If-None-Match'] = '*';
+ } else if (this._conflictMode === OC.FileUpload.CONFLICT_MODE_AUTORENAME) {
+ // POST to parent folder, with slug
+ this.data.type = 'POST';
+ this.data.url = this.uploader.fileList.getUploadUrl('&' + file.name, this.getFullPath());
+ }
+
+ if (file.lastModified) {
+ // preserve timestamp
+ this.data.headers['X-OC-Mtime'] = file.lastModified / 1000;
+ }
+
+ if (!this.uploader.isXHRUpload()) {
+ data.formData = [];
+
+ // pass headers as parameters
+ data.formData.push({name: 'headers', value: JSON.stringify(this.data.headers)});
+ data.formData.push({name: 'requesttoken', value: OC.requestToken});
+ if (data.type === 'POST') {
+ // still add the method to the URL
+ data.url += '?_method=POST';
+ }
+ }
+
+ // wait for creation of the required directory before uploading
+ folderPromise.then(function() {
+ data.submit();
+ }, function() {
+ data.abort();
+ });
+
+ },
+
+ /**
+ * Abort the upload
+ */
+ abort: function() {
+ this.data.abort();
+ },
+
+ /**
+ * Returns the server response
+ *
+ * @return {Object} response
+ */
+ getResponse: function() {
+ var response = this.data.response();
+ if (typeof response.result !== 'string') {
+ //fetch response from iframe
+ response = $.parseJSON(response.result[0].body.innerText);
+ if (!response) {
+ // likely due to internal server error
+ response = {status: 500};
+ }
+ } else {
+ response = response.result;
+ }
+ return response;
+ },
+
+ /**
+ * Returns the status code from the response
+ *
+ * @return {int} status code
+ */
+ getResponseStatus: function() {
+ if (this.uploader.isXHRUpload()) {
+ var xhr = this.data.response().jqXHR;
+ if (xhr) {
+ return xhr.status;
+ }
+ return null;
+ }
+ return this.getResponse().status;
+ },
+
+ /**
+ * Returns the response header by name
+ *
+ * @param {String} headerName header name
+ * @return {Array|String} response header value(s)
+ */
+ getResponseHeader: function(headerName) {
+ headerName = headerName.toLowerCase();
+ if (this.uploader.isXHRUpload()) {
+ return this.data.response().jqXHR.getResponseHeader(headerName);
+ }
+
+ var headers = this.getResponse().headers;
+ if (!headers) {
+ return null;
+ }
+
+ var value = _.find(headers, function(value, key) {
+ return key.toLowerCase() === headerName;
});
- } else {
- formData = _.extend(formData, newData);
+ if (_.isArray(value) && value.length === 1) {
+ return value[0];
+ }
+ return value;
}
- return formData;
-}
+};
/**
* keeps track of uploads in progress and implements callbacks for the conflicts dialog
* @namespace
*/
-OC.Upload = {
+
+OC.Uploader = function() {
+ this.init.apply(this, arguments);
+};
+
+OC.Uploader.prototype = {
+ /**
+ * @type Array<OC.FileUpload>
+ */
_uploads: [],
+
/**
- * deletes the jqHXR object from a data selection
- * @param {object} data
+ * List of directories known to exist.
+ *
+ * Key is the fullpath and value is boolean, true meaning that the directory
+ * was already created so no need to create it again.
+ */
+ _knownDirs: {},
+
+ /**
+ * @type OCA.Files.FileList
+ */
+ fileList: null,
+
+ /**
+ * @type OC.Files.Client
+ */
+ filesClient: null,
+
+ /**
+ * Function that will allow us to know if Ajax uploads are supported
+ * @link https://github.com/New-Bamboo/example-ajax-upload/blob/master/public/index.html
+ * also see article @link http://blog.new-bamboo.co.uk/2012/01/10/ridiculously-simple-ajax-uploads-with-formdata
+ */
+ _supportAjaxUploadWithProgress: function() {
+ return supportFileAPI() && supportAjaxUploadProgressEvents() && supportFormData();
+
+ // Is the File API supported?
+ function supportFileAPI() {
+ var fi = document.createElement('INPUT');
+ fi.type = 'file';
+ return 'files' in fi;
+ }
+
+ // Are progress events supported?
+ function supportAjaxUploadProgressEvents() {
+ var xhr = new XMLHttpRequest();
+ return !! (xhr && ('upload' in xhr) && ('onprogress' in xhr.upload));
+ }
+
+ // Is FormData supported?
+ function supportFormData() {
+ return !! window.FormData;
+ }
+ },
+
+ /**
+ * Returns whether an XHR upload will be used
+ *
+ * @return {bool} true if XHR upload will be used,
+ * false for iframe upload
*/
- deleteUpload:function(data) {
- delete data.jqXHR;
+ isXHRUpload: function () {
+ return !this.fileUploadParam.forceIframeTransport &&
+ ((!this.fileUploadParam.multipart && $.support.xhrFileUpload) ||
+ $.support.xhrFormDataFileUpload);
+ },
+
+ /**
+ * Makes sure that the upload folder and its parents exists
+ *
+ * @param {String} fullPath full path
+ * @return {Promise} promise that resolves when all parent folders
+ * were created
+ */
+ ensureFolderExists: function(fullPath) {
+ if (!fullPath || fullPath === '/') {
+ return $.Deferred().resolve().promise();
+ }
+
+ // remove trailing slash
+ if (fullPath.charAt(fullPath.length - 1) === '/') {
+ fullPath = fullPath.substr(0, fullPath.length - 1);
+ }
+
+ var self = this;
+ var promise = this._knownDirs[fullPath];
+
+ if (this.fileList) {
+ // assume the current folder exists
+ this._knownDirs[this.fileList.getCurrentDirectory()] = $.Deferred().resolve().promise();
+ }
+
+ if (!promise) {
+ var deferred = new $.Deferred();
+ promise = deferred.promise();
+ this._knownDirs[fullPath] = promise;
+
+ // make sure all parents already exist
+ var parentPath = OC.dirname(fullPath);
+ var parentPromise = this._knownDirs[parentPath];
+ if (!parentPromise) {
+ parentPromise = this.ensureFolderExists(parentPath);
+ }
+
+ parentPromise.then(function() {
+ self.filesClient.createDirectory(fullPath).always(function(status) {
+ // 405 is expected if the folder already exists
+ if ((status >= 200 && status < 300) || status === 405) {
+ self.$uploadEl.trigger($.Event('fileuploadcreatedfolder'), fullPath);
+ deferred.resolve();
+ return;
+ }
+ OC.Notification.showTemporary(t('files', 'Could not create folder "{dir}"', {dir: fullPath}));
+ deferred.reject();
+ });
+ }, function() {
+ deferred.reject();
+ });
+ }
+
+ return promise;
+ },
+
+ /**
+ * Submit the given uploads
+ *
+ * @param {Array} array of uploads to start
+ */
+ submitUploads: function(uploads) {
+ var self = this;
+ _.each(uploads, function(upload) {
+ upload.submit();
+ self._uploads[upload.data.uploadId] = upload;
+ });
+ },
+
+ /**
+ * Show conflict for the given file object
+ *
+ * @param {OC.FileUpload} file upload object
+ */
+ showConflict: function(fileUpload) {
+ //show "file already exists" dialog
+ var self = this;
+ var file = fileUpload.getFile();
+ // retrieve more info about this file
+ this.filesClient.getFileInfo(fileUpload.getFullPath()).then(function(status, fileInfo) {
+ var original = fileInfo;
+ var replacement = file;
+ OC.dialogs.fileexists(fileUpload, original, replacement, self);
+ });
},
/**
* cancels all uploads
*/
cancelUploads:function() {
this.log('canceling uploads');
- jQuery.each(this._uploads, function(i, jqXHR) {
- jqXHR.abort();
+ jQuery.each(this._uploads, function(i, upload) {
+ upload.abort();
});
- this._uploads = [];
+ this.clear();
+ },
+ /**
+ * Clear uploads
+ */
+ clear: function() {
+ this._uploads = {};
+ this._knownDirs = {};
},
- rememberUpload:function(jqXHR) {
- if (jqXHR) {
- this._uploads.push(jqXHR);
+ /**
+ * Returns an upload by id
+ *
+ * @param {int} data uploadId
+ * @return {OC.FileUpload} file upload
+ */
+ getUpload: function(data) {
+ if (_.isString(data)) {
+ return this._uploads[data];
+ } else if (data.uploadId) {
+ return this._uploads[data.uploadId];
}
+ return null;
},
+
showUploadCancelMessage: _.debounce(function() {
OC.Notification.showTemporary(t('files', 'Upload cancelled.'), {timeout: 10});
}, 500),
@@ -106,8 +471,8 @@ OC.Upload = {
isProcessing:function() {
var count = 0;
- jQuery.each(this._uploads, function(i, data) {
- if (data.state() === 'pending') {
+ jQuery.each(this._uploads, function(i, upload) {
+ if (upload.isPending()) {
count++;
}
});
@@ -115,9 +480,8 @@ OC.Upload = {
},
/**
* callback for the conflicts dialog
- * @param {object} data
*/
- onCancel:function(data) {
+ onCancel:function() {
this.cancelUploads();
},
/**
@@ -147,43 +511,29 @@ OC.Upload = {
},
/**
* handle skipping an upload
- * @param {object} data
+ * @param {OC.FileUpload} upload
*/
- onSkip:function(data) {
- this.log('skip', null, data);
- this.deleteUpload(data);
+ onSkip:function(upload) {
+ this.log('skip', null, upload);
+ upload.deleteUpload();
},
/**
* handle replacing a file on the server with an uploaded file
- * @param {object} data
+ * @param {FileUpload} data
*/
- onReplace:function(data) {
- this.log('replace', null, data);
- if (data.data) {
- data.data.append('resolution', 'replace');
- } else {
- if (!data.formData) {
- data.formData = {};
- }
- addFormData(data.formData, {resolution: 'replace'});
- }
- data.submit();
+ onReplace:function(upload) {
+ this.log('replace', null, upload);
+ upload.setConflictMode(OC.FileUpload.CONFLICT_MODE_OVERWRITE);
+ upload.submit();
},
/**
* handle uploading a file and letting the server decide a new name
- * @param {object} data
+ * @param {object} upload
*/
- onAutorename:function(data) {
- this.log('autorename', null, data);
- if (data.data) {
- data.data.append('resolution', 'autorename');
- } else {
- if (!data.formData) {
- data.formData = {};
- }
- addFormData(data.formData, {resolution: 'autorename'});
- }
- data.submit();
+ onAutorename:function(upload) {
+ this.log('autorename', null, upload);
+ upload.setConflictMode(OC.FileUpload.CONFLICT_MODE_AUTORENAME);
+ upload.submit();
},
_trace:false, //TODO implement log handler for JS per class?
log:function(caption, e, data) {
@@ -205,11 +555,20 @@ OC.Upload = {
* @param {function} callbacks.onCancel
*/
checkExistingFiles: function (selection, callbacks) {
- var fileList = FileList;
+ var fileList = this.fileList;
var conflicts = [];
// only keep non-conflicting uploads
selection.uploads = _.filter(selection.uploads, function(upload) {
- var fileInfo = fileList.findFile(upload.files[0].name);
+ var file = upload.getFile();
+ if (file.relativePath) {
+ // can't check in subfolder contents
+ return true;
+ }
+ if (!fileList) {
+ // no list to check against
+ return true;
+ }
+ var fileInfo = fileList.findFile(file.name);
if (fileInfo) {
conflicts.push([
// original
@@ -225,9 +584,9 @@ OC.Upload = {
});
if (conflicts.length) {
// wait for template loading
- OC.dialogs.fileexists(null, null, null, OC.Upload).done(function() {
+ OC.dialogs.fileexists(null, null, null, this).done(function() {
_.each(conflicts, function(conflictData) {
- OC.dialogs.fileexists(conflictData[1], conflictData[0], conflictData[1].files[0], OC.Upload);
+ OC.dialogs.fileexists(conflictData[1], conflictData[0], conflictData[1].getFile(), this);
});
});
}
@@ -240,15 +599,26 @@ OC.Upload = {
},
_hideProgressBar: function() {
+ var self = this;
$('#uploadprogresswrapper .stop').fadeOut();
$('#uploadprogressbar').fadeOut(function() {
- $('#file_upload_start').trigger(new $.Event('resized'));
+ self.$uploadEl.trigger(new $.Event('resized'));
});
},
_showProgressBar: function() {
$('#uploadprogressbar').fadeIn();
- $('#file_upload_start').trigger(new $.Event('resized'));
+ this.$uploadEl.trigger(new $.Event('resized'));
+ },
+
+ on: function() {
+ // forward events to upload element
+ this.$uploadEl.on.apply(this.$uploadEl, arguments);
+ },
+
+ off: function() {
+ // forward events to upload element
+ this.$uploadEl.off.apply(this.$uploadEl, arguments);
},
/**
@@ -269,12 +639,34 @@ OC.Upload = {
return ($tr.attr('data-mounttype') === 'shared-root' && $tr.attr('data-mime') !== 'httpd/unix-directory');
},
- init: function() {
+ /**
+ * Initialize the upload object
+ *
+ * @param {Object} $uploadEl upload element
+ * @param {Object} options
+ * @param {OCA.Files.FileList} [options.fileList] file list object
+ * @param {OC.Files.Client} [options.filesClient] files client object
+ * @param {Object} [options.dropZone] drop zone for drag and drop upload
+ */
+ init: function($uploadEl, options) {
var self = this;
- if ( $('#file_upload_start').exists() ) {
- var file_upload_param = {
- dropZone: $('#app-content'), // restrict dropZone to app-content div
- pasteZone: null,
+
+ options = options || {};
+
+ this.fileList = options.fileList;
+ this.filesClient = options.filesClient || OC.Files.getClient();
+
+ $uploadEl = $($uploadEl);
+ this.$uploadEl = $uploadEl;
+
+ if ($uploadEl.exists()) {
+ $('#uploadprogresswrapper .stop').on('click', function() {
+ this.cancelUploads();
+ });
+
+ this.fileUploadParam = {
+ type: 'PUT',
+ dropZone: options.dropZone, // restrict dropZone to content div
autoUpload: false,
sequentialUploads: true,
//singleFileUploads is on by default, so the data.files array will always have length 1
@@ -295,9 +687,13 @@ OC.Upload = {
* @returns {boolean}
*/
add: function(e, data) {
- OC.Upload.log('add', e, data);
+ self.log('add', e, data);
var that = $(this), freeSpace;
+ var upload = new OC.FileUpload(self, data);
+ // can't link directly due to jQuery not liking cyclic deps on its ajax object
+ data.uploadId = _.uniqueId('file-upload');
+
// we need to collect all data upload objects before
// starting the upload so we can check their existence
// and set individual conflict actions. Unfortunately,
@@ -317,16 +713,17 @@ OC.Upload = {
biggestFileBytes: 0
};
}
+ // TODO: move originalFiles to a separate container, maybe inside OC.Upload
var selection = data.originalFiles.selection;
// add uploads
if ( selection.uploads.length < selection.filesToUpload ) {
// remember upload
- selection.uploads.push(data);
+ selection.uploads.push(upload);
}
//examine file
- var file = data.files[0];
+ var file = upload.getFile();
try {
// FIXME: not so elegant... need to refactor that method to return a value
Files.isFileNameValid(file.name);
@@ -336,9 +733,14 @@ OC.Upload = {
data.errorThrown = errorMessage;
}
+ if (data.targetDir) {
+ upload.setTargetFolder(data.targetDir);
+ delete data.targetDir;
+ }
+
// in case folder drag and drop is not supported file will point to a directory
// http://stackoverflow.com/a/20448357
- if ( ! file.type && file.size%4096 === 0 && file.size <= 102400) {
+ if ( ! file.type && file.size % 4096 === 0 && file.size <= 102400) {
var dirUploadFailure = false;
try {
var reader = new FileReader();
@@ -390,7 +792,7 @@ OC.Upload = {
// end upload for whole selection on error
if (data.errorThrown) {
- // trigger fileupload fail
+ // trigger fileupload fail handler
var fu = that.data('blueimp-fileupload') || that.data('fileupload');
fu._trigger('fail', e, data);
return false; //don't upload anything
@@ -405,9 +807,7 @@ OC.Upload = {
var callbacks = {
onNoConflicts: function (selection) {
- $.each(selection.uploads, function(i, upload) {
- upload.submit();
- });
+ self.submitUploads(selection.uploads);
},
onSkipConflicts: function (selection) {
//TODO mark conflicting files as toskip
@@ -425,7 +825,7 @@ OC.Upload = {
}
};
- OC.Upload.checkExistingFiles(selection, callbacks);
+ self.checkExistingFiles(selection, callbacks);
}
@@ -436,106 +836,54 @@ OC.Upload = {
* @param {object} e
*/
start: function(e) {
- OC.Upload.log('start', e, null);
+ self.log('start', e, null);
//hide the tooltip otherwise it covers the progress bar
$('#upload').tipsy('hide');
},
- submit: function(e, data) {
- OC.Upload.rememberUpload(data);
- if (!data.formData) {
- data.formData = {};
- }
-
- var fileDirectory = '';
- if(typeof data.files[0].relativePath !== 'undefined') {
- fileDirectory = data.files[0].relativePath;
- }
-
- var params = {
- requesttoken: oc_requesttoken,
- dir: data.targetDir || FileList.getCurrentDirectory(),
- file_directory: fileDirectory,
- };
- if (data.files[0].isReceivedShare) {
- params.isReceivedShare = true;
- }
-
- addFormData(data.formData, params);
- },
fail: function(e, data) {
- OC.Upload.log('fail', e, data);
- if (typeof data.textStatus !== 'undefined' && data.textStatus !== 'success' ) {
- if (data.textStatus === 'abort') {
- OC.Upload.showUploadCancelMessage();
- } else {
- // HTTP connection problem
- var message = t('files', 'Error uploading file "{fileName}": {message}', {
- fileName: escapeHTML(data.files[0].name),
- message: data.errorThrown
- }, undefined, {escape: false});
- OC.Notification.show(message, {timeout: 0, type: 'error'});
- if (data.result) {
- var result = JSON.parse(data.result);
- if (result && result[0] && result[0].data && result[0].data.code === 'targetnotfound') {
- // abort upload of next files if any
- OC.Upload.cancelUploads();
- }
- }
- }
+ var upload = self.getUpload(data);
+ var status = upload.getResponseStatus();
+ self.log('fail', e, upload);
+
+ if (data.textStatus === 'abort') {
+ self.showUploadCancelMessage();
+ } else if (status === 412) {
+ // file already exists
+ self.showConflict(upload);
+ } else if (status === 404) {
+ // target folder does not exist any more
+ OC.Notification.showTemporary(
+ t('files', 'Target folder "{dir}" does not exist any more', {dir: upload.getFullPath()})
+ );
+ self.cancelUploads();
+ } else if (status === 507) {
+ // not enough space
+ OC.Notification.showTemporary(
+ t('files', 'Not enough free space')
+ );
+ self.cancelUploads();
+ } else {
+ // HTTP connection problem or other error
+ OC.Notification.showTemporary(data.errorThrown, {timeout: 10});
}
- OC.Upload.deleteUpload(data);
+ upload.deleteUpload();
},
/**
* called for every successful upload
* @param {object} e
* @param {object} data
*/
- done: function(e, data) {
- OC.Upload.log('done', e, data);
- // handle different responses (json or body from iframe for ie)
- var response;
- if (typeof data.result === 'string') {
- response = data.result;
- } else {
- //fetch response from iframe
- response = data.result[0].body.innerText;
- }
- var result = JSON.parse(response);
+ done:function(e, data) {
+ var upload = self.getUpload(data);
+ var that = $(this);
+ self.log('done', e, upload);
- delete data.jqXHR;
-
- var fu = $(this).data('blueimp-fileupload') || $(this).data('fileupload');
-
- if (result.status === 'error' && result.data && result.data.message){
- data.textStatus = 'servererror';
- data.errorThrown = result.data.message;
- fu._trigger('fail', e, data);
- } else if (typeof result[0] === 'undefined') {
- data.textStatus = 'servererror';
- data.errorThrown = t('files', 'Could not get result from server.');
- fu._trigger('fail', e, data);
- } else if (result[0].status === 'readonly') {
- var original = result[0];
- var replacement = data.files[0];
- OC.dialogs.fileexists(data, original, replacement, OC.Upload);
- } else if (result[0].status === 'existserror') {
- //show "file already exists" dialog
- var original = result[0];
- var replacement = data.files[0];
- OC.dialogs.fileexists(data, original, replacement, OC.Upload);
- } else if (result[0].status !== 'success') {
- //delete data.jqXHR;
- data.textStatus = 'servererror';
- data.errorThrown = result[0].data.message; // error message has been translated on server
+ var status = upload.getResponseStatus();
+ if (status < 200 || status >= 300) {
+ // trigger fail handler
+ var fu = that.data('blueimp-fileupload') || that.data('fileupload');
fu._trigger('fail', e, data);
- } else { // Successful upload
- // Checking that the uploaded file is the last one and contained in the current directory
- if (data.files[0] === data.originalFiles[data.originalFiles.length - 1] &&
- result[0].directory === FileList.getCurrentDirectory()) {
- // Scroll to the last uploaded file and highlight all of them
- var fileList = _.pluck(data.originalFiles, 'name');
- FileList.highlightFiles(fileList);
- }
+ return;
}
},
/**
@@ -544,15 +892,14 @@ OC.Upload = {
* @param {object} data
*/
stop: function(e, data) {
- OC.Upload.log('stop', e, data);
+ self.log('stop', e, data);
}
};
// initialize jquery fileupload (blueimp)
- var fileupload = $('#file_upload_start').fileupload(file_upload_param);
- window.file_upload_param = fileupload;
+ var fileupload = this.$uploadEl.fileupload(this.fileUploadParam);
- if (supportAjaxUploadWithProgress()) {
+ if (this._supportAjaxUploadWithProgress()) {
//remaining time
var lastUpdate = new Date().getMilliseconds();
var lastSize = 0;
@@ -561,11 +908,12 @@ OC.Upload = {
var bufferIndex = 0;
var bufferTotal = 0;
for(var i = 0; i < bufferSize;i++){
- buffer[i] = 0;
+ buffer[i] = 0;
}
+
// add progress handlers
fileupload.on('fileuploadadd', function(e, data) {
- OC.Upload.log('progress handle fileuploadadd', e, data);
+ self.log('progress handle fileuploadadd', e, data);
//show cancel button
//if (data.dataType !== 'iframe') { //FIXME when is iframe used? only for ie?
// $('#uploadprogresswrapper .stop').show();
@@ -573,7 +921,7 @@ OC.Upload = {
});
// add progress handlers
fileupload.on('fileuploadstart', function(e, data) {
- OC.Upload.log('progress handle fileuploadstart', e, data);
+ self.log('progress handle fileuploadstart', e, data);
$('#uploadprogresswrapper .stop').show();
$('#uploadprogresswrapper .label').show();
$('#uploadprogressbar').progressbar({value: 0});
@@ -584,14 +932,14 @@ OC.Upload = {
+ t('files', '...')
+ '</span></em>');
$('#uploadprogressbar').tipsy({gravity:'n', fade:true, live:true});
- OC.Upload._showProgressBar();
+ self._showProgressBar();
});
fileupload.on('fileuploadprogress', function(e, data) {
- OC.Upload.log('progress handle fileuploadprogress', e, data);
+ self.log('progress handle fileuploadprogress', e, data);
//TODO progressbar in row
});
fileupload.on('fileuploadprogressall', function(e, data) {
- OC.Upload.log('progress handle fileuploadprogressall', e, data);
+ self.log('progress handle fileuploadprogressall', e, data);
var progress = (data.loaded / data.total) * 100;
var thisUpdate = new Date().getMilliseconds();
var diffUpdate = (thisUpdate - lastUpdate)/1000; // eg. 2s
@@ -608,14 +956,14 @@ OC.Upload = {
var smoothRemainingSeconds = (bufferTotal / bufferSize); //seconds
var date = new Date(smoothRemainingSeconds * 1000);
var timeStringDesktop = "";
- var timeStringMobile = "";
+ var timeStringMobile = "";
if(date.getUTCHours() > 0){
- timeStringDesktop = t('files', '{hours}:{minutes}:{seconds} hour{plural_s} left' , {
+ timeStringDesktop = t('files', '{hours}:{minutes}:{seconds} hour{plural_s} left' , {
hours:date.getUTCHours(),
minutes: ('0' + date.getUTCMinutes()).slice(-2),
seconds: ('0' + date.getUTCSeconds()).slice(-2),
plural_s: ( smoothRemainingSeconds === 3600 ? "": "s") // 1 hour = 1*60m*60s = 3600s
- });
+ });
timeStringMobile = t('files', '{hours}:{minutes}h' , {
hours:date.getUTCHours(),
minutes: ('0' + date.getUTCMinutes()).slice(-2),
@@ -626,12 +974,12 @@ OC.Upload = {
minutes: date.getUTCMinutes(),
seconds: ('0' + date.getUTCSeconds()).slice(-2),
plural_s: (smoothRemainingSeconds === 60 ? "": "s") // 1 minute = 1*60s = 60s
- });
+ });
timeStringMobile = t('files', '{minutes}:{seconds}m' , {
minutes: date.getUTCMinutes(),
seconds: ('0' + date.getUTCSeconds()).slice(-2)
});
- } else if(date.getUTCSeconds() > 0){
+ } else if(date.getUTCSeconds() > 0){
timeStringDesktop = t('files', '{seconds} second{plural_s} left' , {
seconds: date.getUTCSeconds(),
plural_s: (smoothRemainingSeconds === 1 ? "": "s") // 1 second = 1s = 1s
@@ -653,14 +1001,16 @@ OC.Upload = {
$('#uploadprogressbar').progressbar('value', progress);
});
fileupload.on('fileuploadstop', function(e, data) {
- OC.Upload.log('progress handle fileuploadstop', e, data);
- OC.Upload._hideProgressBar();
+ self.log('progress handle fileuploadstop', e, data);
+
+ self.clear();
+ self._hideProgressBar();
});
fileupload.on('fileuploadfail', function(e, data) {
- OC.Upload.log('progress handle fileuploadfail', e, data);
+ self.log('progress handle fileuploadfail', e, data);
//if user pressed cancel hide upload progress bar and cancel button
if (data.errorThrown === 'abort') {
- OC.Upload._hideProgressBar();
+ self._hideProgressBar();
}
});
var disableDropState = function() {
@@ -715,36 +1065,20 @@ OC.Upload = {
}
}
- $.assocArraySize = function(obj) {
- // http://stackoverflow.com/a/6700/11236
- var size = 0, key;
- for (key in obj) {
- if (obj.hasOwnProperty(key)) {
- size++;
- }
- }
- return size;
- };
-
// warn user not to leave the page while upload is in progress
$(window).on('beforeunload', function(e) {
- if (OC.Upload.isProcessing()) {
+ if (self.isProcessing()) {
return t('files', 'File upload is in progress. Leaving the page now will cancel the upload.');
}
});
//add multiply file upload attribute to all browsers except konqueror (which crashes when it's used)
if (navigator.userAgent.search(/konqueror/i) === -1) {
- $('#file_upload_start').attr('multiple', 'multiple');
+ this.$uploadEl.attr('multiple', 'multiple');
}
- window.file_upload_param = file_upload_param;
- return file_upload_param;
+ return this.fileUploadParam;
}
};
-$(document).ready(function() {
- OC.Upload.init();
-});
-
diff --git a/apps/files/js/filelist.js b/apps/files/js/filelist.js
index 159d008e6e6..43cb3bac64b 100644
--- a/apps/files/js/filelist.js
+++ b/apps/files/js/filelist.js
@@ -30,6 +30,7 @@
* @param {Object} [options.dragOptions] drag options, disabled by default
* @param {Object} [options.folderDropOptions] folder drop options, disabled by default
* @param {boolean} [options.detailsViewEnabled=true] whether to enable details view
+ * @param {boolean} [options.enableUpload=false] whether to enable uploader
* @param {OC.Files.Client} [options.filesClient] files client to use
*/
var FileList = function($el, options) {
@@ -189,6 +190,11 @@
_folderDropOptions: null,
/**
+ * @type OC.Uploader
+ */
+ _uploader: null,
+
+ /**
* Initialize the file list and its components
*
* @param $el container element with existing markup for the #controls
@@ -328,8 +334,6 @@
this.$el.find('.selectedActions a').tooltip({placement:'top'});
- this.setupUploadEvents();
-
this.$container.on('scroll', _.bind(this._onScroll, this));
if (options.scrollTo) {
@@ -338,6 +342,20 @@
});
}
+ if (options.enableUpload) {
+ // TODO: auto-create this element
+ var $uploadEl = this.$el.find('#file_upload_start');
+ if ($uploadEl.exists()) {
+ this._uploader = new OC.Uploader($uploadEl, {
+ fileList: this,
+ filesClient: this.filesClient,
+ dropZone: $('#content')
+ });
+
+ this.setupUploadEvents(this._uploader);
+ }
+ }
+
OC.Plugins.attach('OCA.Files.FileList', this);
},
@@ -1420,7 +1438,10 @@
return;
}
this._setCurrentDir(targetDir, changeUrl, fileId);
- return this.reload().then(function(success){
+
+ // discard finished uploads list, we'll get it through a regular reload
+ this._uploads = {};
+ this.reload().then(function(success){
if (!success) {
self.changeDirectory(currentDir, true);
}
@@ -1660,6 +1681,24 @@
return OCA.Files.Files.getDownloadUrl(files, dir || this.getCurrentDirectory(), isDir);
},
+ getUploadUrl: function(fileName, dir) {
+ if (_.isUndefined(dir)) {
+ dir = this.getCurrentDirectory();
+ }
+
+ var pathSections = dir.split('/');
+ if (!_.isUndefined(fileName)) {
+ pathSections.push(fileName);
+ }
+ var encodedPath = '';
+ _.each(pathSections, function(section) {
+ if (section !== '') {
+ encodedPath += '/' + encodeURIComponent(section);
+ }
+ });
+ return OC.linkToRemoteBase('webdav') + encodedPath;
+ },
+
/**
* Generates a preview URL based on the URL space.
* @param urlSpec attributes for the URL
@@ -2121,19 +2160,11 @@
)
.done(function() {
// TODO: error handling / conflicts
- self.filesClient.getFileInfo(
- targetPath, {
- properties: self._getWebdavProperties()
- }
- )
- .then(function(status, data) {
- self.add(data, {animate: true, scrollTo: true});
- deferred.resolve(status, data);
- })
- .fail(function(status) {
- OC.Notification.showTemporary(t('files', 'Could not create file "{file}"', {file: name}));
- deferred.reject(status);
- });
+ self.addAndFetchFileInfo(targetPath, '', {scrollTo: true}).then(function(status, data) {
+ deferred.resolve(status, data);
+ }, function() {
+ OC.Notification.showTemporary(t('files', 'Could not create file "{file}"', {file: name}));
+ });
})
.fail(function(status) {
if (status === 412) {
@@ -2174,32 +2205,19 @@
var targetPath = this.getCurrentDirectory() + '/' + name;
this.filesClient.createDirectory(targetPath)
- .done(function(createStatus) {
- self.filesClient.getFileInfo(
- targetPath, {
- properties: self._getWebdavProperties()
- }
- )
- .done(function(status, data) {
- self.add(data, {animate: true, scrollTo: true});
- deferred.resolve(status, data);
- })
- .fail(function() {
- OC.Notification.showTemporary(t('files', 'Could not create folder "{dir}"', {dir: name}));
- deferred.reject(createStatus);
- });
+ .done(function() {
+ self.addAndFetchFileInfo(targetPath, '', {scrollTo:true}).then(function(status, data) {
+ deferred.resolve(status, data);
+ }, function() {
+ OC.Notification.showTemporary(t('files', 'Could not create folder "{dir}"', {dir: name}));
+ });
})
.fail(function(createStatus) {
// method not allowed, folder might exist already
if (createStatus === 405) {
- self.filesClient.getFileInfo(
- targetPath, {
- properties: self._getWebdavProperties()
- }
- )
+ // add it to the list, for completeness
+ self.addAndFetchFileInfo(targetPath, '', {scrollTo:true})
.done(function(status, data) {
- // add it to the list, for completeness
- self.add(data, {animate: true, scrollTo: true});
OC.Notification.showTemporary(
t('files', 'Could not create folder "{dir}" because it already exists', {dir: name})
);
@@ -2222,6 +2240,60 @@
},
/**
+ * Add file into the list by fetching its information from the server first.
+ *
+ * If the given directory does not match the current directory, nothing will
+ * be fetched.
+ *
+ * @param {String} fileName file name
+ * @param {String} [dir] optional directory, defaults to the current one
+ * @param {Object} options same options as #add
+ * @return {Promise} promise that resolves with the file info, or an
+ * already resolved Promise if no info was fetched. The promise rejects
+ * if the file was not found or an error occurred.
+ *
+ * @since 9.0
+ */
+ addAndFetchFileInfo: function(fileName, dir, options) {
+ var self = this;
+ var deferred = $.Deferred();
+ if (_.isUndefined(dir)) {
+ dir = this.getCurrentDirectory();
+ } else {
+ dir = dir || '/';
+ }
+
+ var targetPath = OC.joinPaths(dir, fileName);
+
+ if ((OC.dirname(targetPath) || '/') !== this.getCurrentDirectory()) {
+ // no need to fetch information
+ deferred.resolve();
+ return deferred.promise();
+ }
+
+ var addOptions = _.extend({
+ animate: true,
+ scrollTo: false
+ }, options || {});
+
+ this.filesClient.getFileInfo(targetPath, {
+ properties: this._getWebdavProperties()
+ })
+ .then(function(status, data) {
+ // remove first to avoid duplicates
+ self.remove(data.name);
+ self.add(data, addOptions);
+ deferred.resolve(status, data);
+ })
+ .fail(function(status) {
+ OC.Notification.showTemporary(t('files', 'Could not create file "{file}"', {file: name}));
+ deferred.reject(status);
+ });
+
+ return deferred.promise();
+ },
+
+ /**
* Returns whether the given file name exists in the list
*
* @param {string} file file name
@@ -2590,18 +2662,16 @@
/**
* Setup file upload events related to the file-upload plugin
*/
- setupUploadEvents: function() {
+ setupUploadEvents: function($uploadEl) {
var self = this;
- // handle upload events
- var fileUploadStart = this.$el;
- var delegatedElement = '#file_upload_start';
+ self._uploads = {};
// detect the progress bar resize
- fileUploadStart.on('resized', this._onResize);
+ $uploadEl.on('resized', this._onResize);
- fileUploadStart.on('fileuploaddrop', delegatedElement, function(e, data) {
- OC.Upload.log('filelist handle fileuploaddrop', e, data);
+ $uploadEl.on('fileuploaddrop', function(e, data) {
+ self._uploader.log('filelist handle fileuploaddrop', e, data);
if (self.$el.hasClass('hidden')) {
// do not upload to invisible lists
@@ -2664,13 +2734,8 @@
}
}
});
- fileUploadStart.on('fileuploadadd', function(e, data) {
- OC.Upload.log('filelist handle fileuploadadd', e, data);
-
- //finish delete if we are uploading a deleted file
- if (self.deleteFiles && self.deleteFiles.indexOf(data.files[0].name)!==-1) {
- self.finishDelete(null, true); //delete file before continuing
- }
+ $uploadEl.on('fileuploadadd', function(e, data) {
+ self._uploader.log('filelist handle fileuploadadd', e, data);
// add ui visualization to existing folder
if (data.context && data.context.data('type') === 'dir') {
@@ -2692,126 +2757,57 @@
}
}
+ if (!data.targetDir) {
+ data.targetDir = self.getCurrentDirectory();
+ }
+
});
/*
* when file upload done successfully add row to filelist
* update counter when uploading to sub folder
*/
- fileUploadStart.on('fileuploaddone', function(e, data) {
- OC.Upload.log('filelist handle fileuploaddone', e, data);
+ $uploadEl.on('fileuploaddone', function(e, data) {
+ self._uploader.log('filelist handle fileuploaddone', e, data);
- var response;
- if (typeof data.result === 'string') {
- response = data.result;
- } else {
- // fetch response from iframe
- response = data.result[0].body.innerText;
+ var status = data.jqXHR.status;
+ if (status < 200 || status >= 300) {
+ // error was handled in OC.Uploads already
+ return;
}
- var result = JSON.parse(response);
-
- if (typeof result[0] !== 'undefined' && result[0].status === 'success') {
- var file = result[0];
- var size = 0;
-
- if (data.context && data.context.data('type') === 'dir') {
-
- // update upload counter ui
- var uploadText = data.context.find('.uploadtext');
- var currentUploads = parseInt(uploadText.attr('currentUploads'), 10);
- currentUploads -= 1;
- uploadText.attr('currentUploads', currentUploads);
- var translatedText = n('files', 'Uploading %n file', 'Uploading %n files', currentUploads);
- if (currentUploads === 0) {
- self.showFileBusyState(uploadText.closest('tr'), false);
- uploadText.text(translatedText);
- uploadText.hide();
- } else {
- uploadText.text(translatedText);
- }
-
- // update folder size
- size = parseInt(data.context.data('size'), 10);
- size += parseInt(file.size, 10);
- data.context.attr('data-size', size);
- data.context.find('td.filesize').text(humanFileSize(size));
- } else {
- // only append new file if uploaded into the current folder
- if (file.directory !== self.getCurrentDirectory()) {
- // Uploading folders actually uploads a list of files
- // for which the target directory (file.directory) might lie deeper
- // than the current directory
-
- var fileDirectory = file.directory.replace('/','').replace(/\/$/, "");
- var currentDirectory = self.getCurrentDirectory().replace('/','').replace(/\/$/, "") + '/';
-
- if (currentDirectory !== '/') {
- // abort if fileDirectory does not start with current one
- if (fileDirectory.indexOf(currentDirectory) !== 0) {
- return;
- }
-
- // remove the current directory part
- fileDirectory = fileDirectory.substr(currentDirectory.length);
- }
-
- // only take the first section of the path
- fileDirectory = fileDirectory.split('/');
-
- var fd;
- // if the first section exists / is a subdir
- if (fileDirectory.length) {
- fileDirectory = fileDirectory[0];
-
- // See whether it is already in the list
- fd = self.findFileEl(fileDirectory);
- if (fd.length === 0) {
- var dir = {
- name: fileDirectory,
- type: 'dir',
- mimetype: 'httpd/unix-directory',
- permissions: file.permissions,
- size: 0,
- id: file.parentId
- };
- fd = self.add(dir, {insert: true});
- }
-
- // update folder size
- size = parseInt(fd.attr('data-size'), 10);
- size += parseInt(file.size, 10);
- fd.attr('data-size', size);
- fd.find('td.filesize').text(OC.Util.humanFileSize(size));
- }
-
- return;
- }
-
- // add as stand-alone row to filelist
- size = t('files', 'Pending');
- if (data.files[0].size>=0) {
- size=data.files[0].size;
- }
- //should the file exist in the list remove it
- self.remove(file.name);
- // create new file context
- data.context = self.add(file, {animate: true});
- }
+ var upload = self._uploader.getUpload(data);
+ var fileName = upload.getFileName();
+ var fetchInfoPromise = self.addAndFetchFileInfo(fileName, upload.getFullPath());
+ if (!self._uploads) {
+ self._uploads = {};
+ }
+ if (OC.isSamePath(OC.dirname(upload.getFullPath() + '/'), self.getCurrentDirectory())) {
+ self._uploads[fileName] = fetchInfoPromise;
}
});
- fileUploadStart.on('fileuploadstop', function() {
- OC.Upload.log('filelist handle fileuploadstop');
-
- //cleanup uploading to a dir
- var uploadText = self.$fileList.find('tr .uploadtext');
- self.showFileBusyState(uploadText.closest('tr'), false);
- uploadText.fadeOut();
- uploadText.attr('currentUploads', 0);
-
+ $uploadEl.on('fileuploadcreatedfolder', function(e, fullPath) {
+ self.addAndFetchFileInfo(OC.basename(fullPath), OC.dirname(fullPath));
+ });
+ $uploadEl.on('fileuploadstop', function() {
+ self._uploader.log('filelist handle fileuploadstop');
+
+ // prepare list of uploaded file names in the current directory
+ // and discard the other ones
+ var promises = _.values(self._uploads);
+ var fileNames = _.keys(self._uploads);
+ self._uploads = [];
+
+ // as soon as all info is fetched
+ $.when.apply($, promises).then(function() {
+ // highlight uploaded files
+ self.highlightFiles(fileNames);
+ });
self.updateStorageStatistics();
});
- fileUploadStart.on('fileuploadfail', function(e, data) {
- OC.Upload.log('filelist handle fileuploadfail', e, data);
+ $uploadEl.on('fileuploadfail', function(e, data) {
+ self._uploader.log('filelist handle fileuploadfail', e, data);
+
+ self._uploads = [];
//if user pressed cancel hide upload chrome
if (data.errorThrown === 'abort') {
diff --git a/apps/files/js/files.js b/apps/files/js/files.js
index 2873b84bc9a..0be098b2e73 100644
--- a/apps/files/js/files.js
+++ b/apps/files/js/files.js
@@ -226,17 +226,6 @@
// TODO: move file list related code (upload) to OCA.Files.FileList
$('#file_action_panel').attr('activeAction', false);
- // Triggers invisible file input
- $('#upload a').on('click', function() {
- $(this).parent().children('#file_upload_start').trigger('click');
- return false;
- });
-
- // Trigger cancelling of file upload
- $('#uploadprogresswrapper .stop').on('click', function() {
- OC.Upload.cancelUploads();
- });
-
// drag&drop support using jquery.fileupload
// TODO use OC.dialogs
$(document).bind('drop dragover', function (e) {
diff --git a/apps/files/templates/list.php b/apps/files/templates/list.php
index dda7a1814a0..e741849f38b 100644
--- a/apps/files/templates/list.php
+++ b/apps/files/templates/list.php
@@ -75,8 +75,7 @@
</table>
<input type="hidden" name="dir" id="dir" value="" />
<div class="hiddenuploadfield">
- <input type="file" id="file_upload_start" class="hiddenuploadfield" name="files[]"
- data-url="<?php print_unescaped(OCP\Util::linkTo('files', 'ajax/upload.php')); ?>" />
+ <input type="file" id="file_upload_start" class="hiddenuploadfield" name="files[]" />
</div>
<div id="editor"></div><!-- FIXME Do not use this div in your app! It is deprecated and will be removed in the future! -->
<div id="uploadsize-message" title="<?php p($l->t('Upload too large'))?>">
diff --git a/apps/files/tests/js/fileUploadSpec.js b/apps/files/tests/js/fileUploadSpec.js
index 0483d4649d4..bfaf0a9fe57 100644
--- a/apps/files/tests/js/fileUploadSpec.js
+++ b/apps/files/tests/js/fileUploadSpec.js
@@ -19,11 +19,11 @@
*
*/
-/* global FileList */
-
describe('OC.Upload tests', function() {
var $dummyUploader;
var testFile;
+ var uploader;
+ var failStub;
beforeEach(function() {
testFile = {
@@ -46,59 +46,64 @@ describe('OC.Upload tests', function() {
'</div>'
);
$dummyUploader = $('#file_upload_start');
+ uploader = new OC.Uploader($dummyUploader);
+ failStub = sinon.stub();
+ $dummyUploader.on('fileuploadfail', failStub);
});
afterEach(function() {
- delete window.file_upload_param;
$dummyUploader = undefined;
+ failStub = undefined;
});
- describe('Adding files for upload', function() {
- var params;
- var failStub;
- beforeEach(function() {
- params = OC.Upload.init();
- failStub = sinon.stub();
- $dummyUploader.on('fileuploadfail', failStub);
- });
- afterEach(function() {
- params = undefined;
- failStub = undefined;
- });
-
- /**
- * Add file for upload
- * @param file file data
- */
- function addFile(file) {
- return params.add.call(
+ /**
+ * Add file for upload
+ * @param {Array.<File>} files array of file data to simulate upload
+ * @return {Array.<Object>} array of uploadinfo or null if add() returned false
+ */
+ function addFiles(uploader, files) {
+ return _.map(files, function(file) {
+ var jqXHR = {status: 200};
+ var uploadInfo = {
+ originalFiles: files,
+ files: [file],
+ jqXHR: jqXHR,
+ response: sinon.stub.returns(jqXHR),
+ submit: sinon.stub()
+ };
+ if (uploader.fileUploadParam.add.call(
$dummyUploader[0],
{},
- {
- originalFiles: {},
- files: [file]
- });
- }
+ uploadInfo
+ )) {
+ return uploadInfo;
+ }
+ return null;
+ });
+ }
+ describe('Adding files for upload', function() {
it('adds file when size is below limits', function() {
- var result = addFile(testFile);
- expect(result).toEqual(true);
+ var result = addFiles(uploader, [testFile]);
+ expect(result[0]).not.toEqual(null);
+ expect(result[0].submit.calledOnce).toEqual(true);
});
it('adds file when free space is unknown', function() {
var result;
$('#free_space').val(-2);
- result = addFile(testFile);
+ result = addFiles(uploader, [testFile]);
- expect(result).toEqual(true);
+ expect(result[0]).not.toEqual(null);
+ expect(result[0].submit.calledOnce).toEqual(true);
expect(failStub.notCalled).toEqual(true);
});
it('does not add file if it exceeds upload limit', function() {
var result;
$('#upload_limit').val(1000);
- result = addFile(testFile);
+ result = addFiles(uploader, [testFile]);
- expect(result).toEqual(false);
+ expect(result[0]).toEqual(null);
expect(failStub.calledOnce).toEqual(true);
expect(failStub.getCall(0).args[1].textStatus).toEqual('sizeexceedlimit');
expect(failStub.getCall(0).args[1].errorThrown).toEqual(
@@ -109,9 +114,9 @@ describe('OC.Upload tests', function() {
var result;
$('#free_space').val(1000);
- result = addFile(testFile);
+ result = addFiles(uploader, [testFile]);
- expect(result).toEqual(false);
+ expect(result[0]).toEqual(null);
expect(failStub.calledOnce).toEqual(true);
expect(failStub.getCall(0).args[1].textStatus).toEqual('notenoughspace');
expect(failStub.getCall(0).args[1].errorThrown).toEqual(
@@ -120,12 +125,10 @@ describe('OC.Upload tests', function() {
});
});
describe('Upload conflicts', function() {
- var oldFileList;
var conflictDialogStub;
- var callbacks;
+ var fileList;
beforeEach(function() {
- oldFileList = FileList;
$('#testArea').append(
'<div id="tableContainer">' +
'<table id="filestable">' +
@@ -145,74 +148,56 @@ describe('OC.Upload tests', function() {
'</table>' +
'</div>'
);
- FileList = new OCA.Files.FileList($('#tableContainer'));
+ fileList = new OCA.Files.FileList($('#tableContainer'));
- FileList.add({name: 'conflict.txt', mimetype: 'text/plain'});
- FileList.add({name: 'conflict2.txt', mimetype: 'text/plain'});
+ fileList.add({name: 'conflict.txt', mimetype: 'text/plain'});
+ fileList.add({name: 'conflict2.txt', mimetype: 'text/plain'});
conflictDialogStub = sinon.stub(OC.dialogs, 'fileexists');
- callbacks = {
- onNoConflicts: sinon.stub()
- };
+
+ uploader = new OC.Uploader($dummyUploader, {
+ fileList: fileList
+ });
});
afterEach(function() {
conflictDialogStub.restore();
- FileList.destroy();
- FileList = oldFileList;
+ fileList.destroy();
});
it('does not show conflict dialog when no client side conflict', function() {
- var selection = {
- // yes, the format of uploads is weird...
- uploads: [
- {files: [{name: 'noconflict.txt'}]},
- {files: [{name: 'noconflict2.txt'}]}
- ]
- };
-
- OC.Upload.checkExistingFiles(selection, callbacks);
+ var result = addFiles(uploader, [{name: 'noconflict.txt'}, {name: 'noconflict2.txt'}]);
expect(conflictDialogStub.notCalled).toEqual(true);
- expect(callbacks.onNoConflicts.calledOnce).toEqual(true);
- expect(callbacks.onNoConflicts.calledWith(selection)).toEqual(true);
+ expect(result[0].submit.calledOnce).toEqual(true);
+ expect(result[1].submit.calledOnce).toEqual(true);
});
it('shows conflict dialog when no client side conflict', function() {
- var selection = {
- // yes, the format of uploads is weird...
- uploads: [
- {files: [{name: 'conflict.txt'}]},
- {files: [{name: 'conflict2.txt'}]},
- {files: [{name: 'noconflict.txt'}]}
- ]
- };
-
var deferred = $.Deferred();
conflictDialogStub.returns(deferred.promise());
deferred.resolve();
- OC.Upload.checkExistingFiles(selection, callbacks);
+ var result = addFiles(uploader, [
+ {name: 'conflict.txt'},
+ {name: 'conflict2.txt'},
+ {name: 'noconflict.txt'}
+ ]);
expect(conflictDialogStub.callCount).toEqual(3);
- expect(conflictDialogStub.getCall(1).args[0])
- .toEqual({files: [ { name: 'conflict.txt' } ]});
+ expect(conflictDialogStub.getCall(1).args[0].getFileName())
+ .toEqual('conflict.txt');
expect(conflictDialogStub.getCall(1).args[1])
.toEqual({ name: 'conflict.txt', mimetype: 'text/plain', directory: '/' });
expect(conflictDialogStub.getCall(1).args[2]).toEqual({ name: 'conflict.txt' });
// yes, the dialog must be called several times...
- expect(conflictDialogStub.getCall(2).args[0]).toEqual({
- files: [ { name: 'conflict2.txt' } ]
- });
+ expect(conflictDialogStub.getCall(2).args[0].getFileName()).toEqual('conflict2.txt');
expect(conflictDialogStub.getCall(2).args[1])
.toEqual({ name: 'conflict2.txt', mimetype: 'text/plain', directory: '/' });
expect(conflictDialogStub.getCall(2).args[2]).toEqual({ name: 'conflict2.txt' });
- expect(callbacks.onNoConflicts.calledOnce).toEqual(true);
- expect(callbacks.onNoConflicts.calledWith({
- uploads: [
- {files: [{name: 'noconflict.txt'}]}
- ]
- })).toEqual(true);
+ expect(result[0].submit.calledOnce).toEqual(false);
+ expect(result[1].submit.calledOnce).toEqual(false);
+ expect(result[2].submit.calledOnce).toEqual(true);
});
});
});
diff --git a/apps/files/tests/js/filelistSpec.js b/apps/files/tests/js/filelistSpec.js
index 304f8438a59..55ab2c535af 100644
--- a/apps/files/tests/js/filelistSpec.js
+++ b/apps/files/tests/js/filelistSpec.js
@@ -159,7 +159,8 @@ describe('OCA.Files.FileList tests', function() {
pageSizeStub = sinon.stub(OCA.Files.FileList.prototype, 'pageSize').returns(20);
fileList = new OCA.Files.FileList($('#app-content-files'), {
filesClient: filesClient,
- config: filesConfig
+ config: filesConfig,
+ enableUpload: true
});
});
afterEach(function() {
@@ -2441,7 +2442,7 @@ describe('OCA.Files.FileList tests', function() {
deferredInfo.resolve(
200,
- new FileInfo({
+ new FileInfo({
path: '/subdir',
name: 'test.txt',
mimetype: 'text/plain'
@@ -2501,12 +2502,70 @@ describe('OCA.Files.FileList tests', function() {
// TODO: error cases
// TODO: unique name cases
});
+ describe('addAndFetchFileInfo', function() {
+ var getFileInfoStub;
+ var getFileInfoDeferred;
+
+ beforeEach(function() {
+ getFileInfoDeferred = $.Deferred();
+ getFileInfoStub = sinon.stub(OC.Files.Client.prototype, 'getFileInfo');
+ getFileInfoStub.returns(getFileInfoDeferred.promise());
+ });
+ afterEach(function() {
+ getFileInfoStub.restore();
+ });
+ it('does not fetch if the given folder is not the current one', function() {
+ var promise = fileList.addAndFetchFileInfo('testfile.txt', '/another');
+ expect(getFileInfoStub.notCalled).toEqual(true);
+
+ expect(promise.state()).toEqual('resolved');
+ });
+ it('fetches info when folder is the current one', function() {
+ fileList.addAndFetchFileInfo('testfile.txt', '/subdir');
+ expect(getFileInfoStub.calledOnce).toEqual(true);
+ expect(getFileInfoStub.getCall(0).args[0]).toEqual('/subdir/testfile.txt');
+ });
+ it('adds file data to list when fetching is done', function() {
+ fileList.addAndFetchFileInfo('testfile.txt', '/subdir');
+ getFileInfoDeferred.resolve(200, {
+ name: 'testfile.txt',
+ size: 100
+ });
+ expect(fileList.findFileEl('testfile.txt').attr('data-size')).toEqual('100');
+ });
+ it('replaces file data to list when fetching is done', function() {
+ fileList.addAndFetchFileInfo('testfile.txt', '/subdir', {replace: true});
+ fileList.add({
+ name: 'testfile.txt',
+ size: 95
+ });
+ getFileInfoDeferred.resolve(200, {
+ name: 'testfile.txt',
+ size: 100
+ });
+ expect(fileList.findFileEl('testfile.txt').attr('data-size')).toEqual('100');
+ });
+ it('resolves promise with file data when fetching is done', function() {
+ var promise = fileList.addAndFetchFileInfo('testfile.txt', '/subdir', {replace: true});
+ getFileInfoDeferred.resolve(200, {
+ name: 'testfile.txt',
+ size: 100
+ });
+ expect(promise.state()).toEqual('resolved');
+ promise.then(function(status, data) {
+ expect(status).toEqual(200);
+ expect(data.name).toEqual('testfile.txt');
+ expect(data.size).toEqual(100);
+ });
+ });
+ });
/**
* Test upload mostly by testing the code inside the event handlers
* that were registered on the magic upload object
*/
describe('file upload', function() {
var $uploader;
+ var uploadData;
beforeEach(function() {
// note: this isn't the real blueimp file uploader from jquery.fileupload
@@ -2514,14 +2573,52 @@ describe('OCA.Files.FileList tests', function() {
// test the response of the handlers
$uploader = $('#file_upload_start');
fileList.setFiles(testFiles);
+ // simulate data structure from jquery.upload
+ uploadData = {
+ files: [{
+ name: 'upload.txt'
+ }]
+ };
});
afterEach(function() {
$uploader = null;
+ uploadData = null;
});
+ describe('enableupload', function() {
+ it('sets up uploader when enableUpload is true', function() {
+ expect(fileList._uploader).toBeDefined();
+ });
+ it('does not sets up uploader when enableUpload is false', function() {
+ fileList.destroy();
+ fileList = new OCA.Files.FileList($('#app-content-files'), {
+ filesClient: filesClient
+ });
+ expect(fileList._uploader).toBeFalsy();
+ });
+ });
+
+ describe('adding files for upload', function() {
+ /**
+ * Simulate add event on the given target
+ *
+ * @return event object including the result
+ */
+ function addFile(data) {
+ var ev = new $.Event('fileuploadadd', {});
+ // using triggerHandler instead of trigger so we can pass
+ // extra data
+ $uploader.triggerHandler(ev, data || {});
+ return ev;
+ }
+
+ it('sets target dir to the current directory', function() {
+ addFile(uploadData);
+ expect(uploadData.targetDir).toEqual('/subdir');
+ });
+ });
describe('dropping external files', function() {
- var uploadData;
/**
* Simulate drop event on the given target
@@ -2540,17 +2637,6 @@ describe('OCA.Files.FileList tests', function() {
return ev;
}
- beforeEach(function() {
- // simulate data structure from jquery.upload
- uploadData = {
- files: [{
- relativePath: 'fileToUpload.txt'
- }]
- };
- });
- afterEach(function() {
- uploadData = null;
- });
it('drop on a tr or crumb outside file list does not trigger upload', function() {
var $anotherTable = $('<table><tbody><tr><td>outside<div class="crumb">crumb</div></td></tr></table>');
var ev;
@@ -2574,12 +2660,14 @@ describe('OCA.Files.FileList tests', function() {
ev = dropOn(fileList.$fileList.find('th:first'), uploadData);
expect(ev.result).not.toEqual(false);
+ expect(uploadData.targetDir).toEqual('/subdir');
});
it('drop on an element on the table container triggers upload', function() {
var ev;
ev = dropOn($('#app-content-files'), uploadData);
expect(ev.result).not.toEqual(false);
+ expect(uploadData.targetDir).toEqual('/subdir');
});
it('drop on an element inside the table does not trigger upload if no upload permission', function() {
$('#permissions').val(0);
@@ -2603,6 +2691,7 @@ describe('OCA.Files.FileList tests', function() {
ev = dropOn(fileList.findFileEl('One.txt').find('td:first'), uploadData);
expect(ev.result).not.toEqual(false);
+ expect(uploadData.targetDir).toEqual('/subdir');
});
it('drop on a folder row inside the table triggers upload to target folder', function() {
var ev;
@@ -2635,6 +2724,97 @@ describe('OCA.Files.FileList tests', function() {
expect(fileList.findFileEl('afile.txt').find('.uploadtext').length).toEqual(0);
});
});
+
+ describe('after folder creation due to folder upload', function() {
+ it('fetches folder info', function() {
+ var fetchInfoStub = sinon.stub(fileList, 'addAndFetchFileInfo');
+
+ var ev = new $.Event('fileuploadcreatedfolder', {});
+ $uploader.triggerHandler(ev, '/subdir/newfolder');
+
+ expect(fetchInfoStub.calledOnce).toEqual(true);
+ expect(fetchInfoStub.getCall(0).args[0]).toEqual('newfolder');
+ expect(fetchInfoStub.getCall(0).args[1]).toEqual('/subdir');
+
+ fetchInfoStub.restore();
+ });
+ });
+
+ describe('after upload', function() {
+ var fetchInfoStub;
+
+ beforeEach(function() {
+ fetchInfoStub = sinon.stub(fileList, 'addAndFetchFileInfo');
+
+ });
+ afterEach(function() {
+ fetchInfoStub.restore();
+ });
+
+
+ function createUpload(name, dir) {
+ var data = {
+ files: [{
+ name: name
+ }],
+ upload: {
+ getFileName: sinon.stub().returns(name),
+ getFullPath: sinon.stub().returns(dir)
+ },
+ jqXHR: {
+ status: 200
+ }
+ }
+ return data;
+ }
+
+ /**
+ * Simulate add event on the given target
+ *
+ * @return event object including the result
+ */
+ function addFile(data) {
+ var ev = new $.Event('fileuploaddone', {});
+ // using triggerHandler instead of trigger so we can pass
+ // extra data
+ var deferred = $.Deferred();
+ fetchInfoStub.returns(deferred.promise());
+ $uploader.triggerHandler(ev, data || {});
+ return deferred;
+ }
+
+ it('fetches file info', function() {
+ addFile(createUpload('upload.txt', '/subdir'));
+ expect(fetchInfoStub.calledOnce).toEqual(true);
+ expect(fetchInfoStub.getCall(0).args[0]).toEqual('upload.txt');
+ expect(fetchInfoStub.getCall(0).args[1]).toEqual('/subdir');
+ });
+ it('highlights all uploaded files after all fetches are done', function() {
+ var highlightStub = sinon.stub(fileList, 'highlightFiles');
+ var def1 = addFile(createUpload('upload.txt', '/subdir'));
+ var def2 = addFile(createUpload('upload2.txt', '/subdir'));
+ var def3 = addFile(createUpload('upload3.txt', '/another'));
+ $uploader.triggerHandler(new $.Event('fileuploadstop'));
+
+ expect(highlightStub.notCalled).toEqual(true);
+ def1.resolve();
+ expect(highlightStub.notCalled).toEqual(true);
+ def2.resolve();
+ def3.resolve();
+ expect(highlightStub.calledOnce).toEqual(true);
+ expect(highlightStub.getCall(0).args[0]).toEqual(['upload.txt', 'upload2.txt']);
+
+ highlightStub.restore();
+ });
+ it('queries storage stats', function() {
+ var statStub = sinon.stub(fileList, 'updateStorageStatistics');
+ addFile(createUpload('upload.txt', '/subdir'));
+ expect(statStub.notCalled).toEqual(true);
+ $uploader.triggerHandler(new $.Event('fileuploadstop'));
+ expect(statStub.calledOnce).toEqual(true);
+ statStub.restore();
+ });
+ });
});
describe('Handling errors', function () {
var deferredList;
diff --git a/apps/files_sharing/js/public.js b/apps/files_sharing/js/public.js
index 0dfff235998..0045c7156d2 100644
--- a/apps/files_sharing/js/public.js
+++ b/apps/files_sharing/js/public.js
@@ -72,7 +72,8 @@ OCA.Sharing.PublicApp = {
folderDropOptions: folderDropOptions,
fileActions: fileActions,
detailsViewEnabled: false,
- filesClient: filesClient
+ filesClient: filesClient,
+ enableUpload: true
}
);
this.files = OCA.Files.Files;
@@ -170,6 +171,30 @@ OCA.Sharing.PublicApp = {
return OC.generateUrl('/s/' + token + '/download') + '?' + OC.buildQueryString(params);
};
+ this.fileList.getUploadUrl = function(fileName, dir) {
+ if (_.isUndefined(dir)) {
+ dir = this.getCurrentDirectory();
+ }
+
+ var pathSections = dir.split('/');
+ if (!_.isUndefined(fileName)) {
+ pathSections.push(fileName);
+ }
+ var encodedPath = '';
+ _.each(pathSections, function(section) {
+ if (section !== '') {
+ encodedPath += '/' + encodeURIComponent(section);
+ }
+ });
+ var base = '';
+
+ if (!this._uploader.isXHRUpload()) {
+ // also add auth in URL due to POST workaround
+ base = OC.getProtocol() + '://' + token + '@' + OC.getHost() + (OC.getPort() ? ':' + OC.getPort() : '');
+ }
+ return base + OC.getRootPath() + '/public.php/webdav' + encodedPath;
+ };
+
this.fileList.getAjaxUrl = function (action, params) {
params = params || {};
params.t = token;
@@ -203,20 +228,12 @@ OCA.Sharing.PublicApp = {
OCA.Files.FileList.prototype.updateEmptyContent.apply(this, arguments);
};
- var file_upload_start = $('#file_upload_start');
- file_upload_start.on('fileuploadadd', function (e, data) {
- var fileDirectory = '';
- if (typeof data.files[0].relativePath !== 'undefined') {
- fileDirectory = data.files[0].relativePath;
+ this.fileList._uploader.on('fileuploadadd', function(e, data) {
+ if (!data.headers) {
+ data.headers = {};
}
- // Add custom data to the upload handler
- data.formData = {
- requesttoken: $('#publicUploadRequestToken').val(),
- dirToken: $('#dirToken').val(),
- subdir: data.targetDir || self.fileList.getCurrentDirectory(),
- file_directory: fileDirectory
- };
+ data.headers.Authorization = 'Basic ' + btoa(token + ':');
});
// do not allow sharing from the public page
diff --git a/apps/files_sharing/tests/js/publicAppSpec.js b/apps/files_sharing/tests/js/publicAppSpec.js
index 58565744882..e8ec9899ecc 100644
--- a/apps/files_sharing/tests/js/publicAppSpec.js
+++ b/apps/files_sharing/tests/js/publicAppSpec.js
@@ -87,10 +87,18 @@ describe('OCA.Sharing.PublicApp tests', function() {
});
it('Uses public webdav endpoint', function() {
+ App._initialized = false;
+ fakeServer.restore();
+ window.fakeServer = sinon.fakeServer.create();
+
+ // uploader function messes up with fakeServer
+ var uploaderDetectStub = sinon.stub(OC.Uploader.prototype, '_supportAjaxUploadWithProgress');
+ App.initialize($('#preview'));
expect(fakeServer.requests.length).toEqual(1);
expect(fakeServer.requests[0].method).toEqual('PROPFIND');
expect(fakeServer.requests[0].url).toEqual('https://example.com:9876/owncloud/public.php/webdav/subdir');
expect(fakeServer.requests[0].requestHeaders.Authorization).toEqual('Basic c2g0dG9rOm51bGw=');
+ uploaderDetectStub.restore();
});
describe('Download Url', function() {
@@ -118,5 +126,20 @@ describe('OCA.Sharing.PublicApp tests', function() {
.toEqual(OC.webroot + '/index.php/apps/files_sharing/ajax/test.php?a=1&b=x%20y&t=sh4tok');
});
});
+ describe('Upload Url', function() {
+ var fileList;
+
+ beforeEach(function() {
+ fileList = App.fileList;
+ });
+ it('returns correct upload URL', function() {
+ expect(fileList.getUploadUrl('some file.txt'))
+ .toEqual('/owncloud/public.php/webdav/subdir/some%20file.txt');
+ });
+ it('returns correct upload URL with specified dir', function() {
+ expect(fileList.getUploadUrl('some file.txt', 'sub'))
+ .toEqual('/owncloud/public.php/webdav/sub/some%20file.txt');
+ });
+ });
});
});